From 139ae2a549e2597ae27f2a71f87dcf126d00046c Mon Sep 17 00:00:00 2001 From: Jason Grim Date: Fri, 22 Dec 2023 12:20:06 -0500 Subject: [PATCH 001/115] #119 Created base controllers for native API --- .../sublinksapi/SublinksApiApplication.java | 24 +++++++- .../controllers/AnnouncementController.java | 61 +++++++++++++++++++ .../controllers/CommentController.java | 60 ++++++++++++++++++ .../AbstractSublinksApiController.java | 5 ++ .../controllers/CommunityController.java | 60 ++++++++++++++++++ .../controllers/InstanceController.java | 60 ++++++++++++++++++ .../person/controllers/PersonController.java | 60 ++++++++++++++++++ .../v1/post/controllers/PostController.java | 60 ++++++++++++++++++ .../controllers/PrivatemessageController.java | 60 ++++++++++++++++++ src/main/resources/application.properties | 7 +-- 10 files changed, 450 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/AnnouncementController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/CommentController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/controllers/AbstractSublinksApiController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/CommunityController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/InstanceController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/PostController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/PrivatemessageController.java diff --git a/src/main/java/com/sublinks/sublinksapi/SublinksApiApplication.java b/src/main/java/com/sublinks/sublinksapi/SublinksApiApplication.java index b6b997df..a038831d 100644 --- a/src/main/java/com/sublinks/sublinksapi/SublinksApiApplication.java +++ b/src/main/java/com/sublinks/sublinksapi/SublinksApiApplication.java @@ -31,6 +31,7 @@ public static void main(String[] args) { @Bean public OpenAPI customOpenAPI() { + return new OpenAPI() .components(new Components() .addSecuritySchemes("bearerAuth", @@ -41,9 +42,9 @@ public OpenAPI customOpenAPI() { } @Bean - public GroupedOpenApi v3OpenApi(@Value("${springdoc.version}") String appVersion, - @Value("${springdoc.pathsToMatch}") String pathsToMatch, + public GroupedOpenApi v3LemmyApi(@Value("${springdoc.version}") String appVersion, @Value("#{${springdoc.servers}}") List servers) { + return GroupedOpenApi.builder().group("v3") .addOperationCustomizer((operation, handlerMethod) -> { operation.addSecurityItem(new SecurityRequirement().addList("bearerAuth")); @@ -53,7 +54,24 @@ public GroupedOpenApi v3OpenApi(@Value("${springdoc.version}") String appVersion .title("Lemmy OpenAPI Documentation") .version(appVersion)) .servers(servers.stream().map(s -> new Server().url(s)).toList())) - .pathsToMatch(pathsToMatch) + .pathsToMatch("/api/v3/**") + .build(); + } + + @Bean + public GroupedOpenApi v1SublinksApi(@Value("${springdoc.version}") String appVersion, + @Value("#{${springdoc.servers}}") List servers) { + + return GroupedOpenApi.builder().group("v1") + .addOperationCustomizer((operation, handlerMethod) -> { + operation.addSecurityItem(new SecurityRequirement().addList("bearerAuth")); + return operation; + }) + .addOpenApiCustomizer(openApi -> openApi.info(new Info() + .title("OpenAPI Documentation") + .version(appVersion)) + .servers(servers.stream().map(s -> new Server().url(s)).toList())) + .pathsToMatch("/api/v1/**") .build(); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/AnnouncementController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/AnnouncementController.java new file mode 100644 index 00000000..861a682a --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/AnnouncementController.java @@ -0,0 +1,61 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.annoucement.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("api/v1/announcement") +@Tag(name = "Announcement", description = "Announcement API") +public class AnnouncementController extends AbstractSublinksApiController { + + @Operation(summary = "Get a list of announcements") + @GetMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void index() { + // TODO: implement + } + + @Operation(summary = "Get a specific announcement") + @GetMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void show(@PathVariable String id) { + // TODO: implement + } + + @Operation(summary = "Create a new announcement") + @PostMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void create() { + // TODO: implement + } + + @Operation(summary = "Update an announcement") + @PostMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void update(@PathVariable String id) { + // TODO: implement + } + + @Operation(summary = "Delete an announcement") + @DeleteMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void delete(@PathVariable String id) { + // TODO: implement + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/CommentController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/CommentController.java new file mode 100644 index 00000000..a4d8ef11 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/CommentController.java @@ -0,0 +1,60 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.comment.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("api/v1/comment") +@Tag(name = "Comment", description = "Comment API") +public class CommentController extends AbstractSublinksApiController { + @Operation(summary = "Get a list of comments") + @GetMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void index() { + // TODO: implement + } + + @Operation(summary = "Get a specific comment") + @GetMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void show(@PathVariable String id) { + // TODO: implement + } + + @Operation(summary = "Create a new comment") + @PostMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void create() { + // TODO: implement + } + + @Operation(summary = "Update an comment") + @PostMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void update(@PathVariable String id) { + // TODO: implement + } + + @Operation(summary = "Delete an comment") + @DeleteMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void delete(@PathVariable String id) { + // TODO: implement + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/controllers/AbstractSublinksApiController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/controllers/AbstractSublinksApiController.java new file mode 100644 index 00000000..949a3f1c --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/controllers/AbstractSublinksApiController.java @@ -0,0 +1,5 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.common.controllers; + +public abstract class AbstractSublinksApiController { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/CommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/CommunityController.java new file mode 100644 index 00000000..c9f60828 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/CommunityController.java @@ -0,0 +1,60 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("api/v1/community") +@Tag(name = "Community", description = "Community API") +public class CommunityController extends AbstractSublinksApiController { + @Operation(summary = "Get a list of communities") + @GetMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void index() { + // TODO: implement + } + + @Operation(summary = "Get a specific community") + @GetMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void show(@PathVariable String id) { + // TODO: implement + } + + @Operation(summary = "Create a new community") + @PostMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void create() { + // TODO: implement + } + + @Operation(summary = "Update an community") + @PostMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void update(@PathVariable String id) { + // TODO: implement + } + + @Operation(summary = "Delete an community") + @DeleteMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void delete(@PathVariable String id) { + // TODO: implement + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/InstanceController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/InstanceController.java new file mode 100644 index 00000000..d17cdc5d --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/InstanceController.java @@ -0,0 +1,60 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.instance.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("api/v1/instance") +@Tag(name = "Instance", description = "Instance API") +public class InstanceController extends AbstractSublinksApiController { + @Operation(summary = "Get a list of instances") + @GetMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void index() { + // TODO: implement + } + + @Operation(summary = "Get a specific instance") + @GetMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void show(@PathVariable String id) { + // TODO: implement + } + + @Operation(summary = "Create a new instance") + @PostMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void create() { + // TODO: implement + } + + @Operation(summary = "Update an instance") + @PostMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void update(@PathVariable String id) { + // TODO: implement + } + + @Operation(summary = "Delete an instance") + @DeleteMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void delete(@PathVariable String id) { + // TODO: implement + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java new file mode 100644 index 00000000..568e683c --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java @@ -0,0 +1,60 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("api/v1/person") +@Tag(name = "Person", description = "Person API") +public class PersonController extends AbstractSublinksApiController { + @Operation(summary = "Get a list of persons") + @GetMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void index() { + // TODO: implement + } + + @Operation(summary = "Get a specific person") + @GetMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void show(@PathVariable String id) { + // TODO: implement + } + + @Operation(summary = "Create a new person") + @PostMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void create() { + // TODO: implement + } + + @Operation(summary = "Update an person") + @PostMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void update(@PathVariable String id) { + // TODO: implement + } + + @Operation(summary = "Delete an person") + @DeleteMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void delete(@PathVariable String id) { + // TODO: implement + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/PostController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/PostController.java new file mode 100644 index 00000000..1d442ff1 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/PostController.java @@ -0,0 +1,60 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("api/v1/post") +@Tag(name = "Post", description = "Post API") +public class PostController extends AbstractSublinksApiController { + @Operation(summary = "Get a list of posts") + @GetMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void index() { + // TODO: implement + } + + @Operation(summary = "Get a specific post") + @GetMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void show(@PathVariable String id) { + // TODO: implement + } + + @Operation(summary = "Create a new post") + @PostMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void create() { + // TODO: implement + } + + @Operation(summary = "Update an post") + @PostMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void update(@PathVariable String id) { + // TODO: implement + } + + @Operation(summary = "Delete an post") + @DeleteMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void delete(@PathVariable String id) { + // TODO: implement + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/PrivatemessageController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/PrivatemessageController.java new file mode 100644 index 00000000..4616035f --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/PrivatemessageController.java @@ -0,0 +1,60 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +@RequestMapping("api/v1/privatemessage") +@Tag(name = "Privatemessage", description = "Privatemessage API") +public class PrivatemessageController extends AbstractSublinksApiController { + @Operation(summary = "Get a list of privatemessages") + @GetMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void index() { + // TODO: implement + } + + @Operation(summary = "Get a specific privatemessage") + @GetMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void show(@PathVariable String id) { + // TODO: implement + } + + @Operation(summary = "Create a new privatemessage") + @PostMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void create() { + // TODO: implement + } + + @Operation(summary = "Update an privatemessage") + @PostMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void update(@PathVariable String id) { + // TODO: implement + } + + @Operation(summary = "Delete an privatemessage") + @DeleteMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void delete(@PathVariable String id) { + // TODO: implement + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a213fb77..11604fcb 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,12 +9,11 @@ spring.datasource.url=${SUBLINKS_DB_URL} spring.datasource.username=${SUBLINKS_DB_USERNAME} spring.datasource.password=${SUBLINKS_DB_PASSWORD} # Spring Doc -springdoc.pathsToMatch=/api/v3/** -springdoc.version=v0.19.0 +springdoc.version=v0.1.0 # Production and test Lemmy URLs -springdoc.servers={'https://lemmy.ml','https://enterprise.lemmy.ml','https://ds9.lemmy.ml','https://voyager.lemmy.ml'} +springdoc.servers={'https://discuss.online'} springdoc.swagger-ui.path=/swagger-ui -springdoc.api-docs.path=/v3/api-docs +springdoc.api-docs.path=/api/api-docs springdoc.swagger-ui.enabled=true springdoc.api-docs.enabled=true springdoc.enable-spring-security=true From 9388d2aca6b78b07b80dce78427fa4feab4ceae2 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Wed, 24 Apr 2024 16:04:24 +0200 Subject: [PATCH 002/115] Minor fix addition --- .../api/lemmy/v3/site/controllers/SiteController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java index 038e7859..9ec8d28f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java @@ -144,7 +144,7 @@ public SiteResponse createSite(@Valid @RequestBody final CreateSite createSiteFo config.reportEmailAdmins(false); final InstanceConfig instanceConfig = config.build(); - + instanceConfigService.createInstanceConfig(instanceConfig); slurFilterService.updateOrCreateLemmySlur(createSiteForm.slur_filter_regex()); instance.setInstanceConfig(instanceConfig); From d34042134364ab3a06c5ad8da820720b09a0ce77 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Wed, 24 Apr 2024 19:38:22 +0200 Subject: [PATCH 003/115] Added Personmapper and RoleMapper --- .../models/CommentResponseDto.java | 13 +++++ .../sublinks/v1/common/enums/DateSort.java | 6 ++ .../person/controllers/PersonController.java | 19 ++++-- .../v1/person/mappers/PersonMapper.java | 58 +++++++++++++++++++ .../v1/person/models/CreatePerson.java | 15 +++++ .../v1/person/models/IndexPerson.java | 13 +++++ .../sublinks/v1/person/models/Langauges.java | 10 ++++ .../v1/person/models/PersonResponse.java | 27 +++++++++ .../services/SublinksPersonService.java | 5 ++ .../v1/roles/controllers/RolesController.java | 56 ++++++++++++++++++ .../sublinks/v1/roles/mappers/RoleMapper.java | 36 ++++++++++++ .../api/sublinks/v1/roles/models/Role.java | 15 +++++ 12 files changed, 267 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CommentResponseDto.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/DateSort.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/CreatePerson.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/Langauges.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/RolesController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/RoleMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/Role.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CommentResponseDto.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CommentResponseDto.java new file mode 100644 index 00000000..948a4eb5 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CommentResponseDto.java @@ -0,0 +1,13 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models; + +public record CommentResponseDto( + String id, + String postId, + String personId, + String communityId, + String content, + String createdAt, + String updatedAt +) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/DateSort.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/DateSort.java new file mode 100644 index 00000000..029b7ba1 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/DateSort.java @@ -0,0 +1,6 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.common.enums; + +public enum DateSort { + ASC, + DESC +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java index 568e683c..c4038263 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java @@ -1,25 +1,32 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.IndexPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @RequestMapping("api/v1/person") @Tag(name = "Person", description = "Person API") public class PersonController extends AbstractSublinksApiController { - @Operation(summary = "Get a list of persons") + + @Operation(summary = "Get a list of persons") @GetMapping - @ApiResponses(value = { - // TODO: add responses - }) - public void index() { - // TODO: implement + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { + @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PersonResponse.class))})}) + public void index(@RequestBody @Valid IndexPerson indexPerson) { } @Operation(summary = "Get a specific person") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java new file mode 100644 index 00000000..339d479d --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java @@ -0,0 +1,58 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.mappers; + +import com.sublinks.sublinksapi.api.lemmy.v3.utils.DateUtils; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.RoleMapper; +import com.sublinks.sublinksapi.authorization.services.RoleAuthorizingService; +import com.sublinks.sublinksapi.person.entities.Person; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.Named; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {RoleMapper.class}) +public abstract class PersonMapper implements Converter { + + @Override + @Mapping(target = "key", source = "person.name") + + @Mapping(target = "name", source = "person.name") + @Mapping(target = "displayName", source = "person", qualifiedByName = "display_name") + @Mapping(target = "avatar", source = "person", qualifiedByName = "avatar") + @Mapping(target = "banner", source = "person", qualifiedByName = "banner") + @Mapping(target = "isBanned", source = "person", qualifiedByName = "is_banned") + @Mapping(target = "banExpiresAt", source = "person.role.expiresAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "isDeleted", source = "person.deleted") + @Mapping(target = "isBotAccount", source = "person.botAccount") + @Mapping(target = "role", expression = "java(roleMapper.convert(person.getRole()))") + @Mapping(target = "languages", source = "person.languages") + @Mapping(target = "createdAt", source = "person.createdAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "updatedAt", source = "person.updatedAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + public abstract PersonResponse convert(@Nullable Person person); + + @Named("is_banned") + boolean mapIsBanned(Person person) { + + return RoleAuthorizingService.isBanned(person); + } + + @Named("display_name") + String mapDisplayName(Person person) { + + return !person.getDisplayName().isBlank() ? person.getDisplayName() : null; + } + + @Named("avatar") + String mapAvatar(Person person) { + + return !person.getAvatarImageUrl().isBlank() ? person.getAvatarImageUrl() : null; + } + + @Named("banner") + String mapBanner(Person person) { + + return !person.getBannerImageUrl().isBlank() ? person.getBannerImageUrl() : null; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/CreatePerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/CreatePerson.java new file mode 100644 index 00000000..747c9123 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/CreatePerson.java @@ -0,0 +1,15 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.models; + +import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.Role; +import java.util.List; +import lombok.Builder; + +@Builder +public record CreatePerson(String name, + String displayName, + Role role, + List languages, + String createdAt, + String updatedAt) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java new file mode 100644 index 00000000..b7e05b65 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java @@ -0,0 +1,13 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.models; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.DateSort; +import lombok.Builder; + +@Builder +public record IndexPerson(String search, + boolean local, + DateSort dateSort, + int limit, + int page) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/Langauges.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/Langauges.java new file mode 100644 index 00000000..cc4c0dac --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/Langauges.java @@ -0,0 +1,10 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.models; + +import lombok.Builder; + +@Builder +public record Langauges(String key, + String code, + String name) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java new file mode 100644 index 00000000..7e181a60 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java @@ -0,0 +1,27 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.models; + +import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.Role; +import java.util.List; +import java.util.Optional; +import lombok.Builder; + +@Builder +public record PersonResponse(String key, + String name, + String displayNname, + String avatarImageUrl, + String bannerImageUrl, + String bio, + String matrixUserId, + String actorId, + boolean isLocal, + boolean isBanned, + Optional banExpiresAt, + boolean isDeleted, + boolean isBotAccount, + Role role, + List languages, + String createdAt, + String updatedAt) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java new file mode 100644 index 00000000..549fe407 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -0,0 +1,5 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.services; + +public class SublinksPersonService { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/RolesController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/RolesController.java new file mode 100644 index 00000000..69ca7c35 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/RolesController.java @@ -0,0 +1,56 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.roles.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.*; + +@RequestMapping("api/v1/roles") +@Tag(name = "Roles", description = "Roles API") +public class RolesController extends AbstractSublinksApiController { + @Operation(summary = "Get a list of roles") + @GetMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void index() { + // TODO: implement + } + + @Operation(summary = "Get a specific role") + @GetMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void show(@PathVariable String id) { + // TODO: implement + } + + @Operation(summary = "Create a new post") + @PostMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void create() { + // TODO: implement + } + + @Operation(summary = "Update an post") + @PostMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void update(@PathVariable String id) { + // TODO: implement + } + + @Operation(summary = "Delete an post") + @DeleteMapping("/{id}") + @ApiResponses(value = { + // TODO: add responses + }) + public void delete(@PathVariable String id) { + // TODO: implement + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/RoleMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/RoleMapper.java new file mode 100644 index 00000000..a4313fd5 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/RoleMapper.java @@ -0,0 +1,36 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers; + +import com.sublinks.sublinksapi.api.lemmy.v3.utils.DateUtils; +import com.sublinks.sublinksapi.authorization.entities.Role; +import com.sublinks.sublinksapi.authorization.services.RoleAuthorizingService; +import java.util.Date; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.Named; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = { + RoleAuthorizingService.class}) +public abstract class RoleMapper implements + Converter { + + @Override + @Mapping(target = "key", source = "role.name") + @Mapping(target = "name", source = "role.name") + @Mapping(target = "description", source = "role.description") + @Mapping(target = "isActive", source = "role.isActive") + @Mapping(target = "isExpired", source = "role", qualifiedByName = "is_expired") + @Mapping(target = "expiresAt", source = "role.expiresAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "createdAt", source = "role.createdAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "updatedAt", source = "role.updaetedAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + public abstract com.sublinks.sublinksapi.api.sublinks.v1.roles.models.Role convert( + @Nullable Role role); + + @Named("is_expired") + boolean mapIsExpired(Role role) { + + return new Date().after(role.getExpiresAt()); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/Role.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/Role.java new file mode 100644 index 00000000..32671149 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/Role.java @@ -0,0 +1,15 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.roles.models; + +import lombok.Builder; + +@Builder +public record Role(String key, + String name, + String description, + boolean isActive, + boolean isExpired, + String expiresAt, + String createdAt, + String updatedAt) { + +} From c29a96bea8770bef8b66dcfd28d7f687aa751d28 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:03:45 +0200 Subject: [PATCH 004/115] Remove unnecessary blank line in PersonMapper This commit removes an unnecessary blank line in the PersonMapper class. The removal cleans up the code and improves its readability. This change adheres to the defined code style guide. --- .../sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java index 339d479d..de9fa2b4 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java @@ -17,7 +17,6 @@ public abstract class PersonMapper implements Converter @Override @Mapping(target = "key", source = "person.name") - @Mapping(target = "name", source = "person.name") @Mapping(target = "displayName", source = "person", qualifiedByName = "display_name") @Mapping(target = "avatar", source = "person", qualifiedByName = "avatar") From 523d863c201e10f31f672b9ee61e48b450d53a60 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Mon, 29 Apr 2024 17:02:03 +0200 Subject: [PATCH 005/115] Refactor PersonService, implement JWT auth and person registration The code refactor involved updating PersonService's relevant methods and adjusting their arguments and properties. Alongside this, JSON Web Token (JWT) authentication has been implemented to secure APIs. A new process for person registration has also been introduced, which now includes email verification and handling different registration modes. New components such as JwtFilter, JwtUtil and JwtPerson have been included for managing JWTs, and a new timeline endpoint for default controller is also introduced. --- .../sublinks/v1/authentication/JwtFilter.java | 90 ++++++++ .../sublinks/v1/authentication/JwtPerson.java | 30 +++ .../sublinks/v1/authentication/JwtUtil.java | 84 +++++++ .../authentication/config/SecurityConfig.java | 36 +++ .../AbstractSublinksApiController.java | 57 +++++ .../enums/{DateSort.java => SortOrder.java} | 2 +- .../controllers/LanguageController.java | 58 +++++ .../v1/languages/mappers/LanguageMapper.java | 22 ++ .../v1/languages/models/LanguageResponse.java | 10 + .../person/controllers/PersonController.java | 92 +++++--- .../v1/person/mappers/PersonMapper.java | 3 +- .../v1/person/models/CreatePerson.java | 18 +- .../v1/person/models/IndexPerson.java | 5 +- .../sublinks/v1/person/models/Langauges.java | 10 - .../v1/person/models/LoginPerson.java | 12 + .../v1/person/models/LoginResponse.java | 11 + .../v1/person/models/PersonResponse.java | 4 +- .../v1/person/models/RegistrationState.java | 9 + .../v1/person/models/UpdatePerson.java | 19 ++ .../services/SublinksPersonService.java | 216 ++++++++++++++++++ .../mappers/PrivateMessageMapper.java | 27 +++ .../models/PrivateMessageResponse.java | 18 ++ .../search/controllers/SearchController.java | 20 ++ .../v1/search/models/SearchResponse.java | 12 + .../api/sublinks/v1/utils/DateUtils.java | 13 ++ .../v1/utils/PaginationControllerUtils.java | 25 ++ .../language/entities/Language.java | 2 +- .../repositories/LanguageRepository.java | 3 + .../language/services/LanguageService.java | 7 + .../sublinksapi/person/entities/Person.java | 17 +- .../PersonRegistrationApplication.java | 4 +- .../PersonRegistrationApplicationStatus.java | 1 + .../person/repositories/PersonRepository.java | 7 + .../person/services/PersonService.java | 43 ++-- ...20231003__Create_initial_entity_tables.sql | 4 +- .../services/LanguageServiceUnitTests.java | 2 +- 36 files changed, 906 insertions(+), 87 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtFilter.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtPerson.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtUtil.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SecurityConfig.java rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/{DateSort.java => SortOrder.java} (76%) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/LanguageController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/mappers/LanguageMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/models/LanguageResponse.java delete mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/Langauges.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginPerson.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginResponse.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/RegistrationState.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/UpdatePerson.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/mappers/PrivateMessageMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/PrivateMessageResponse.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SearchController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/SearchResponse.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/DateUtils.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/PaginationControllerUtils.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtFilter.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtFilter.java new file mode 100644 index 00000000..390db21c --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtFilter.java @@ -0,0 +1,90 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.authentication; + +import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.person.repositories.PersonRepository; +import com.sublinks.sublinksapi.person.services.UserDataService; +import io.jsonwebtoken.ExpiredJwtException; +import io.jsonwebtoken.security.SignatureException; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.core.annotation.Order; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +@Component +@RequiredArgsConstructor +@Order(1) +public class JwtFilter extends OncePerRequestFilter { + + private final JwtUtil jwtUtil; + private final PersonRepository personRepository; + private final UserDataService userDataService; + + @Override + protected void doFilterInternal(final HttpServletRequest request, + final HttpServletResponse response, final FilterChain filterChain) + throws ServletException, IOException { + + String authorizingToken = request.getHeader("Authorization"); + + if (authorizingToken == null && request.getCookies() != null) { + for (Cookie cookie : request.getCookies()) { + if (cookie.getName().equals("jwt")) { + authorizingToken = cookie.getValue(); + break; + } + } + + } + + String token = null; + String userName = null; + + try { + if (authorizingToken != null) { + if (authorizingToken.startsWith("Bearer ")) { + token = authorizingToken.substring(7); + } else { + token = authorizingToken; + } + userName = jwtUtil.extractUsername(token); + } + } catch (ExpiredJwtException | SignatureException ex) { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "invalid_token"); + } + + if (userName != null && SecurityContextHolder.getContext().getAuthentication() == null) { + final Optional person = personRepository.findOneByName(userName); + if (person.isEmpty()) { + throw new UsernameNotFoundException("Invalid name"); + } + + if (jwtUtil.validateToken(token, person.get())) { + + // Add a check if token and ip was changed? To give like a "warning" to the user that he has a new ip logged into his account + userDataService.checkAndAddIpRelation(person.get(), request.getRemoteAddr(), token, + request.getHeader("User-Agent")); + final JwtPerson authenticationToken = new JwtPerson(person.get(), + person.get().getAuthorities()); + authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); + SecurityContextHolder.getContext().setAuthentication(authenticationToken); + } + } + filterChain.doFilter(request, response); + } + + @Override + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + + return !request.getServletPath().startsWith("/api/v3"); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtPerson.java new file mode 100644 index 00000000..4f49b78d --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtPerson.java @@ -0,0 +1,30 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.authentication; + +import com.sublinks.sublinksapi.person.entities.Person; +import java.util.Collection; +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +public class JwtPerson extends AbstractAuthenticationToken { + + private final Person person; + + public JwtPerson(final Person person, final Collection authorities) { + + super(authorities); + this.person = person; + setAuthenticated(true); + } + + @Override + public Object getCredentials() { + + return null; + } + + @Override + public Object getPrincipal() { + + return this.person; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtUtil.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtUtil.java new file mode 100644 index 00000000..2a68cad8 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtUtil.java @@ -0,0 +1,84 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.authentication; + +import com.sublinks.sublinksapi.person.entities.Person; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.io.Decoders; +import io.jsonwebtoken.security.Keys; +import java.io.Serial; +import java.io.Serializable; +import java.security.Key; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.function.Function; +import javax.crypto.SecretKey; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +@Component +public class JwtUtil implements Serializable { + + public static final long JWT_TOKEN_VALIDITY = 24 * 60 * 60; + @Serial + private static final long serialVersionUID = -2550185165626007488L; + private final String secret; + + public JwtUtil(@Value("${jwt.secret}") final String secret) { + + this.secret = secret; + } + + public String generateToken(final Person person) { + + final Map claims = new HashMap<>(); + return doGenerateToken(claims, person.getUsername()); + } + + public Boolean validateToken(final String token, final Person person) { + + final String tokenUsername = extractUsername(token); + return (tokenUsername.equals(person.getUsername()) && !isTokenExpired(token)); + } + + public String extractUsername(final String token) { + + return extractClaim(token, Claims::getSubject); + } + + public Date extractExpiration(final String token) { + + return extractClaim(token, Claims::getExpiration); + } + + public T extractClaim(final String token, final Function claimsResolver) { + + final Claims claims = extractAllClaims(token); + return claimsResolver.apply(claims); + } + + private Claims extractAllClaims(final String token) { + + final SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)); + return Jwts.parser().verifyWith(key).build().parseSignedClaims(token).getPayload(); + } + + private Boolean isTokenExpired(final String token) { + + return extractExpiration(token).before(new Date()); + } + + private String doGenerateToken(final Map claims, final String subject) { + + final byte[] keyBytes = Decoders.BASE64.decode(secret); + final Key key = Keys.hmacShaKeyFor(keyBytes); + return Jwts.builder() + .claims(claims) + .subject(subject) + .issuedAt(new Date(System.currentTimeMillis())) + .expiration(new Date(System.currentTimeMillis() + JWT_TOKEN_VALIDITY * 1000)) + .signWith(key) + .compact(); + } + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SecurityConfig.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SecurityConfig.java new file mode 100644 index 00000000..215a511e --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SecurityConfig.java @@ -0,0 +1,36 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.authentication.config; + +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.JwtFilter; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final JwtFilter jwtFilter; + + @Bean + public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception { + + http + .csrf(AbstractHttpConfigurer::disable) + .authorizeHttpRequests((requests) -> requests + .anyRequest().permitAll() + ) + .sessionManagement( + (sessionManagement) -> sessionManagement.sessionCreationPolicy( + SessionCreationPolicy.STATELESS) + ) + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); + return http.build(); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/controllers/AbstractSublinksApiController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/controllers/AbstractSublinksApiController.java index 949a3f1c..58423dbf 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/controllers/AbstractSublinksApiController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/controllers/AbstractSublinksApiController.java @@ -1,5 +1,62 @@ package com.sublinks.sublinksapi.api.sublinks.v1.common.controllers; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.JwtPerson; +import com.sublinks.sublinksapi.person.entities.Person; +import java.util.Optional; +import java.util.function.Supplier; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + public abstract class AbstractSublinksApiController { + /** + * Get the person object or throw a 400 Bad Request exception. + * + * @param principal JwtPerson object that contains the person as it's principal + * @return Person + * @throws ResponseStatusException Exception thrown when Person not present + */ + public Person getPersonOrThrowBadRequest(JwtPerson principal) throws ResponseStatusException { + + return Optional.ofNullable(principal).map(p -> (Person) p.getPrincipal()).orElseThrow( + () -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); + } + + /** + * Get the person object or throw a 400 Bad Request exception. + * + * @param principal JwtPerson object that contains the person as it's principal + * @return Person + * @throws ResponseStatusException Exception thrown when Person not present + */ + public Person getPersonOrThrow(JwtPerson principal, + Supplier exceptionSupplier) throws X { + + return Optional.ofNullable(principal).map(p -> (Person) p.getPrincipal()).orElseThrow( + exceptionSupplier); + } + + + /** + * Get the person object or throw a 401 Unauthorized exception. + * + * @param principal JwtPerson object that contains the person as it's principal + * @return Person + * @throws ResponseStatusException Exception thrown when Person not present + */ + public Person getPersonOrThrowUnauthorized(JwtPerson principal) throws ResponseStatusException { + + return Optional.ofNullable(principal).map(p -> (Person) p.getPrincipal()).orElseThrow( + () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED)); + } + + public Person getPerson(JwtPerson principal) { + + return getOptionalPerson(principal).orElse(null); + } + + public Optional getOptionalPerson(JwtPerson principal) { + + return Optional.ofNullable(principal).map(p -> (Person) p.getPrincipal()); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/DateSort.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SortOrder.java similarity index 76% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/DateSort.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SortOrder.java index 029b7ba1..b1c119a6 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/DateSort.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SortOrder.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.common.enums; -public enum DateSort { +public enum SortOrder { ASC, DESC } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/LanguageController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/LanguageController.java new file mode 100644 index 00000000..21c68bcc --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/LanguageController.java @@ -0,0 +1,58 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.languages.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.languages.models.LanguageResponse; +import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; +import com.sublinks.sublinksapi.language.entities.Language; +import com.sublinks.sublinksapi.language.services.LanguageService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.ConversionService; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.server.ResponseStatusException; + +@RequestMapping("api/v1/languages") +@Tag(name = "Languages", description = "Languages API") +@AllArgsConstructor +public class LanguageController extends AbstractSublinksApiController { + + private final LanguageService languageService; + private final LocalInstanceContext localInstanceContext; + private final ConversionService conversionService; + + @Operation(summary = "Get a list of languagesKeys") + @GetMapping + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "List of languagesKeys", useReturnTypeSchema = true)}) + public List index() { + + List languages = languageService.instanceLanguages(localInstanceContext.instance()); + + return languages.stream().map( + language -> conversionService.convert(language, LanguageResponse.class)).toList(); + + } + + @Operation(summary = "Get a specific language") + @GetMapping("/{id}") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Language", useReturnTypeSchema = true), + @ApiResponse(responseCode = "404", description = "Language not found")}) + public LanguageResponse show(@PathVariable String id) { + + List languages = languageService.instanceLanguages(localInstanceContext.instance()); + + Language foundLanguage = languages.stream().filter( + language -> language.getId().equals(Long.valueOf(id))).findFirst().orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "language_not_found")); + + return conversionService.convert(foundLanguage, LanguageResponse.class); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/mappers/LanguageMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/mappers/LanguageMapper.java new file mode 100644 index 00000000..c0cee1b1 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/mappers/LanguageMapper.java @@ -0,0 +1,22 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.languages.mappers; + +import com.sublinks.sublinksapi.api.sublinks.v1.languages.models.LangaugeResponse; +import com.sublinks.sublinksapi.authorization.services.RoleAuthorizingService; +import com.sublinks.sublinksapi.language.entities.Language; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = { + RoleAuthorizingService.class}) +public abstract class LanguageMapper implements Converter { + + @Override + @Mapping(target = "key", source = "language.name") + @Mapping(target = "name", source = "language.name") + @Mapping(target = "code", source = "language.code") + public abstract LangaugeResponse convert(@Nullable Language language); + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/models/LanguageResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/models/LanguageResponse.java new file mode 100644 index 00000000..26d0400b --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/models/LanguageResponse.java @@ -0,0 +1,10 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.languages.models; + +import lombok.Builder; + +@Builder +public record LanguageResponse(String key, + String code, + String name) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java index c4038263..e43fd262 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java @@ -1,67 +1,107 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.controllers; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.JwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.CreatePerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.IndexPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.UpdatePerson; +import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; +import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.person.repositories.PersonRepository; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; -import org.springframework.http.MediaType; +import java.util.List; +import java.util.Optional; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.ConversionService; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.server.ResponseStatusException; @RequestMapping("api/v1/person") @Tag(name = "Person", description = "Person API") +@AllArgsConstructor public class PersonController extends AbstractSublinksApiController { + private final PersonRepository personRepository; + private final SublinksPersonService sublinksPersonService; + private final ConversionService conversionService; + @Operation(summary = "Get a list of persons") @GetMapping - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PersonResponse.class))})}) - public void index(@RequestBody @Valid IndexPerson indexPerson) { + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List index(@RequestBody @Valid IndexPerson indexPerson) { + + return personRepository.findAllByNameAndBiography(indexPerson.search(), + PageRequest.of(indexPerson.page(), indexPerson.limit())).stream().map( + person -> conversionService.convert(person, PersonResponse.class)).toList(); } @Operation(summary = "Get a specific person") - @GetMapping("/{id}") + @GetMapping("/{key}") @ApiResponses(value = { - // TODO: add responses - }) - public void show(@PathVariable String id) { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PersonResponse show(@PathVariable String id) { + + Optional personResponse = personRepository.findById(Long.parseLong(id)).map( + person -> conversionService.convert(person, PersonResponse.class)); + + return personResponse.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); } - @Operation(summary = "Create a new person") + @Operation(summary = "Register a new person") @PostMapping @ApiResponses(value = { - // TODO: add responses - }) - public void create() { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public LoginResponse create(final HttpServletRequest request, + @RequestBody @Valid final CreatePerson createPerson) { + + return sublinksPersonService.registerPerson(createPerson, request.getRemoteAddr(), + request.getHeader("User-Agent")); + } + + @Operation(summary = "Log into a user") + @PostMapping("/{key}/login") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public LoginResponse login(final HttpServletRequest request, + @RequestBody @Valid final LoginPerson loginPerson) { + + return sublinksPersonService.login(loginPerson, request.getRemoteAddr(), + request.getHeader("User-Agent")); } @Operation(summary = "Update an person") - @PostMapping("/{id}") + @PostMapping("/{key}") @ApiResponses(value = { - // TODO: add responses - }) - public void update(@PathVariable String id) { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PersonResponse update(@PathVariable String key, + @RequestBody @Valid final UpdatePerson updatePersonForm, final JwtPerson principal) { + + final Person person = getPersonOrThrowUnauthorized(principal); + + return sublinksPersonService.updatePerson(person, updatePersonForm); } - @Operation(summary = "Delete an person") - @DeleteMapping("/{id}") + @Operation(summary = "Delete/Purge an person ( as an admin )") + @DeleteMapping("/{key}") @ApiResponses(value = { - // TODO: add responses - }) - public void delete(@PathVariable String id) { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public void delete(@PathVariable String key) { // TODO: implement } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java index de9fa2b4..f84830fa 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java @@ -1,8 +1,8 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.mappers; -import com.sublinks.sublinksapi.api.lemmy.v3.utils.DateUtils; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.RoleMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; import com.sublinks.sublinksapi.authorization.services.RoleAuthorizingService; import com.sublinks.sublinksapi.person.entities.Person; import org.mapstruct.Mapper; @@ -26,7 +26,6 @@ public abstract class PersonMapper implements Converter @Mapping(target = "isDeleted", source = "person.deleted") @Mapping(target = "isBotAccount", source = "person.botAccount") @Mapping(target = "role", expression = "java(roleMapper.convert(person.getRole()))") - @Mapping(target = "languages", source = "person.languages") @Mapping(target = "createdAt", source = "person.createdAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) @Mapping(target = "updatedAt", source = "person.updatedAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) public abstract PersonResponse convert(@Nullable Person person); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/CreatePerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/CreatePerson.java index 747c9123..62e0b031 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/CreatePerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/CreatePerson.java @@ -1,15 +1,23 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.models; -import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.Role; +import com.sublinks.sublinksapi.api.sublinks.v1.languages.models.LanguageResponse; import java.util.List; +import java.util.Optional; import lombok.Builder; @Builder public record CreatePerson(String name, String displayName, - Role role, - List languages, - String createdAt, - String updatedAt) { + Optional email, + List languages, + Optional avatarImageUrl, + Optional bannerImageUrl, + Optional bio, + Optional matrixUserId, + Optional password, + Optional passwordConfirmation, + Optional answer, + Optional captcha_token, + Optional captcha_answer) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java index b7e05b65..5746ca2a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java @@ -1,12 +1,13 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.models; -import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.DateSort; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortOrder; +import java.util.List; import lombok.Builder; @Builder public record IndexPerson(String search, boolean local, - DateSort dateSort, + SortOrder sortOrder, int limit, int page) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/Langauges.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/Langauges.java deleted file mode 100644 index cc4c0dac..00000000 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/Langauges.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.person.models; - -import lombok.Builder; - -@Builder -public record Langauges(String key, - String code, - String name) { - -} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginPerson.java new file mode 100644 index 00000000..67a1770e --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginPerson.java @@ -0,0 +1,12 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.models; + +import java.util.Optional; +import lombok.Builder; + +@Builder +public record LoginPerson(String username, + String password, + Optional captcha_token, + Optional captcha_answer) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginResponse.java new file mode 100644 index 00000000..01d731a5 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginResponse.java @@ -0,0 +1,11 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.models; + +import java.util.Optional; +import lombok.Builder; + +@Builder +public record LoginResponse(Optional token, + RegistrationState status, + Optional error) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java index 7e181a60..283e1433 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java @@ -1,14 +1,13 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.models; import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.Role; -import java.util.List; import java.util.Optional; import lombok.Builder; @Builder public record PersonResponse(String key, String name, - String displayNname, + String displayName, String avatarImageUrl, String bannerImageUrl, String bio, @@ -20,7 +19,6 @@ public record PersonResponse(String key, boolean isDeleted, boolean isBotAccount, Role role, - List languages, String createdAt, String updatedAt) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/RegistrationState.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/RegistrationState.java new file mode 100644 index 00000000..5ea72a57 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/RegistrationState.java @@ -0,0 +1,9 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.models; + +public enum RegistrationState { + UNCHANGED, + CREATED, + APPLICATION_CREATED, + VERIFICATION_EMAIL_SENT, + NOT_CREATED +} \ No newline at end of file diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/UpdatePerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/UpdatePerson.java new file mode 100644 index 00000000..61e68ac6 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/UpdatePerson.java @@ -0,0 +1,19 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.models; + +import java.util.List; +import java.util.Optional; +import lombok.Builder; + +@Builder +public record UpdatePerson(Optional displayName, + Optional email, + Optional> languagesKeys, + Optional avatarImageUrl, + Optional bannerImageUrl, + Optional bio, + Optional matrixUserId, + Optional oldPassword, + Optional password, + Optional passwordConfirmation) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index 549fe407..18b343c6 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -1,5 +1,221 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.services; +import com.sublinks.sublinksapi.api.lemmy.v3.enums.RegistrationMode; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.JwtUtil; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.CreatePerson; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.RegistrationState; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.UpdatePerson; +import com.sublinks.sublinksapi.email.entities.Email; +import com.sublinks.sublinksapi.email.enums.EmailTemplatesEnum; +import com.sublinks.sublinksapi.email.services.EmailService; +import com.sublinks.sublinksapi.instance.entities.InstanceConfig; +import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; +import com.sublinks.sublinksapi.language.repositories.LanguageRepository; +import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.person.entities.PersonEmailVerification; +import com.sublinks.sublinksapi.person.entities.PersonRegistrationApplication; +import com.sublinks.sublinksapi.person.enums.PersonRegistrationApplicationStatus; +import com.sublinks.sublinksapi.person.repositories.PersonRegistrationApplicationRepository; +import com.sublinks.sublinksapi.person.repositories.PersonRepository; +import com.sublinks.sublinksapi.person.services.PersonEmailVerificationService; +import com.sublinks.sublinksapi.person.services.PersonRegistrationApplicationService; +import com.sublinks.sublinksapi.person.services.PersonService; +import com.sublinks.sublinksapi.person.services.UserDataService; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.ConversionService; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; +import org.thymeleaf.context.Context; + +@AllArgsConstructor public class SublinksPersonService { + private final PersonService personService; + private final JwtUtil jwtUtil; + private final PersonRepository personRepository; + private final LocalInstanceContext localInstanceContext; + private final EmailService emailService; + private final PersonEmailVerificationService personEmailVerificationService; + private final PersonRegistrationApplicationService personRegistrationApplicationService; + private final PersonRegistrationApplicationRepository personRegistrationApplicationRepository; + private final UserDataService userDataService; + private final ConversionService conversionService; + private final LanguageRepository languageRepository; + + public LoginResponse registerPerson(final CreatePerson createPersonForm, final String ip, + final String userAgent) { + + final InstanceConfig instanceConfig = localInstanceContext.instance().getInstanceConfig(); + + if (instanceConfig != null && instanceConfig.isRequireEmailVerification()) { + if (createPersonForm.email().isEmpty()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "email_required"); + } + } + + final Person.PersonBuilder personBuilder = Person.builder() + .name(createPersonForm.name()) + .displayName(createPersonForm.displayName()) + .avatarImageUrl(createPersonForm.avatarImageUrl().orElse(null)) + .bannerImageUrl(createPersonForm.bannerImageUrl().orElse(null)) + .biography(createPersonForm.bio().orElse(null)) + .matrixUserId(createPersonForm.matrixUserId().orElse(null)); + + final Person person = personBuilder.build(); + + personService.createPerson(person); + + String token = jwtUtil.generateToken(person); + RegistrationState status = RegistrationState.CREATED; + + if (instanceConfig != null && instanceConfig.isRequireEmailVerification()) { + token = null; + + PersonEmailVerification personEmailVerification = personEmailVerificationService.create( + person, ip, userAgent); + + Map params = emailService.getDefaultEmailParameters(); + + params.put("person", person); + params.put("verificationUrl", localInstanceContext.instance().getDomain() + "/verify_email/" + + personEmailVerification.getToken()); + try { + final String template_name = EmailTemplatesEnum.VERIFY_EMAIL.toString(); + emailService.saveToQueue(Email.builder() + .personRecipients(List.of(person)) + .subject(emailService.getSubjects().get(template_name).getAsString()) + .htmlContent(emailService.formatTextEmailTemplate(template_name, + new Context(Locale.getDefault(), params))) + .textContent(emailService.formatEmailTemplate(template_name, + new Context(Locale.getDefault(), params))) + .build()); + status = RegistrationState.VERIFICATION_EMAIL_SENT; + } catch (Exception e) { + personRepository.delete(person); + return LoginResponse.builder() + .token(Optional.empty()) + .status(RegistrationState.NOT_CREATED) + .error(Optional.of("email_send_failed")) + .build(); + } + } + if (instanceConfig != null + && instanceConfig.getRegistrationMode() == RegistrationMode.RequireApplication) { + token = null; + + personRegistrationApplicationService.createPersonRegistrationApplication( + PersonRegistrationApplication.builder() + .applicationStatus(instanceConfig.isRequireEmailVerification() + ? PersonRegistrationApplicationStatus.inactive + : PersonRegistrationApplicationStatus.pending) + .person(person) + .question(instanceConfig.getRegistrationQuestion()) + .answer(createPersonForm.answer().orElse(null)) + .build()); + if (!instanceConfig.isRequireEmailVerification()) { + status = RegistrationState.APPLICATION_CREATED; + } + } + + if (token != null) { + userDataService.checkAndAddIpRelation(person, ip, token, userAgent); + } + + return LoginResponse.builder().token(Optional.ofNullable(token)).status(status).build(); + } + + public LoginResponse login(final LoginPerson loginPersonForm, final String ip, + final String userAgent) { + + final Optional foundPerson = personRepository.findOneByName(loginPersonForm.username()); + + if (foundPerson.isEmpty()) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found"); + } + + final Person person = foundPerson.get(); + + if (person.isDeleted()) { + return LoginResponse.builder() + .token(Optional.empty()) + .status(RegistrationState.UNCHANGED) + .error(Optional.of("person_deleted")) + .build(); + } + + if (!person.isEmailVerified()) { + return LoginResponse.builder() + .token(Optional.empty()) + .status(RegistrationState.UNCHANGED) + .error(Optional.of("email_not_verified")) + .build(); + } + + Optional application = personRegistrationApplicationRepository.findOneByPerson( + person); + + if (application.isPresent() && application.get().getApplicationStatus() + != PersonRegistrationApplicationStatus.approved) { + return LoginResponse.builder() + .token(Optional.empty()) + .status(RegistrationState.UNCHANGED) + .error(Optional.of("application_not_approved")) + .build(); + } + + if (!personService.isPasswordEqual(person, loginPersonForm.password())) { + return LoginResponse.builder() + .token(Optional.empty()) + .status(RegistrationState.UNCHANGED) + .error(Optional.of("password_incorrect")) + .build(); + } + + final String token = jwtUtil.generateToken(person); + + userDataService.checkAndAddIpRelation(person, ip, token, userAgent); + + return LoginResponse.builder() + .token(Optional.of(token)) + .status(RegistrationState.UNCHANGED) + .build(); + } + + public PersonResponse updatePerson(Person person, UpdatePerson updatePersonForm) { + + if (updatePersonForm.languagesKeys().isPresent()) { + person.setLanguages( + languageRepository.findAllByCodeIsIn(updatePersonForm.languagesKeys().get())); + } + + updatePersonForm.displayName().ifPresent(person::setDisplayName); + updatePersonForm.email().ifPresent(person::setEmail); + updatePersonForm.avatarImageUrl().ifPresent(person::setAvatarImageUrl); + updatePersonForm.bannerImageUrl().ifPresent(person::setBannerImageUrl); + updatePersonForm.bio().ifPresent(person::setBiography); + updatePersonForm.matrixUserId().ifPresent(person::setMatrixUserId); + + if (updatePersonForm.oldPassword().isPresent() && updatePersonForm.password().isPresent() + && updatePersonForm.passwordConfirmation().isPresent()) { + if (!personService.isPasswordEqual(person, updatePersonForm.oldPassword().get())) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "password_incorrect"); + } + if (!updatePersonForm.password().get().equals( + updatePersonForm.passwordConfirmation().get())) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "password_mismatch"); + } + personService.updatePassword(person, updatePersonForm.password().get()); + } + + personRepository.save(person); + + return conversionService.convert(person, PersonResponse.class); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/mappers/PrivateMessageMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/mappers/PrivateMessageMapper.java new file mode 100644 index 00000000..fd4826af --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/mappers/PrivateMessageMapper.java @@ -0,0 +1,27 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.mappers;https://github.com/sublinks/sublinks-docker + +import com.sublinks.sublinksapi.api.sublinks.v1.person.mappers.PersonMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.PrivateMessageResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; +import com.sublinks.sublinksapi.privatemessages.entities.PrivateMessage; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {PersonMapper.class}) +public abstract class PrivateMessageMapper implements + Converter { + + @Override + @Mapping(target = "key", source = "privateMessage.id") + @Mapping(target = "content", source = "privateMessage.content") + @Mapping(target = "isLocal", source = "privateMessage.local") + @Mapping(target = "isDeleted", source = "privateMessage.deleted") + @Mapping(target = "sender", source = "privateMessage.sender", expression = "java(personMapper.convert(privateMessage.getSender()))") + @Mapping(target = "recipient", source = "privateMessage.recipient", expression = "java(personMapper.convert(privateMessage.getRecipient()))") + @Mapping(target = "createdAt", source = "privateMessage.createdAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "updatedAt", source = "privateMessage.updatedAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + public abstract PrivateMessageResponse convert(@Nullable PrivateMessage privateMessage); +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/PrivateMessageResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/PrivateMessageResponse.java new file mode 100644 index 00000000..e9a5dd39 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/PrivateMessageResponse.java @@ -0,0 +1,18 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models; + +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; +import lombok.Builder; + +@Builder +public record PrivateMessageResponse(String key, + PersonResponse sender, + PersonResponse recipient, + String content, + boolean isLocal, + boolean isDeleted, + boolean isRead, + String activityPubId, + String createdAt, + String updatedAt) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SearchController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SearchController.java new file mode 100644 index 00000000..dd935bb5 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SearchController.java @@ -0,0 +1,20 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.search.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.*; + +@RequestMapping("api/v1/search") +@Tag(name = "Search", description = "Search API") +public class SearchController extends AbstractSublinksApiController { + @Operation(summary = "Get a list of privatemessages") + @GetMapping + @ApiResponses(value = { + // TODO: add responses + }) + public void index() { + // TODO: implement + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/SearchResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/SearchResponse.java new file mode 100644 index 00000000..536911d0 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/SearchResponse.java @@ -0,0 +1,12 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.search.models; + +import com.sublinks.sublinksapi.api.lemmy.v3.user.models.Person; +import java.util.List; +import lombok.Builder; + +// @todo: Add Communities, Posts, Comments, and Messages +@Builder +public record SearchResponse(String key, + List persons) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/DateUtils.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/DateUtils.java new file mode 100644 index 00000000..cd094f7f --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/DateUtils.java @@ -0,0 +1,13 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.utils; + +import java.time.format.DateTimeFormatter; + +/** + * Date Utils + */ +public class DateUtils { + + public static final String FRONT_END_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSSSSSX"; + public static DateTimeFormatter FRONT_END_DATETIME_FORMATTER = DateTimeFormatter.ofPattern( + DateUtils.FRONT_END_DATE_FORMAT); +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/PaginationControllerUtils.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/PaginationControllerUtils.java new file mode 100644 index 00000000..09b8eede --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/PaginationControllerUtils.java @@ -0,0 +1,25 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.utils; + +import org.springframework.lang.Nullable; + +public class PaginationControllerUtils { + + public static int getAbsoluteMinNumber(@Nullable Integer number, + @Nullable Integer defaultNumber) { + + if (number == null && defaultNumber == null) { + return 1; + } + + if (defaultNumber == null) { + defaultNumber = number; + } + + if (number == null) { + return Math.abs(defaultNumber); + } + + return Math.abs(Math.min(number, defaultNumber)); + } + +} diff --git a/src/main/java/com/sublinks/sublinksapi/language/entities/Language.java b/src/main/java/com/sublinks/sublinksapi/language/entities/Language.java index 746becb7..ef914703 100644 --- a/src/main/java/com/sublinks/sublinksapi/language/entities/Language.java +++ b/src/main/java/com/sublinks/sublinksapi/language/entities/Language.java @@ -27,7 +27,7 @@ @NoArgsConstructor @Builder @Entity -@Table(name = "languages") +@Table(name = "languagesKeys") public class Language { /** diff --git a/src/main/java/com/sublinks/sublinksapi/language/repositories/LanguageRepository.java b/src/main/java/com/sublinks/sublinksapi/language/repositories/LanguageRepository.java index 02ae5a12..831372d7 100644 --- a/src/main/java/com/sublinks/sublinksapi/language/repositories/LanguageRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/language/repositories/LanguageRepository.java @@ -1,9 +1,12 @@ package com.sublinks.sublinksapi.language.repositories; import com.sublinks.sublinksapi.language.entities.Language; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; public interface LanguageRepository extends JpaRepository { Language findLanguageByCode(String code); + + List findAllByCodeIsIn(List codes); } diff --git a/src/main/java/com/sublinks/sublinksapi/language/services/LanguageService.java b/src/main/java/com/sublinks/sublinksapi/language/services/LanguageService.java index b4d9acda..1bce7c65 100644 --- a/src/main/java/com/sublinks/sublinksapi/language/services/LanguageService.java +++ b/src/main/java/com/sublinks/sublinksapi/language/services/LanguageService.java @@ -34,4 +34,11 @@ public List languageIdsToEntity(final Collection languageIds) } return languages; } + + public List instanceLanguages(final Instance instance) { + + final List languages = new ArrayList<>(); + languages.addAll(instance.getLanguages()); + return languages; + } } diff --git a/src/main/java/com/sublinks/sublinksapi/person/entities/Person.java b/src/main/java/com/sublinks/sublinksapi/person/entities/Person.java index b5887fda..234bb1db 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/entities/Person.java +++ b/src/main/java/com/sublinks/sublinksapi/person/entities/Person.java @@ -162,34 +162,31 @@ public class Person implements UserDetails, Principal { @Column(nullable = false) private String password; - @Column(nullable = false) + @Column(nullable = true, name = "avatar_image_url") private String avatarImageUrl; - @Column(nullable = false, name = "banner_image_url") + @Column(nullable = true, name = "banner_image_url") private String bannerImageUrl; - @Column(nullable = false) + @Column(nullable = true) private String biography; - @Column(nullable = false, name = "interface_language") + @Column(nullable = true, name = "interface_language") private String interfaceLanguage; - @Column(nullable = false, name = "default_theme") + @Column(nullable = true, name = "default_theme") private String defaultTheme; - @Column(nullable = false, name = "default_listing_type") + @Column(nullable = true, name = "default_listing_type") @Enumerated(EnumType.STRING) - private ListingType defaultListingType; - @Column(nullable = false, name = "default_sort_type") + @Column(nullable = true, name = "default_sort_type") @Enumerated(EnumType.STRING) - private SortType defaultSortType; @Column(nullable = false, name = "post_listing_type") @Enumerated(EnumType.STRING) - private PostListingMode postListingType; @Column(nullable = false, name = "is_infinite_scroll") diff --git a/src/main/java/com/sublinks/sublinksapi/person/entities/PersonRegistrationApplication.java b/src/main/java/com/sublinks/sublinksapi/person/entities/PersonRegistrationApplication.java index 7509a627..0bdd295b 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/entities/PersonRegistrationApplication.java +++ b/src/main/java/com/sublinks/sublinksapi/person/entities/PersonRegistrationApplication.java @@ -58,7 +58,6 @@ public class PersonRegistrationApplication { @Column(nullable = false, name = "application_status") @Enumerated(EnumType.STRING) - private PersonRegistrationApplicationStatus applicationStatus; @CreationTimestamp @@ -95,6 +94,7 @@ public final boolean equals(Object o) { public final int hashCode() { return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer() - .getPersistentClass().hashCode() : getClass().hashCode(); + .getPersistentClass() + .hashCode() : getClass().hashCode(); } } diff --git a/src/main/java/com/sublinks/sublinksapi/person/enums/PersonRegistrationApplicationStatus.java b/src/main/java/com/sublinks/sublinksapi/person/enums/PersonRegistrationApplicationStatus.java index 6dec96dc..177e9eea 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/enums/PersonRegistrationApplicationStatus.java +++ b/src/main/java/com/sublinks/sublinksapi/person/enums/PersonRegistrationApplicationStatus.java @@ -1,6 +1,7 @@ package com.sublinks.sublinksapi.person.enums; public enum PersonRegistrationApplicationStatus { + inactive, pending, approved, rejected diff --git a/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java b/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java index 363d5b4b..1ee2b2d7 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java @@ -1,12 +1,19 @@ package com.sublinks.sublinksapi.person.repositories; import com.sublinks.sublinksapi.person.entities.Person; +import java.util.List; import java.util.Optional; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface PersonRepository extends JpaRepository { Optional findOneByName(String name); Optional findOneByEmail(String email); + + @Query(value = "SELECT p FROM people p WHERE p.search_vector @@ to_tsquery('keyword', :keyword)", countQuery = "SELECT COUNT(p.id) FROM people p WHERE p.search_vector @@ to_tsquery('english', :keyword)", nativeQuery = true) + List findAllByNameAndBiography(@Param("keyword") String keyword, Pageable pageable); } diff --git a/src/main/java/com/sublinks/sublinksapi/person/services/PersonService.java b/src/main/java/com/sublinks/sublinksapi/person/services/PersonService.java index 141ed396..66c5be34 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/services/PersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/person/services/PersonService.java @@ -65,8 +65,8 @@ public class PersonService { public Set generateInitialRoles() { - Role adminRole = roleRepository.save( - Role.builder().description("Admin role for admins").name("Admin").isActive(true).build()); + Role adminRole = roleRepository.save(Role.builder().description("Admin role for admins").name( + "Admin").isActive(true).build()); adminRole.setRolePermissions(Collections.singleton(rolePermissionsRepository.save( com.sublinks.sublinksapi.authorization.entities.RolePermissions.builder() @@ -91,22 +91,19 @@ public Set generateInitialRoles() { .build()); bannedRole.setRolePermissions(rolePermissions.stream() - .map(rolePermission -> rolePermissionsRepository.save( - RolePermissions.builder().role(bannedRole).permission(rolePermission).build())) + .map(rolePermission -> rolePermissionsRepository.save(RolePermissions.builder().role( + bannedRole).permission(rolePermission).build())) .collect(Collectors.toSet())); rolePermissions.remove(RolePermission.BANNED); rolePermissions.add(RolePermission.DEFAULT); - Role defaultUserRole = roleRepository.save(Role.builder() - .description("Default role for all users") - .name("User") - .isActive(true) - .build()); + Role defaultUserRole = roleRepository.save(Role.builder().description( + "Default role for all users").name("User").isActive(true).build()); defaultUserRole.setRolePermissions(rolePermissions.stream() - .map(rolePermission -> rolePermissionsRepository.save( - RolePermissions.builder().role(defaultUserRole).permission(rolePermission).build())) + .map(rolePermission -> rolePermissionsRepository.save(RolePermissions.builder().role( + defaultUserRole).permission(rolePermission).build())) .collect(Collectors.toSet())); rolePermissions.remove(RolePermission.DEFAULT); @@ -165,15 +162,12 @@ public Set generateInitialRoles() { rolePermissions.add(RolePermission.REPORT_COMMUNITY_RESOLVE); rolePermissions.add(RolePermission.REPORT_COMMUNITY_READ); - Role registeredUserRole = roleRepository.save(Role.builder() - .description("Default Role for all registered users") - .name("Registered") - .isActive(true) - .build()); + Role registeredUserRole = roleRepository.save(Role.builder().description( + "Default Role for all registered users").name("Registered").isActive(true).build()); registeredUserRole.setRolePermissions(rolePermissions.stream() - .map(rolePermission -> rolePermissionsRepository.save( - RolePermissions.builder().role(registeredUserRole).permission(rolePermission).build())) + .map(rolePermission -> rolePermissionsRepository.save(RolePermissions.builder().role( + registeredUserRole).permission(rolePermission).build())) .collect(Collectors.toSet())); return new HashSet<>(roleRepository.findAll()); @@ -230,10 +224,8 @@ public void createPerson(final Person person) { final String userActorId = baseUrlUtil.getBaseUrl() + "/u/" + person.getName(); person.setActorId(userActorId); - person.setLinkPersonInstance(LinkPersonInstance.builder() - .instance(localInstanceContext.instance()) - .person(person) - .build()); + person.setLinkPersonInstance(LinkPersonInstance.builder().instance( + localInstanceContext.instance()).person(person).build()); final List languages = new ArrayList<>( localInstanceContext.instance().getLanguages()); @@ -301,4 +293,11 @@ public void deleteUserAccount(final Person person, final boolean deleteContent) personDeletedPublisher.publish(personRepository.save(person)); } + + public void updatePassword(Person person, String newPassword) { + + person.setPassword(encodePassword(newPassword)); + personRepository.save(person); + + } } 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 8dac3b72..fd245b8c 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 @@ -219,7 +219,7 @@ CREATE TABLE people biography TEXT NULL, interface_language VARCHAR(20) NULL, default_theme VARCHAR(255) NULL, - default_listing_type VARCHAR(255) NULL, + default_listing_type VARCHAR(255) NULL DEFAULT 'List', default_sort_type VARCHAR(255) NULL DEFAULT 'Active', is_show_scores BOOL NOT NULL DEFAULT false, is_show_read_posts BOOL NOT NULL DEFAULT false, @@ -234,7 +234,7 @@ CREATE TABLE people is_collapse_bot_comments BOOL NOT NULL DEFAULT false, is_auto_expanding BOOL NOT NULL DEFAULT false, is_blur_nsfw BOOL NOT NULL DEFAULT false, - post_listing_type VARCHAR(255) DEFAULT 'List', + post_listing_type VARCHAR(255) NULL DEFAULT 'List', matrix_user_id TEXT NULL, public_key TEXT NOT NULL, private_key TEXT NULL, diff --git a/src/test/java/com/sublinks/sublinksapi/language/services/LanguageServiceUnitTests.java b/src/test/java/com/sublinks/sublinksapi/language/services/LanguageServiceUnitTests.java index c234d9fb..d5e45c24 100644 --- a/src/test/java/com/sublinks/sublinksapi/language/services/LanguageServiceUnitTests.java +++ b/src/test/java/com/sublinks/sublinksapi/language/services/LanguageServiceUnitTests.java @@ -73,6 +73,6 @@ void givenCollectionOfLanguageIds_whenLanguageIdsToEntity_thenReturnListOfLangua List languages = languageService.languageIdsToEntity(languageIds); assertEquals(2, languages.size(), - "Number of languages instances returned did not match expected"); + "Number of languagesKeys instances returned did not match expected"); } } From adab1510ca70d9af664bd46c47e905a5e7dcf8cb Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Thu, 2 May 2024 08:25:39 +0200 Subject: [PATCH 006/115] Refactor codebase to switch primitive boolean to Boolean and expand API functionality Made changes throughout the project by replacing primitive boolean with its Wrapper class, Boolean. This allows for null values, which can represent 'undefined' status. In addition, introduced new community-related endpoints and services, for enhanced community details management. Also, new SortType and ListingType enumerations were added, to enrich the sorting and listing options. Implemented relevant conversions and mapping for new types. --- .../controllers/CommunityOwnerController.java | 43 ++++---- .../sublinks/v1/authentication/JwtFilter.java | 2 +- .../sublinks/v1/common/enums/ListingType.java | 9 ++ .../sublinks/v1/common/enums/SortOrder.java | 6 -- .../sublinks/v1/common/enums/SortType.java | 23 ++++ .../mappers/LemmyListingTypeMapper.java | 20 ++++ .../common/mappers/LemmySortTypeMapper.java | 20 ++++ .../controllers/CommunityController.java | 100 +++++++++++++----- .../v1/community/mappers/CommunityMapper.java | 34 ++++++ .../community/models/CommunityResponse.java | 28 +++++ .../v1/community/models/CreateCommunity.java | 13 +++ .../v1/community/models/IndexCommunity.java | 16 +++ .../v1/community/models/UpdateCommunity.java | 14 +++ .../services/SublinksCommunityService.java | 62 +++++++++++ .../person/controllers/PersonController.java | 6 +- .../v1/person/mappers/PersonMapper.java | 2 +- .../v1/person/models/IndexPerson.java | 8 +- .../v1/person/models/PersonResponse.java | 8 +- .../models/PrivateMessageResponse.java | 6 +- .../sublinks/v1/roles/mappers/RoleMapper.java | 2 +- .../api/sublinks/v1/roles/models/Role.java | 4 +- 21 files changed, 357 insertions(+), 69 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/ListingType.java delete mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SortOrder.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SortType.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmyListingTypeMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmySortTypeMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/CommunityMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityResponse.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CreateCommunity.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityOwnerController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityOwnerController.java index 62c17f1b..e8d25965 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityOwnerController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityOwnerController.java @@ -71,28 +71,27 @@ public CommunityResponse create(@Valid @RequestBody final CreateCommunity create Person person = getPersonOrThrowUnauthorized(principal); - roleAuthorizingService.hasAdminOrPermissionOrThrow(person, - RolePermission.CREATE_COMMUNITY, + roleAuthorizingService.hasAdminOrPermissionOrThrow(person, RolePermission.CREATE_COMMUNITY, () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); final List languages = new ArrayList<>(); if (createCommunityForm.discussion_languages() != null) { for (String languageCode : createCommunityForm.discussion_languages()) { - final Optional language = localInstanceContext.languageRepository() - .findById(Long.valueOf(languageCode)); + final Optional language = localInstanceContext.languageRepository().findById( + Long.valueOf(languageCode)); language.ifPresent(languages::add); } } - Community.CommunityBuilder communityBuilder = Community.builder() - .instance(localInstanceContext.instance()).title(createCommunityForm.title()) - .titleSlug(slugUtil.stringToSlug(createCommunityForm.name())) - .description(createCommunityForm.description()).isPostingRestrictedToMods( - createCommunityForm.posting_restricted_to_mods() != null - && createCommunityForm.posting_restricted_to_mods()) - .isNsfw(createCommunityForm.nsfw() != null && createCommunityForm.nsfw()) - .iconImageUrl(createCommunityForm.icon()).bannerImageUrl(createCommunityForm.banner()) - .languages(languages); + Community.CommunityBuilder communityBuilder = Community.builder().instance( + localInstanceContext.instance()).title(createCommunityForm.title()).titleSlug( + slugUtil.stringToSlug(createCommunityForm.name())).description( + createCommunityForm.description()).isPostingRestrictedToMods( + createCommunityForm.posting_restricted_to_mods() != null + && createCommunityForm.posting_restricted_to_mods()).isNsfw( + createCommunityForm.nsfw() != null && createCommunityForm.nsfw()).iconImageUrl( + createCommunityForm.icon()).bannerImageUrl(createCommunityForm.banner()).languages( + languages); try { communityBuilder.title(slurFilterService.censorText(createCommunityForm.title())); @@ -135,10 +134,16 @@ public CommunityResponse create(@Valid @RequestBody final CreateCommunity create Community community = communityBuilder.build(); final Set linkPersonCommunities = new LinkedHashSet<>(); - linkPersonCommunities.add(LinkPersonCommunity.builder().community(community).person(person) - .linkType(LinkPersonCommunityType.owner).build()); - linkPersonCommunities.add(LinkPersonCommunity.builder().community(community).person(person) - .linkType(LinkPersonCommunityType.follower).build()); + linkPersonCommunities.add(LinkPersonCommunity.builder() + .community(community) + .person(person) + .linkType(LinkPersonCommunityType.owner) + .build()); + linkPersonCommunities.add(LinkPersonCommunity.builder() + .community(community) + .person(person) + .linkType(LinkPersonCommunityType.follower) + .build()); communityService.createCommunity(community); @@ -189,8 +194,8 @@ CommunityResponse update(@Valid final @RequestBody EditCommunity editCommunityFo final List languages = new ArrayList<>(); for (String languageCode : editCommunityForm.discussion_languages()) { - final Optional language = localInstanceContext.languageRepository() - .findById(Long.valueOf(languageCode)); + final Optional language = localInstanceContext.languageRepository().findById( + Long.valueOf(languageCode)); language.ifPresent(languages::add); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtFilter.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtFilter.java index 390db21c..d4017fbc 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtFilter.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtFilter.java @@ -83,7 +83,7 @@ protected void doFilterInternal(final HttpServletRequest request, } @Override - protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + protected Boolean shouldNotFilter(HttpServletRequest request) throws ServletException { return !request.getServletPath().startsWith("/api/v3"); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/ListingType.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/ListingType.java new file mode 100644 index 00000000..f9fdb0cf --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/ListingType.java @@ -0,0 +1,9 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.common.enums; + +public enum ListingType { + All, + Local, + Subscribed, + ModeratorView, +} + diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SortOrder.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SortOrder.java deleted file mode 100644 index b1c119a6..00000000 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SortOrder.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.common.enums; - -public enum SortOrder { - ASC, - DESC -} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SortType.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SortType.java new file mode 100644 index 00000000..30044596 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SortType.java @@ -0,0 +1,23 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.common.enums; + +public enum SortType { + Active, + Hot, + New, + Old, + TopDay, + TopWeek, + TopMonth, + TopYear, + TopAll, + MostComments, + NewComments, + TopHour, + TopSixHour, + TopTwelveHour, + TopThreeMonths, + TopSixMonths, + TopNineMonths, + Controversial, + Scaled, +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmyListingTypeMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmyListingTypeMapper.java new file mode 100644 index 00000000..1041639b --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmyListingTypeMapper.java @@ -0,0 +1,20 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.common.mappers; + +import com.sublinks.sublinksapi.api.lemmy.v3.enums.ListingType; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +public class LemmyListingTypeMapper implements + Converter { + + @Nullable + @Override + public ListingType convert( + com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType listingType) { + + return ListingType.valueOf(listingType.name()); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmySortTypeMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmySortTypeMapper.java new file mode 100644 index 00000000..9d66e3da --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmySortTypeMapper.java @@ -0,0 +1,20 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.common.mappers; + +import com.sublinks.sublinksapi.api.lemmy.v3.enums.SortType; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +public class LemmySortTypeMapper implements + Converter { + + @Nullable + @Override + public SortType convert( + com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType listingType) { + + return SortType.valueOf(listingType.name()); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/CommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/CommunityController.java index c9f60828..4f8ca1a4 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/CommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/CommunityController.java @@ -1,60 +1,110 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.controllers; +import com.sublinks.sublinksapi.api.lemmy.v3.enums.ListingType; +import com.sublinks.sublinksapi.api.lemmy.v3.enums.SortType; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.JwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.IndexCommunity; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; +import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; +import com.sublinks.sublinksapi.community.models.CommunitySearchCriteria; +import com.sublinks.sublinksapi.community.repositories.CommunityRepository; +import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import java.util.List; +import java.util.Optional; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.ConversionService; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.server.ResponseStatusException; +@AllArgsConstructor @RequestMapping("api/v1/community") @Tag(name = "Community", description = "Community API") public class CommunityController extends AbstractSublinksApiController { - @Operation(summary = "Get a list of communities") + + private final CommunityRepository communityRepository; + private final SublinksCommunityService sublinksCommunityService; + private final ConversionService conversionService; + + @Operation(summary = "Get a list of communities") @GetMapping @ApiResponses(value = { - // TODO: add responses - }) - public void index() { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List index(@Valid final IndexCommunity indexCommunityForm, + final JwtPerson jwtPerson) { + + final Optional person = getOptionalPerson(jwtPerson); + + return communityRepository.allCommunitiesBySearchCriteria( + CommunitySearchCriteria.builder() + .page(indexCommunityForm.page()) + .perPage(indexCommunityForm.limit()) + .sortType(conversionService.convert(indexCommunityForm.sortType(), SortType.class)) + .listingType( + conversionService.convert(indexCommunityForm.listingType(), ListingType.class)) + .showNsfw(indexCommunityForm.showNsfw().orElse(false)) + .person(person.orElse(null)) + .build()).stream().map( + community -> conversionService.convert(community, CommunityResponse.class)).toList(); + } @Operation(summary = "Get a specific community") - @GetMapping("/{id}") + @GetMapping("/{key}") @ApiResponses(value = { - // TODO: add responses - }) - public void show(@PathVariable String id) { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public CommunityResponse show(@PathVariable final String key) { + + try { + return conversionService.convert(communityRepository.findCommunityByTitleSlug(key), + CommunityResponse.class); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Community not found"); + } } @Operation(summary = "Create a new community") @PostMapping @ApiResponses(value = { - // TODO: add responses - }) - public void create() { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public CommunityResponse create(@RequestBody @Valid final CreateCommunity createCommunity, + final JwtPerson jwtPerson) { + + final Person person = getPersonOrThrowUnauthorized(jwtPerson); + + return sublinksCommunityService.createCommunity(createCommunity, person); } @Operation(summary = "Update an community") - @PostMapping("/{id}") + @PostMapping("/{key}") @ApiResponses(value = { - // TODO: add responses - }) - public void update(@PathVariable String id) { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public CommunityResponse update(@PathVariable String key, + @RequestBody @Valid UpdateCommunity updateCommunityForm, final JwtPerson principal) { + + final Person person = getPersonOrThrowUnauthorized(principal); + + return sublinksCommunityService.updateCommunity(key, updateCommunityForm, person); } - @Operation(summary = "Delete an community") - @DeleteMapping("/{id}") + @Operation(summary = "Purge an community") + @DeleteMapping("/{key}") @ApiResponses(value = { - // TODO: add responses - }) - public void delete(@PathVariable String id) { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public void delete(@PathVariable String key) { + // @TODO: implement } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/CommunityMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/CommunityMapper.java new file mode 100644 index 00000000..0409233a --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/CommunityMapper.java @@ -0,0 +1,34 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.mappers; + +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.languages.mappers.LanguageMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; +import com.sublinks.sublinksapi.community.entities.Community; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {LanguageMapper.class}) +public abstract class CommunityMapper implements Converter { + + @Override + @Mapping(target = "key", source = "community.titleSlug") + @Mapping(target = "title", source = "community.title") + @Mapping(target = "titleSlug", source = "community.titleSlug") + @Mapping(target = "description", source = "community.description") + @Mapping(target = "iconImageUrl", source = "community.iconImageUrl") + @Mapping(target = "bannerImageUrl", source = "community.bannerImageUrl") + @Mapping(target = "activityId", source = "community.activityPubId") + @Mapping(target = "languages", source = "community.languages", expression = "java(community.getLanguages().stream().map(languageMapper::convert).toList())") + @Mapping(target = "isLocal", source = "community.isLocal") + @Mapping(target = "isDeleted", source = "community.isDeleted") + @Mapping(target = "isRemoved", source = "community.isRemoved") + @Mapping(target = "isNsfw", source = "community.isNsfw") + @Mapping(target = "isPostingRestrictedToMods", source = "community.isPostingRestrictedToMods") + @Mapping(target = "publicKey", source = "community.publicKey") + @Mapping(target = "createdAt", source = "community.createdAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "updatedAt", source = "community.updatedAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + public abstract CommunityResponse convert(@Nullable Community community); +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityResponse.java new file mode 100644 index 00000000..db2b3ac9 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityResponse.java @@ -0,0 +1,28 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models; + +import com.sublinks.sublinksapi.api.sublinks.v1.languages.models.LanguageResponse; +import java.util.List; +import java.util.Optional; +import lombok.Builder; + +@Builder +public record CommunityResponse(String key, + String title, + String titleSlug, + String description, + String iconImageUrl, + Optional bannerImageUrl, + String activityId, + List languages, + Boolean isLocal, + Boolean isDeleted, + Boolean isRemoved, + Boolean isNsfw, + Boolean restrictedToModerators, + String publicKey, + String createdAt, + String updatedAt + +) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CreateCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CreateCommunity.java new file mode 100644 index 00000000..0d898f33 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CreateCommunity.java @@ -0,0 +1,13 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models; + +import java.util.Optional; + +public record CreateCommunity(String title, + String titleSlug, + String description, + Optional iconImageUrl, + Optional bannerImageUrl, + Boolean isNsfw, + Boolean isPostingRestrictedToMods) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java new file mode 100644 index 00000000..b9811409 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java @@ -0,0 +1,16 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; +import java.util.List; +import java.util.Optional; + +public record IndexCommunity(String search, + SortType sortType, + ListingType listingType, + Optional> communityKeys, + Optional showNsfw, + int limit, + int page) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java new file mode 100644 index 00000000..2fb6f05e --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java @@ -0,0 +1,14 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models; + +import java.util.Optional; + +public record UpdateCommunity(String title, + String description, + Optional iconImageUrl, + Optional bannerImageUrl, + Boolean isNsfw, + Optional deleted, + Optional removed, + Boolean isPostingRestrictedToMods) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java new file mode 100644 index 00000000..e63702b4 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -0,0 +1,62 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.services; + +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; +import com.sublinks.sublinksapi.community.entities.Community; +import com.sublinks.sublinksapi.community.repositories.CommunityRepository; +import com.sublinks.sublinksapi.community.services.CommunityService; +import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; +import com.sublinks.sublinksapi.person.entities.Person; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.ConversionService; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +@AllArgsConstructor +public class SublinksCommunityService { + + private final CommunityRepository communityRepository; + private final ConversionService conversionService; + private final CommunityService communityService; + private final LocalInstanceContext localInstanceContext; + + public CommunityResponse createCommunity(CreateCommunity createCommunity, Person person) { + + Community community = Community.builder() + .title(createCommunity.title()) + .titleSlug(createCommunity.titleSlug()) + .bannerImageUrl(createCommunity.bannerImageUrl().orElse(null)) + .iconImageUrl(createCommunity.iconImageUrl().orElse(null)) + .isNsfw(createCommunity.isNsfw()) + .isPostingRestrictedToMods(createCommunity.isPostingRestrictedToMods()) + .description(createCommunity.description()) + .instance(localInstanceContext.instance()) + .build(); + communityService.createCommunity(community); + + return conversionService.convert(community, CommunityResponse.class); + } + + public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommunityForm, + Person person) { + + Community community; + try { + community = communityRepository.findCommunityByTitleSlug(key); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Community not found"); + } + + community.setTitle(updateCommunityForm.title()); + community.setDescription(updateCommunityForm.description()); + community.setBannerImageUrl(updateCommunityForm.bannerImageUrl().orElse(null)); + community.setIconImageUrl(updateCommunityForm.iconImageUrl().orElse(null)); + community.setNsfw(updateCommunityForm.isNsfw()); + community.setPostingRestrictedToMods(updateCommunityForm.isPostingRestrictedToMods()); + communityRepository.save(community); + + return conversionService.convert(community, CommunityResponse.class); + + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java index e43fd262..46ba2fc8 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java @@ -44,7 +44,7 @@ public class PersonController extends AbstractSublinksApiController { @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List index(@RequestBody @Valid IndexPerson indexPerson) { + public List index(@Valid final IndexPerson indexPerson) { return personRepository.findAllByNameAndBiography(indexPerson.search(), PageRequest.of(indexPerson.page(), indexPerson.limit())).stream().map( @@ -55,9 +55,9 @@ public List index(@RequestBody @Valid IndexPerson indexPerson) { @GetMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public PersonResponse show(@PathVariable String id) { + public PersonResponse show(@PathVariable String key) { - Optional personResponse = personRepository.findById(Long.parseLong(id)).map( + Optional personResponse = personRepository.findOneByName(key).map( person -> conversionService.convert(person, PersonResponse.class)); return personResponse.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java index f84830fa..8bf4ab91 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java @@ -31,7 +31,7 @@ public abstract class PersonMapper implements Converter public abstract PersonResponse convert(@Nullable Person person); @Named("is_banned") - boolean mapIsBanned(Person person) { + Boolean mapIsBanned(Person person) { return RoleAuthorizingService.isBanned(person); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java index 5746ca2a..222653ea 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java @@ -1,13 +1,13 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.models; -import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortOrder; -import java.util.List; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; import lombok.Builder; @Builder public record IndexPerson(String search, - boolean local, - SortOrder sortOrder, + ListingType listingType, + SortType sortType, int limit, int page) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java index 283e1433..d1999274 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java @@ -13,11 +13,11 @@ public record PersonResponse(String key, String bio, String matrixUserId, String actorId, - boolean isLocal, - boolean isBanned, + Boolean isLocal, + Boolean isBanned, Optional banExpiresAt, - boolean isDeleted, - boolean isBotAccount, + Boolean isDeleted, + Boolean isBotAccount, Role role, String createdAt, String updatedAt) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/PrivateMessageResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/PrivateMessageResponse.java index e9a5dd39..3f55e280 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/PrivateMessageResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/PrivateMessageResponse.java @@ -8,9 +8,9 @@ public record PrivateMessageResponse(String key, PersonResponse sender, PersonResponse recipient, String content, - boolean isLocal, - boolean isDeleted, - boolean isRead, + Boolean isLocal, + Boolean isDeleted, + Boolean isRead, String activityPubId, String createdAt, String updatedAt) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/RoleMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/RoleMapper.java index a4313fd5..330968d2 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/RoleMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/RoleMapper.java @@ -29,7 +29,7 @@ public abstract com.sublinks.sublinksapi.api.sublinks.v1.roles.models.Role conve @Nullable Role role); @Named("is_expired") - boolean mapIsExpired(Role role) { + Boolean mapIsExpired(Role role) { return new Date().after(role.getExpiresAt()); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/Role.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/Role.java index 32671149..79e25ccf 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/Role.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/Role.java @@ -6,8 +6,8 @@ public record Role(String key, String name, String description, - boolean isActive, - boolean isExpired, + Boolean isActive, + Boolean isExpired, String expiresAt, String createdAt, String updatedAt) { From 6384abab55edf059e8b1f91a1a6c5b179f3dc8dc Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Thu, 2 May 2024 08:26:08 +0200 Subject: [PATCH 007/115] Fixed a bug --- .../sublinksapi/api/sublinks/v1/authentication/JwtFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtFilter.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtFilter.java index d4017fbc..390db21c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtFilter.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtFilter.java @@ -83,7 +83,7 @@ protected void doFilterInternal(final HttpServletRequest request, } @Override - protected Boolean shouldNotFilter(HttpServletRequest request) throws ServletException { + protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { return !request.getServletPath().startsWith("/api/v3"); } From 93f62f0d793c48ed10119faa5c1f4858dcb6fc9f Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Thu, 2 May 2024 12:52:30 +0200 Subject: [PATCH 008/115] Fixed a bug --- .../person/services/UserDataService.java | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/person/services/UserDataService.java b/src/main/java/com/sublinks/sublinksapi/person/services/UserDataService.java index ac4d7852..242cca63 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/services/UserDataService.java +++ b/src/main/java/com/sublinks/sublinksapi/person/services/UserDataService.java @@ -37,8 +37,7 @@ public void checkAndAddIpRelation(Person person, String ipAddress, String token, @Nullable String userAgent) { boolean saveUserIps = userDataConfig.isSaveUserData(); - Optional foundData = getActiveUserDataByPersonAndIpAddress(person, token, ipAddress, - userAgent); + Optional foundData = getActiveUserDataByPersonAndIpAddress(person, token); if (foundData.isPresent()) { @@ -94,15 +93,9 @@ public Optional getActiveUserDataByPersonAndToken(Person person, Strin return userDataRepository.findFirstByPersonAndTokenAndActiveIsTrue(person, token); } - private Optional getActiveUserDataByPersonAndIpAddress(Person person, String token, - String ipAddress, String userAgent) { + private Optional getActiveUserDataByPersonAndIpAddress(Person person, String token) { - if (userDataConfig.isSaveUserData()) { - return userDataRepository.findFirstByPersonAndTokenAndActiveIsTrue(person, ipAddress); - } - - return userDataRepository.findFirstByPersonAndTokenAndIpAddressAndUserAgentAndActiveIsTrue( - person, token, ipAddress, userAgent); + return userDataRepository.findFirstByPersonAndTokenAndActiveIsTrue(person, token); } @Transactional From 6034093177c77e098e9ef4a6f90df9e61cded9b4 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Thu, 2 May 2024 15:10:09 +0200 Subject: [PATCH 009/115] Add find methods in repositories and enhance community service Added method to find Instance by domain in the InstanceRepository and find methods for Community in the CommunityRepository. Enhanced SublinksCommunityService to handle community updates, deletion and removal, and validate unique titleSlugs upon creation. Also introduced ActorIdUtils for handling actorIDs. --- .../services/SublinksCommunityService.java | 28 ++++++++++++- .../api/sublinks/v1/utils/ActorIdUtils.java | 41 +++++++++++++++++++ .../repositories/CommunityRepository.java | 6 +++ .../repositories/InstanceRepository.java | 2 + 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/ActorIdUtils.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index e63702b4..f489dd51 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -3,10 +3,13 @@ import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; +import com.sublinks.sublinksapi.api.sublinks.v1.utils.ActorIdUtils; +import com.sublinks.sublinksapi.authorization.services.RoleAuthorizingService; import com.sublinks.sublinksapi.community.entities.Community; import com.sublinks.sublinksapi.community.repositories.CommunityRepository; import com.sublinks.sublinksapi.community.services.CommunityService; import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; +import com.sublinks.sublinksapi.instance.repositories.InstanceRepository; import com.sublinks.sublinksapi.person.entities.Person; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; @@ -20,9 +23,17 @@ public class SublinksCommunityService { private final ConversionService conversionService; private final CommunityService communityService; private final LocalInstanceContext localInstanceContext; + private final RoleAuthorizingService roleAuthorizingService; + private final InstanceRepository instanceRepository; public CommunityResponse createCommunity(CreateCommunity createCommunity, Person person) { + final Community oldCommunity = communityRepository.findCommunityByTitleSlug( + createCommunity.titleSlug()); + if (oldCommunity != null) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "community_slug_already_exist"); + } + Community community = Community.builder() .title(createCommunity.title()) .titleSlug(createCommunity.titleSlug()) @@ -43,9 +54,24 @@ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommu Community community; try { + String domain = ActorIdUtils.getActorDomain(key); + if (domain != null && domain.equals(localInstanceContext.instance().getDomain())) { + key = ActorIdUtils.getActorId(key); + } community = communityRepository.findCommunityByTitleSlug(key); } catch (Exception e) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Community not found"); + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found"); + } + + if (updateCommunityForm.deleted().orElse(null) != null + && updateCommunityForm.deleted().get() != community.isDeleted()) { + community.setDeleted(updateCommunityForm.deleted().orElse(community.isDeleted())); + //@todo: do modlog + } + if (updateCommunityForm.removed().orElse(null) != null + && updateCommunityForm.removed().get() != community.isRemoved()) { + community.setRemoved(updateCommunityForm.removed().orElse(community.isRemoved())); + //@todo: do modlog } community.setTitle(updateCommunityForm.title()); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/ActorIdUtils.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/ActorIdUtils.java new file mode 100644 index 00000000..447050ac --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/ActorIdUtils.java @@ -0,0 +1,41 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.utils; + +import java.util.List; + +public class ActorIdUtils { + + public static boolean isActorIdValid(String actorId) { + + return actorId.contains("@"); + } + + public static List splitActorId(String actorId) { + + if (!actorId.contains("@")) { + return null; + } + return List.of(actorId.split("@")); + } + + + public static String getActorId(String actorId) { + + List parts = splitActorId(actorId); + if (parts == null) { + return null; + } + + return parts.get(0); + } + + public static String getActorDomain(String actorId) { + + List parts = splitActorId(actorId); + if (parts == null) { + return null; + } + + return parts.get(1); + } + +} diff --git a/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepository.java b/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepository.java index 8eecdc05..3b1759e5 100644 --- a/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepository.java @@ -1,6 +1,8 @@ package com.sublinks.sublinksapi.community.repositories; import com.sublinks.sublinksapi.community.entities.Community; +import com.sublinks.sublinksapi.instance.entities.Instance; +import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; public interface CommunityRepository extends JpaRepository, @@ -11,4 +13,8 @@ public interface CommunityRepository extends JpaRepository, Community findCommunityByIsLocalTrueAndTitleSlug(String titleSlug); Community findCommunityByTitleSlug(String titleSlug); + + List findCommunitiesByInstance(Instance instance); + + Community findCommunityByPublicKey(String publicKey); } diff --git a/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceRepository.java b/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceRepository.java index ffbdcc5f..663ce9e5 100644 --- a/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceRepository.java @@ -5,4 +5,6 @@ public interface InstanceRepository extends JpaRepository { + Instance findInstanceByDomain(String domain); + } From 364454a21d7554771be8771d8b363af6ef54f04e Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Sat, 4 May 2024 17:53:06 +0200 Subject: [PATCH 010/115] Implement comment service logic and enhance authorization Added SublinksCommentService for handling comment creation with proper validations and exception handling. Adjusted RoleAuthorizingService by introducing helper methods to check a person's permission status. Updated CommunityModActionsController with refined exception handling logic. Added necessary find methods in PostRepository and CommentRepository. Introduced new model classes for comments. --- .../CommunityModActionsController.java | 97 ++++++---------- .../v1/comment/mappers/CommentMapper.java | 31 ++++++ .../v1/comment/models/CommentResponse.java | 19 ++++ .../v1/comment/models/CreateComment.java | 13 +++ .../v1/comment/models/IndexComment.java | 19 ++++ .../services/SublinksCommentService.java | 51 +++++++++ .../community/models/CommunityResponse.java | 2 +- .../v1/community/models/UpdateCommunity.java | 8 +- .../services/SublinksCommunityService.java | 39 +++++-- .../services/RoleAuthorizingService.java | 105 ++++++++++-------- .../sublinksapi/comment/entities/Comment.java | 1 - .../repositories/CommentRepository.java | 3 + .../comment/services/CommentService.java | 8 +- .../services/LinkPersonCommunityService.java | 24 +++- .../sublinksapi/post/entities/Post.java | 1 - .../post/repositories/PostRepository.java | 3 + 16 files changed, 289 insertions(+), 135 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/CommentMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentResponse.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CreateComment.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java index 71efde10..2224a955 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java @@ -105,9 +105,8 @@ CommunityResponse hide(@Valid @RequestBody final HideCommunity hideCommunityForm .build(); moderationLogService.createModerationLog(moderationLog); - return CommunityResponse.builder() - .community_view(lemmyCommunityService.communityViewFromCommunity(community)) - .build(); + return CommunityResponse.builder().community_view( + lemmyCommunityService.communityViewFromCommunity(community)).build(); } @Operation(summary = "Delete a community.") @@ -118,16 +117,14 @@ CommunityResponse delete(@Valid final DeleteCommunity deleteCommunityForm, JwtPe final Person person = getPersonOrThrowUnauthorized(principal); - roleAuthorizingService.hasAdminOrPermissionOrThrow(person, RolePermission.DELETE_COMMUNITY, - () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); - - RoleAuthorizingService.isAdminElseThrow(person, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN)); - final Community community = communityRepository.findById( - (long) deleteCommunityForm.community_id()) - .orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + (long) deleteCommunityForm.community_id()).orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + + if (!linkPersonCommunityService.hasLink(person, community, LinkPersonCommunityType.owner)) { + roleAuthorizingService.hasAdminOrPermissionOrThrow(person, RolePermission.DELETE_COMMUNITY, + () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); + } community.setDeleted(deleteCommunityForm.deleted()); communityRepository.save(community); @@ -143,9 +140,8 @@ CommunityResponse delete(@Valid final DeleteCommunity deleteCommunityForm, JwtPe .build(); moderationLogService.createModerationLog(moderationLog); - return CommunityResponse.builder() - .community_view(lemmyCommunityService.communityViewFromCommunity(community)) - .build(); + return CommunityResponse.builder().community_view( + lemmyCommunityService.communityViewFromCommunity(community)).build(); } @Operation(summary = "A moderator remove for a community.") @@ -162,15 +158,11 @@ CommunityResponse remove(@Valid @RequestBody final RemoveCommunity removeCommuni () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); final Community community = communityRepository.findById( - (long) removeCommunityForm.community_id()) - .orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - - final boolean isAllowed = - linkPersonCommunityService.hasLink(person, community, LinkPersonCommunityType.moderator) - || linkPersonCommunityService.hasLink(person, community, LinkPersonCommunityType.owner); + (long) removeCommunityForm.community_id()).orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - if (!isAllowed) { + if (!linkPersonCommunityService.hasAnyLink(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_allowed"); } @@ -189,9 +181,8 @@ CommunityResponse remove(@Valid @RequestBody final RemoveCommunity removeCommuni .build(); moderationLogService.createModerationLog(moderationLog); - return CommunityResponse.builder() - .community_view(lemmyCommunityService.communityViewFromCommunity(community)) - .build(); + return CommunityResponse.builder().community_view( + lemmyCommunityService.communityViewFromCommunity(community)).build(); } @Operation(summary = "Transfer your community to an existing moderator.") @@ -208,15 +199,11 @@ GetCommunityResponse transfer(@Valid @RequestBody final TransferCommunity transf () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); final Community community = communityRepository.findById( - (long) transferCommunityForm.community_id()) - .orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + (long) transferCommunityForm.community_id()).orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - final boolean isAllowed = - RoleAuthorizingService.isAdmin(person) || linkPersonCommunityService.hasLink(person, - community, LinkPersonCommunityType.owner); - - if (!isAllowed) { + if (!linkPersonCommunityService.hasLinkOrAdmin(person, community, + LinkPersonCommunityType.owner)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_allowed"); } @@ -229,10 +216,8 @@ GetCommunityResponse transfer(@Valid @RequestBody final TransferCommunity transf } final Person oldOwner = linkPersonCommunityService.getPersonsFromCommunityAndListTypes( - community, List.of(LinkPersonCommunityType.owner)) - .stream() - .findFirst() - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "owner_not_found")); + community, List.of(LinkPersonCommunityType.owner)).stream().findFirst().orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "owner_not_found")); linkPersonCommunityService.addLink(oldOwner, community, LinkPersonCommunityType.moderator); linkPersonCommunityService.removeLink(oldOwner, community, LinkPersonCommunityType.owner); @@ -250,9 +235,8 @@ GetCommunityResponse transfer(@Valid @RequestBody final TransferCommunity transf .build(); moderationLogService.createModerationLog(moderationLog); - return GetCommunityResponse.builder() - .community_view(lemmyCommunityService.communityViewFromCommunity(community)) - .build(); + return GetCommunityResponse.builder().community_view( + lemmyCommunityService.communityViewFromCommunity(community)).build(); } @Operation(summary = "Ban a user from a community.") @@ -271,11 +255,8 @@ BanFromCommunityResponse banUser(@Valid @RequestBody final BanFromCommunity banP .orElseThrow( () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - final boolean isAllowed = - linkPersonCommunityService.hasLink(person, community, LinkPersonCommunityType.moderator) - || linkPersonCommunityService.hasLink(person, community, LinkPersonCommunityType.owner); - - if (!isAllowed) { + if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_allowed"); } @@ -321,10 +302,8 @@ BanFromCommunityResponse banUser(@Valid @RequestBody final BanFromCommunity banP .build(); moderationLogService.createModerationLog(moderationLog); - return BanFromCommunityResponse.builder() - .banned(banPersonForm.ban()) - .person_view(lemmyPersonService.getPersonView(personToBan)) - .build(); + return BanFromCommunityResponse.builder().banned(banPersonForm.ban()).person_view( + lemmyPersonService.getPersonView(personToBan)).build(); } @Operation(summary = "Add a moderator to your community.") @@ -342,13 +321,12 @@ AddModToCommunityResponse addMod(@Valid @RequestBody AddModToCommunity addModToC () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); final Community community = communityRepository.findById( - (long) addModToCommunityForm.community_id()) - .orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + (long) addModToCommunityForm.community_id()).orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - final boolean isAllowed = - linkPersonCommunityService.hasLink(person, community, LinkPersonCommunityType.moderator) - || linkPersonCommunityService.hasLink(person, community, LinkPersonCommunityType.owner); + final boolean isAllowed = linkPersonCommunityService.hasLink(person, community, + LinkPersonCommunityType.moderator) || linkPersonCommunityService.hasLink(person, community, + LinkPersonCommunityType.owner); if (!isAllowed) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_allowed"); @@ -374,14 +352,13 @@ AddModToCommunityResponse addMod(@Valid @RequestBody AddModToCommunity addModToC Collection moderators = linkPersonCommunityService.getPersonsFromCommunityAndListTypes( community, List.of(LinkPersonCommunityType.moderator)); - List moderatorsView = moderators.stream() - .map(moderator -> CommunityModeratorView.builder() + List moderatorsView = moderators.stream().map( + moderator -> CommunityModeratorView.builder() .moderator(conversionService.convert(moderator, com.sublinks.sublinksapi.api.lemmy.v3.user.models.Person.class)) .community(conversionService.convert(community, com.sublinks.sublinksapi.api.lemmy.v3.community.models.Community.class)) - .build()) - .toList(); + .build()).toList(); // Create Moderation Log ModerationLog moderationLog = ModerationLog.builder() diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/CommentMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/CommentMapper.java new file mode 100644 index 00000000..202744cf --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/CommentMapper.java @@ -0,0 +1,31 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.comment.mappers; + +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.mappers.PersonMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; +import com.sublinks.sublinksapi.comment.entities.Comment; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {PersonMapper.class}) +public abstract class CommentMapper implements Converter { + + @Override + @Mapping(target = "key", source = "comment.path") + @Mapping(target = "activityPubId", source = "comment.activityPubId") + @Mapping(target = "body", source = "comment.commentBody") + @Mapping(target = "path", source = "comment.path") + @Mapping(target = "isLocal", source = "comment.isLocal") + @Mapping(target = "isDeleted", source = "comment.isDeleted") + @Mapping(target = "isFeatured", source = "comment.isFeatured") + @Mapping(target = "isRemoved", expression = "java(comment.isRemoved())") + @Mapping(target = "creator", expression = "java(personMapper.convert(comment.getCreator()))") + @Mapping(target = "createdAt", source = "comment.createdAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "updatedAt", source = "comment.updatedAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + public abstract CommentResponse convert(@Nullable Comment comment); + + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentResponse.java new file mode 100644 index 00000000..f2ef5159 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentResponse.java @@ -0,0 +1,19 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.comment.models; + +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; +import lombok.Builder; + +@Builder +public record CommentResponse(String key, + String activityPubId, + String body, + String path, + Boolean isLocal, + Boolean isDeleted, + Boolean isFeatured, + Boolean isRemoved, + String createdAt, + PersonResponse creator, + String updatedAt) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CreateComment.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CreateComment.java new file mode 100644 index 00000000..7eeef173 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CreateComment.java @@ -0,0 +1,13 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.comment.models; + +import java.util.Optional; +import lombok.Builder; + +@Builder +public record CreateComment(String body, + String parentKey, + String postKey, + Optional languageKey, + Optional featured) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java new file mode 100644 index 00000000..c3205188 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java @@ -0,0 +1,19 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.comment.models; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; +import java.util.List; +import java.util.Optional; +import lombok.Builder; + +@Builder +public record IndexComment(String search, + SortType sortType, + ListingType listingType, + Optional> communityKeys, + Optional> postKeys, + Optional showNsfw, + int limit, + int page) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java new file mode 100644 index 00000000..26d1bb35 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -0,0 +1,51 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.comment.services; + +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CreateComment; +import com.sublinks.sublinksapi.comment.entities.Comment; +import com.sublinks.sublinksapi.comment.repositories.CommentRepository; +import com.sublinks.sublinksapi.comment.services.CommentService; +import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; +import com.sublinks.sublinksapi.language.repositories.LanguageRepository; +import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.post.entities.Post; +import com.sublinks.sublinksapi.post.repositories.PostRepository; +import com.sublinks.sublinksapi.shared.RemovedState; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.ConversionService; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +@AllArgsConstructor +public class SublinksCommentService { + + private final LocalInstanceContext localInstanceContext; + private final ConversionService conversionService; + private final LanguageRepository languageRepository; + private final CommentRepository commentRepository; + private final CommentService commentService; + private final PostRepository postRepository; + + public CommentResponse createComment(CreateComment createComment, Person person) { + + final Post parentPost = postRepository.findByTitleSlugAndRemovedStateIs(createComment.postKey(), + RemovedState.NOT_REMOVED).orElseThrow( + () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "post_not_found")); + + final Comment parentComment = commentRepository.findByPath(createComment.parentKey()) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); + + Comment comment = Comment.builder() + .commentBody(createComment.body()) + .person(person) + .post(parentPost) + .path(parentComment.getPath()) + .build(); + + commentService.createComment(comment, parentComment); + return conversionService.convert(comment, CommentResponse.class); + } + + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityResponse.java index db2b3ac9..aebf5e4b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityResponse.java @@ -12,7 +12,7 @@ public record CommunityResponse(String key, String description, String iconImageUrl, Optional bannerImageUrl, - String activityId, + String activityPubId, List languages, Boolean isLocal, Boolean isDeleted, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java index 2fb6f05e..ade18f09 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java @@ -2,13 +2,13 @@ import java.util.Optional; -public record UpdateCommunity(String title, - String description, +public record UpdateCommunity(Optional title, + Optional description, Optional iconImageUrl, Optional bannerImageUrl, - Boolean isNsfw, + Optional isNsfw, Optional deleted, Optional removed, - Boolean isPostingRestrictedToMods) { + Optional isPostingRestrictedToMods) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index f489dd51..95de6248 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -11,6 +11,7 @@ import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; import com.sublinks.sublinksapi.instance.repositories.InstanceRepository; import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.person.services.LinkPersonCommunityService; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpStatus; @@ -25,6 +26,7 @@ public class SublinksCommunityService { private final LocalInstanceContext localInstanceContext; private final RoleAuthorizingService roleAuthorizingService; private final InstanceRepository instanceRepository; + private final LinkPersonCommunityService linkPersonCommunityService; public CommunityResponse createCommunity(CreateCommunity createCommunity, Person person) { @@ -52,6 +54,8 @@ public CommunityResponse createCommunity(CreateCommunity createCommunity, Person public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommunityForm, Person person) { + + Community community; try { String domain = ActorIdUtils.getActorDomain(key); @@ -63,23 +67,38 @@ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommu throw new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found"); } - if (updateCommunityForm.deleted().orElse(null) != null - && updateCommunityForm.deleted().get() != community.isDeleted()) { + if(!roleAuthorizingService.isModeratorOrAdmin(person, community)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_update_community"); + } + + + final Boolean isDeleted = updateCommunityForm.deleted().orElse(null); + + if (isDeleted != null && isDeleted != community.isDeleted()) { + community.setDeleted(updateCommunityForm.deleted().orElse(community.isDeleted())); //@todo: do modlog } - if (updateCommunityForm.removed().orElse(null) != null - && updateCommunityForm.removed().get() != community.isRemoved()) { + + final Boolean isRemoved = updateCommunityForm.removed().orElse(null); + + if (isRemoved != null && isRemoved != community.isRemoved()) { + + if (!roleAuthorizingService.isModeratorOrAdmin(person, community)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_remove_community"); + } community.setRemoved(updateCommunityForm.removed().orElse(community.isRemoved())); //@todo: do modlog } - community.setTitle(updateCommunityForm.title()); - community.setDescription(updateCommunityForm.description()); - community.setBannerImageUrl(updateCommunityForm.bannerImageUrl().orElse(null)); - community.setIconImageUrl(updateCommunityForm.iconImageUrl().orElse(null)); - community.setNsfw(updateCommunityForm.isNsfw()); - community.setPostingRestrictedToMods(updateCommunityForm.isPostingRestrictedToMods()); + updateCommunityForm.title().ifPresent(community::setTitle); + updateCommunityForm.description().ifPresent(community::setDescription); + updateCommunityForm.isNsfw().ifPresent(community::setNsfw); + updateCommunityForm.iconImageUrl().ifPresent(community::setIconImageUrl); + updateCommunityForm.bannerImageUrl().ifPresent(community::setBannerImageUrl); + updateCommunityForm.isPostingRestrictedToMods().ifPresent( + community::setPostingRestrictedToMods); communityRepository.save(community); return conversionService.convert(community, CommunityResponse.class); diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/RoleAuthorizingService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/RoleAuthorizingService.java index 25b4c6d8..cf086ab3 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/RoleAuthorizingService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/RoleAuthorizingService.java @@ -3,9 +3,13 @@ import com.sublinks.sublinksapi.authorization.entities.Role; import com.sublinks.sublinksapi.authorization.enums.RolePermission; import com.sublinks.sublinksapi.authorization.repositories.RoleRepository; +import com.sublinks.sublinksapi.community.entities.Community; import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.person.enums.LinkPersonCommunityType; +import com.sublinks.sublinksapi.person.services.LinkPersonCommunityService; import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.function.Supplier; import lombok.NonNull; @@ -17,12 +21,12 @@ public class RoleAuthorizingService { private final RoleRepository roleRepository; + private final LinkPersonCommunityService linkPersonCommunityService; public static boolean isBanned(final Role role) { - return role.getRolePermissions() - .stream() - .anyMatch(x -> x.getPermission().equals(RolePermission.BANNED)); + return role.getRolePermissions().stream().anyMatch( + x -> x.getPermission().equals(RolePermission.BANNED)); } public static boolean isBanned(final Person person) { @@ -45,9 +49,8 @@ public static boolean isAdmin(final Person person) { public static boolean isAdmin(@NonNull final Role role) { - return role.getRolePermissions() - .stream() - .anyMatch(x -> x.getPermission().equals(RolePermission.ADMIN)); + return role.getRolePermissions().stream().anyMatch( + x -> x.getPermission().equals(RolePermission.ADMIN)); } public static void isAdminElseThrow(Person person, @@ -61,55 +64,43 @@ public static void isAdminElseThrow(Person person, public Set getAdmins() { // i didnt used hashset here because it is not safe to use as it could duplicate. - return roleRepository.findAllByRolePermissionContains(RolePermission.ADMIN) - .stream() - .map(role -> role.getPersons().stream().toList()) - .reduce(new ArrayList<>(), (arr, ele) -> { - ele.forEach(x -> { - if (!arr.contains(x)) { - arr.add(x); - } - }); - return arr; - }) - .stream() - .collect(HashSet::new, HashSet::add, HashSet::addAll); + return roleRepository.findAllByRolePermissionContains(RolePermission.ADMIN).stream().map( + role -> role.getPersons().stream().toList()).reduce(new ArrayList<>(), (arr, ele) -> { + ele.forEach(x -> { + if (!arr.contains(x)) { + arr.add(x); + } + }); + return arr; + }).stream().collect(HashSet::new, HashSet::add, HashSet::addAll); } public Set getUsers() { // i didnt used hashset here because it is not safe to use as it could duplicate. - return roleRepository.findAllByRolePermissionContains(RolePermission.DEFAULT) - .stream() - .map(role -> role.getPersons().stream().toList()) - .reduce(new ArrayList<>(), (arr, ele) -> { - ele.forEach(x -> { - if (!arr.contains(x)) { - arr.add(x); - } - }); - return arr; - }) - .stream() - .collect(HashSet::new, HashSet::add, HashSet::addAll); + return roleRepository.findAllByRolePermissionContains(RolePermission.DEFAULT).stream().map( + role -> role.getPersons().stream().toList()).reduce(new ArrayList<>(), (arr, ele) -> { + ele.forEach(x -> { + if (!arr.contains(x)) { + arr.add(x); + } + }); + return arr; + }).stream().collect(HashSet::new, HashSet::add, HashSet::addAll); } public Set getBannedUsers() { // i didnt used hashset here because it is not safe to use as it could duplicate. - return roleRepository.findAllByRolePermissionContains(RolePermission.BANNED) - .stream() - .map(role -> role.getPersons().stream().toList()) - .reduce(new ArrayList<>(), (arr, ele) -> { - ele.forEach(x -> { - if (!arr.contains(x)) { - arr.add(x); - } - }); - return arr; - }) - .stream() - .collect(HashSet::new, HashSet::add, HashSet::addAll); + return roleRepository.findAllByRolePermissionContains(RolePermission.BANNED).stream().map( + role -> role.getPersons().stream().toList()).reduce(new ArrayList<>(), (arr, ele) -> { + ele.forEach(x -> { + if (!arr.contains(x)) { + arr.add(x); + } + }); + return arr; + }).stream().collect(HashSet::new, HashSet::add, HashSet::addAll); } public Role getAdminRole() { @@ -242,9 +233,8 @@ public boolean hasPermission(final Role role, final RolePermission rolePermissio return true; } - return role.getRolePermissions() - .stream() - .anyMatch(x -> x.getPermission().equals(rolePermission)); + return role.getRolePermissions().stream().anyMatch( + x -> x.getPermission().equals(rolePermission)); } public boolean hasPermission(final Person person, final RolePermission rolePermission) { @@ -327,4 +317,23 @@ public void hasAnyPermissionOrThrow(final Person person, hasAnyPermissionOrThrow(role, rolePermissions, exceptionSupplier); } + + public boolean isModerator(final Person person, final Community community) { + + if (person == null || person.getRole() == null) { + return false; + } + + return linkPersonCommunityService.hasAnyLink(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)); + } + + public boolean isModeratorOrAdmin(final Person person, final Community community) { + + if (person == null || person.getRole() == null) { + return false; + } + + return isAdmin(person) || isModerator(person, community); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/comment/entities/Comment.java b/src/main/java/com/sublinks/sublinksapi/comment/entities/Comment.java index 6061bfd6..c2d6a61f 100644 --- a/src/main/java/com/sublinks/sublinksapi/comment/entities/Comment.java +++ b/src/main/java/com/sublinks/sublinksapi/comment/entities/Comment.java @@ -83,7 +83,6 @@ public class Comment implements Serializable { @Column(nullable = false, name = "removed_state") @Enumerated(EnumType.STRING) - private RemovedState removedState; @Column(nullable = false, name = "is_local") diff --git a/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentRepository.java b/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentRepository.java index aec5a21e..f8a8c9a9 100644 --- a/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentRepository.java @@ -3,9 +3,12 @@ import com.sublinks.sublinksapi.comment.entities.Comment; import com.sublinks.sublinksapi.person.entities.Person; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface CommentRepository extends JpaRepository, CommentRepositorySearch { List findByPerson(Person person); + + Optional findByPath(String path); } diff --git a/src/main/java/com/sublinks/sublinksapi/comment/services/CommentService.java b/src/main/java/com/sublinks/sublinksapi/comment/services/CommentService.java index 12888598..3b9676f3 100644 --- a/src/main/java/com/sublinks/sublinksapi/comment/services/CommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/comment/services/CommentService.java @@ -184,8 +184,8 @@ public void removeAllCommentsFromCommunityAndUser(final Community community, fin final boolean removed) { commentRepository.allCommentsByCommunityAndPersonAndRemoved(community, person, - List.of(removed ? RemovedState.NOT_REMOVED : RemovedState.REMOVED_BY_COMMUNITY)) - .forEach(comment -> { + List.of(removed ? RemovedState.NOT_REMOVED : RemovedState.REMOVED_BY_COMMUNITY)).forEach( + comment -> { comment.setRemovedState( removed ? RemovedState.REMOVED_BY_COMMUNITY : RemovedState.NOT_REMOVED); commentRepository.save(comment); @@ -202,8 +202,8 @@ public void removeAllCommentsFromCommunityAndUser(final Community community, fin public void removeAllCommentsFromUser(final Person person, final boolean removed) { commentRepository.allCommentsByPersonAndRemoved(person, - List.of(removed ? RemovedState.NOT_REMOVED : RemovedState.REMOVED_BY_INSTANCE)) - .forEach(comment -> { + List.of(removed ? RemovedState.NOT_REMOVED : RemovedState.REMOVED_BY_INSTANCE)).forEach( + comment -> { comment.setRemovedState( removed ? RemovedState.REMOVED_BY_INSTANCE : RemovedState.NOT_REMOVED); commentRepository.save(comment); diff --git a/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java b/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java index f35ab372..4fdaf072 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java @@ -1,5 +1,6 @@ package com.sublinks.sublinksapi.person.services; +import com.sublinks.sublinksapi.authorization.services.RoleAuthorizingService; import com.sublinks.sublinksapi.community.entities.Community; import com.sublinks.sublinksapi.person.entities.LinkPersonCommunity; import com.sublinks.sublinksapi.person.entities.Person; @@ -24,6 +25,17 @@ public class LinkPersonCommunityService { private final LinkPersonCommunityCreatedPublisher linkPersonCommunityCreatedPublisher; private final LinkPersonCommunityDeletedPublisher linkPersonCommunityDeletedPublisher; + public boolean hasLinkOrAdmin(Person person, Community community, LinkPersonCommunityType type) { + + return RoleAuthorizingService.isAdmin(person) || hasLink(person, community, type); + } + + public boolean hasAnyLinkOrAdmin(Person person, Community community, + List types) { + + return RoleAuthorizingService.isAdmin(person) || hasAnyLink(person, community, types); + } + public boolean hasLink(Person person, Community community, LinkPersonCommunityType type) { final Optional linkPersonCommunity = linkPersonCommunityRepository.getLinkPersonCommunityByCommunityAndPersonAndLinkType( @@ -42,8 +54,8 @@ public boolean hasAnyLink(Person person, Community community, @Transactional public void addLink(Person person, Community community, LinkPersonCommunityType type) { - final LinkPersonCommunity newLink = LinkPersonCommunity.builder().community(community) - .person(person).linkType(type).build(); + final LinkPersonCommunity newLink = LinkPersonCommunity.builder().community(community).person( + person).linkType(type).build(); person.getLinkPersonCommunity().add(newLink); community.getLinkPersonCommunity().add(newLink); linkPersonCommunityRepository.save(newLink); @@ -58,10 +70,10 @@ public void removeLink(Person person, Community community, LinkPersonCommunityTy if (linkPersonCommunity.isEmpty()) { return; } - person.getLinkPersonCommunity() - .removeIf(l -> Objects.equals(l.getId(), linkPersonCommunity.get().getId())); - community.getLinkPersonCommunity() - .removeIf(l -> Objects.equals(l.getId(), linkPersonCommunity.get().getId())); + person.getLinkPersonCommunity().removeIf( + l -> Objects.equals(l.getId(), linkPersonCommunity.get().getId())); + community.getLinkPersonCommunity().removeIf( + l -> Objects.equals(l.getId(), linkPersonCommunity.get().getId())); linkPersonCommunityRepository.delete(linkPersonCommunity.get()); linkPersonCommunityDeletedPublisher.publish(linkPersonCommunity.get()); } diff --git a/src/main/java/com/sublinks/sublinksapi/post/entities/Post.java b/src/main/java/com/sublinks/sublinksapi/post/entities/Post.java index 8f9e8a8e..69810ef3 100644 --- a/src/main/java/com/sublinks/sublinksapi/post/entities/Post.java +++ b/src/main/java/com/sublinks/sublinksapi/post/entities/Post.java @@ -87,7 +87,6 @@ public class Post { @Column(nullable = false, name = "removed_state") @Enumerated(EnumType.STRING) - private RemovedState removedState; @Column(nullable = false, name = "is_local") diff --git a/src/main/java/com/sublinks/sublinksapi/post/repositories/PostRepository.java b/src/main/java/com/sublinks/sublinksapi/post/repositories/PostRepository.java index 715a5192..e7dbf4d4 100644 --- a/src/main/java/com/sublinks/sublinksapi/post/repositories/PostRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/post/repositories/PostRepository.java @@ -1,8 +1,11 @@ package com.sublinks.sublinksapi.post.repositories; import com.sublinks.sublinksapi.post.entities.Post; +import com.sublinks.sublinksapi.shared.RemovedState; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface PostRepository extends JpaRepository, PostRepositorySearch { + Optional findByTitleSlugAndRemovedStateIs(String titleSlug, RemovedState removedState); } From 7945a0a248051841bddb893672e955c394042904 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Sat, 4 May 2024 18:01:25 +0200 Subject: [PATCH 011/115] Refactor to use Optional in CommunityRepository methods CommunityRepository has been refactored to use Optional for the return type of findCommunityByTitleSlug method to better handle absence of value situations. Additionally, logic has been added in createCommunity and updateCommunity methods to check user permissions for these operations. The updateCommunity method has also been simplified for better readability and efficiency. --- .../services/SublinksCommunityService.java | 29 ++++++++++--------- .../repositories/CommunityRepository.java | 3 +- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index 95de6248..91619ca2 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -4,6 +4,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.utils.ActorIdUtils; +import com.sublinks.sublinksapi.authorization.enums.RolePermission; import com.sublinks.sublinksapi.authorization.services.RoleAuthorizingService; import com.sublinks.sublinksapi.community.entities.Community; import com.sublinks.sublinksapi.community.repositories.CommunityRepository; @@ -12,6 +13,7 @@ import com.sublinks.sublinksapi.instance.repositories.InstanceRepository; import com.sublinks.sublinksapi.person.entities.Person; import com.sublinks.sublinksapi.person.services.LinkPersonCommunityService; +import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpStatus; @@ -30,12 +32,16 @@ public class SublinksCommunityService { public CommunityResponse createCommunity(CreateCommunity createCommunity, Person person) { - final Community oldCommunity = communityRepository.findCommunityByTitleSlug( + final Optional oldCommunity = communityRepository.findCommunityByTitleSlug( createCommunity.titleSlug()); - if (oldCommunity != null) { + if (oldCommunity.isPresent()) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "community_slug_already_exist"); } + if (roleAuthorizingService.hasAdminOrPermission(person, RolePermission.CREATE_COMMUNITY)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_create_community"); + } + Community community = Community.builder() .title(createCommunity.title()) .titleSlug(createCommunity.titleSlug()) @@ -54,24 +60,21 @@ public CommunityResponse createCommunity(CreateCommunity createCommunity, Person public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommunityForm, Person person) { + String domain = ActorIdUtils.getActorDomain(key); + if (domain != null && domain.equals(localInstanceContext.instance().getDomain())) { + key = ActorIdUtils.getActorId(key); + } + Optional foundCommunity = communityRepository.findCommunityByTitleSlug(key); - - Community community; - try { - String domain = ActorIdUtils.getActorDomain(key); - if (domain != null && domain.equals(localInstanceContext.instance().getDomain())) { - key = ActorIdUtils.getActorId(key); - } - community = communityRepository.findCommunityByTitleSlug(key); - } catch (Exception e) { + if (foundCommunity.isEmpty()) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found"); } - if(!roleAuthorizingService.isModeratorOrAdmin(person, community)) { + Community community = foundCommunity.get(); + if (!roleAuthorizingService.isModeratorOrAdmin(person, community)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_update_community"); } - final Boolean isDeleted = updateCommunityForm.deleted().orElse(null); if (isDeleted != null && isDeleted != community.isDeleted()) { diff --git a/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepository.java b/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepository.java index 3b1759e5..93643c4b 100644 --- a/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepository.java @@ -3,6 +3,7 @@ import com.sublinks.sublinksapi.community.entities.Community; import com.sublinks.sublinksapi.instance.entities.Instance; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface CommunityRepository extends JpaRepository, @@ -12,7 +13,7 @@ public interface CommunityRepository extends JpaRepository, Community findCommunityByIsLocalTrueAndTitleSlug(String titleSlug); - Community findCommunityByTitleSlug(String titleSlug); + Optional findCommunityByTitleSlug(String titleSlug); List findCommunitiesByInstance(Instance instance); From 842ce8dfb5eafcee2bb718981f2c6469f4a2434d Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Sun, 5 May 2024 12:04:28 +0200 Subject: [PATCH 012/115] Refactor authentication and add language support to comments This commit renames classes related to JWT authentication for coherence and better readability. It also includes a new column in the Language entity to handle language codes. Furthermore, a validation has been added to check language availability while creating comments. Minor syntax changes are also performed to improve readability and comply with updated editor settings. This refactoring ensures better code organization and enhances functionality by accepting comments in different languages. --- .editorconfig | 4 +- .../authentication/config/SecurityConfig.java | 36 ------------- .../AbstractLemmyApiController.java | 2 +- ...TypeMapper.java => ListingTypeMapper.java} | 2 +- .../controllers/ModerationLogController.java | 15 ++---- .../v3/post/controllers/PostController.java | 4 +- .../search/controllers/SearchController.java | 4 +- .../user/controllers/UserAuthController.java | 53 ++++++++----------- ...{JwtFilter.java => SublinksJwtFilter.java} | 10 ++-- ...{JwtPerson.java => SublinksJwtPerson.java} | 4 +- .../{JwtUtil.java => SublinksJwtUtil.java} | 4 +- .../authentication/config/SecurityConfig.java | 22 ++++---- ...entMapper.java => LemmyCommentMapper.java} | 17 +++--- .../services/SublinksCommentService.java | 13 ++++- .../AbstractSublinksApiController.java | 14 ++--- ...apper.java => SublinksSortTypeMapper.java} | 2 +- .../controllers/CommunityController.java | 12 ++--- ...pper.java => SublinksCommunityMapper.java} | 24 +++++---- ...apper.java => SublinksLanguageMapper.java} | 6 +-- .../person/controllers/PersonController.java | 4 +- ...nMapper.java => SublinksPersonMapper.java} | 29 +++++++--- .../services/SublinksPersonService.java | 8 +-- ...java => SublinksPrivateMessageMapper.java} | 17 +++--- ...oleMapper.java => SublinksRoleMapper.java} | 6 +-- .../utils/mappers/OptionalStringMapper.java | 19 +++++++ .../sublinksapi/comment/entities/Comment.java | 3 +- .../language/entities/Language.java | 5 +- 27 files changed, 172 insertions(+), 167 deletions(-) delete mode 100644 src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java rename src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/enums/mappers/{LemmyListingTypeMapper.java => ListingTypeMapper.java} (89%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/{JwtFilter.java => SublinksJwtFilter.java} (90%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/{JwtPerson.java => SublinksJwtPerson.java} (75%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/{JwtUtil.java => SublinksJwtUtil.java} (95%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/{CommentMapper.java => LemmyCommentMapper.java} (66%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/{LemmySortTypeMapper.java => SublinksSortTypeMapper.java} (93%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/{CommunityMapper.java => SublinksCommunityMapper.java} (61%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/mappers/{LanguageMapper.java => SublinksLanguageMapper.java} (81%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/{PersonMapper.java => SublinksPersonMapper.java} (65%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/mappers/{PrivateMessageMapper.java => SublinksPrivateMessageMapper.java} (68%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/{RoleMapper.java => SublinksRoleMapper.java} (86%) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/mappers/OptionalStringMapper.java diff --git a/.editorconfig b/.editorconfig index 90d99b9b..0a6a5af1 100644 --- a/.editorconfig +++ b/.editorconfig @@ -190,8 +190,8 @@ ij_java_message_dd_suffix = EJB ij_java_message_eb_prefix = ij_java_message_eb_suffix = Bean ij_java_method_annotation_wrap = split_into_lines -ij_java_method_brace_style = end_of_line -ij_java_method_call_chain_wrap = on_every_item +ij_java_method_brace_style = next_line_if_wrapped +ij_java_method_call_chain_wrap = split_into_lines ij_java_method_parameters_new_line_after_left_paren = false ij_java_method_parameters_right_paren_on_new_line = false ij_java_method_parameters_wrap = normal diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java deleted file mode 100644 index 356db327..00000000 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.sublinks.sublinksapi.api.lemmy.v3.authentication.config; - -import com.sublinks.sublinksapi.api.lemmy.v3.authentication.JwtFilter; -import lombok.RequiredArgsConstructor; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; - -@Configuration -@EnableWebSecurity -@RequiredArgsConstructor -public class SecurityConfig { - - private final JwtFilter jwtFilter; - - @Bean - public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception { - - http - .csrf(AbstractHttpConfigurer::disable) - .authorizeHttpRequests((requests) -> requests - .anyRequest().permitAll() - ) - .sessionManagement( - (sessionManagement) -> sessionManagement.sessionCreationPolicy( - SessionCreationPolicy.STATELESS) - ) - .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); - return http.build(); - } -} diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/common/controllers/AbstractLemmyApiController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/common/controllers/AbstractLemmyApiController.java index f7611edf..611c47a1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/common/controllers/AbstractLemmyApiController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/common/controllers/AbstractLemmyApiController.java @@ -34,7 +34,7 @@ public Person getPersonOrThrowBadRequest(JwtPerson principal) throws ResponseSta * @throws ResponseStatusException Exception thrown when Person not present */ public Person getPersonOrThrow(JwtPerson principal, - Supplier exceptionSupplier) throws X { + Supplier exceptionSupplier) throws X { return Optional.ofNullable(principal).map(p -> (Person) p.getPrincipal()) .orElseThrow(exceptionSupplier); diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/enums/mappers/LemmyListingTypeMapper.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/enums/mappers/ListingTypeMapper.java similarity index 89% rename from src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/enums/mappers/LemmyListingTypeMapper.java rename to src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/enums/mappers/ListingTypeMapper.java index a95c8ecd..877a85d7 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/enums/mappers/LemmyListingTypeMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/enums/mappers/ListingTypeMapper.java @@ -5,7 +5,7 @@ import org.mapstruct.MappingConstants; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING) -public interface LemmyListingTypeMapper { +public interface ListingTypeMapper { ListingType map(com.sublinks.sublinksapi.api.lemmy.v3.enums.ListingType listingType); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/modlog/controllers/ModerationLogController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/modlog/controllers/ModerationLogController.java index 1f24b89b..db93bbff 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/modlog/controllers/ModerationLogController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/modlog/controllers/ModerationLogController.java @@ -54,11 +54,8 @@ public class ModerationLogController extends AbstractLemmyApiController { private final RoleAuthorizingService roleAuthorizingService; @Operation(summary = "Get the modlog.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", - content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, - schema = @Schema(implementation = GetModlogResponse.class))}) - }) + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { + @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = GetModlogResponse.class))})}) @GetMapping GetModlogResponse index(@Valid final GetModLog getModLogForm, final JwtPerson principal) { @@ -88,12 +85,8 @@ GetModlogResponse index(@Valid final GetModLog getModLogForm, final JwtPerson pr final List hidden_communities = new ArrayList<>(); final Page moderationLogs = moderationLogService.searchModerationLogs( - getModLogForm.type_(), - getModLogForm.community_id(), - getModLogForm.mod_person_id(), - getModLogForm.other_person_id(), - getModLogForm.page(), - limit, + getModLogForm.type_(), getModLogForm.community_id(), getModLogForm.mod_person_id(), + getModLogForm.other_person_id(), getModLogForm.page(), limit, Sort.by("createdAt").descending()); for (ModerationLog moderationLog : moderationLogs.getContent()) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostController.java index 5a323974..ef01dee8 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostController.java @@ -324,9 +324,9 @@ ListPostLikesResponse listLikes(@Valid ListPostLikes listPostLikesForm, JwtPerso @ApiResponse(responseCode = "400", description = "Post Not Found", content = { @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ApiError.class))})}) @PutMapping("save") - public PostResponse saveForLater(@Valid @RequestBody SavePost savePostForm, JwtPerson jwtPerson) { + public PostResponse saveForLater(@Valid @RequestBody SavePost savePostForm, JwtPerson JwtPerson) { - final Person person = getPersonOrThrowUnauthorized(jwtPerson); + final Person person = getPersonOrThrowUnauthorized(JwtPerson); roleAuthorizingService.hasAdminOrPermissionOrThrow(person, RolePermission.FAVORITE_POST, () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/search/controllers/SearchController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/search/controllers/SearchController.java index 34bd3a93..285b1e1b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/search/controllers/SearchController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/search/controllers/SearchController.java @@ -60,9 +60,9 @@ public class SearchController extends AbstractLemmyApiController { )}) @GetMapping - SearchResponse search(@Valid final Search searchForm, final JwtPerson jwtPerson) { + SearchResponse search(@Valid final Search searchForm, final JwtPerson JwtPerson) { - final Optional person = getOptionalPerson(jwtPerson); + final Optional person = getOptionalPerson(JwtPerson); roleAuthorizingService.hasAdminOrPermissionOrThrow(person.orElse(null), RolePermission.INSTANCE_SEARCH, diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/user/controllers/UserAuthController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/user/controllers/UserAuthController.java index 58854a12..fa7942ae 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/user/controllers/UserAuthController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/user/controllers/UserAuthController.java @@ -82,7 +82,7 @@ public class UserAuthController extends AbstractLemmyApiController { private static final Logger logger = LoggerFactory.getLogger(UserAuthController.class); - private final JwtUtil jwtUtil; + private final JwtUtil lemmyJwtUtil; private final PersonService personService; private final PersonRepository personRepository; private final LocalInstanceContext localInstanceContext; @@ -146,7 +146,7 @@ LoginResponse create(final HttpServletRequest request, throw new RuntimeException("Passwords do not match"); } personService.createPerson(person); - String token = jwtUtil.generateToken(person); + String token = lemmyJwtUtil.generateToken(person); boolean send_verification_email = false; @@ -154,12 +154,9 @@ LoginResponse create(final HttpServletRequest request, if (instanceConfig.getRegistrationMode() == RegistrationMode.RequireApplication) { personRegistrationApplicationService.createPersonRegistrationApplication( - PersonRegistrationApplication.builder() - .applicationStatus(PersonRegistrationApplicationStatus.pending) - .person(person) - .question(instanceConfig.getRegistrationQuestion()) - .answer(registerForm.answer()) - .build()); + PersonRegistrationApplication.builder().applicationStatus( + PersonRegistrationApplicationStatus.pending).person(person).question( + instanceConfig.getRegistrationQuestion()).answer(registerForm.answer()).build()); token = ""; } @@ -223,11 +220,8 @@ LoginResponse create(final HttpServletRequest request, } person.setEmailVerified(!send_verification_email); personService.updatePerson(person); - return LoginResponse.builder() - .jwt(token) - .registration_created(true) - .verify_email_sent(send_verification_email) - .build(); + return LoginResponse.builder().jwt(token).registration_created(true).verify_email_sent( + send_verification_email).build(); } @Operation(summary = "Fetch a Captcha.") @@ -240,9 +234,8 @@ GetCaptchaResponse captcha() { return GetCaptchaResponse.builder().build(); } Captcha captcha = captchaService.getCaptcha(); - return GetCaptchaResponse.builder() - .ok(conversionService.convert(captcha, CaptchaResponse.class)) - .build(); + return GetCaptchaResponse.builder().ok( + conversionService.convert(captcha, CaptchaResponse.class)).build(); } @Operation(summary = "Log into lemmy.") @@ -254,8 +247,8 @@ GetCaptchaResponse captcha() { LoginResponse login(final HttpServletRequest request, @Valid @RequestBody final Login loginForm) throws LemmyException { - final Person person = personRepository.findOneByName(loginForm.username_or_email()) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED)); + final Person person = personRepository.findOneByName(loginForm.username_or_email()).orElseThrow( + () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED)); // @todo verify password if (person.isDeleted()) { @@ -280,14 +273,13 @@ LoginResponse login(final HttpServletRequest request, @Valid @RequestBody final throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "password_incorrect"); } - final String token = jwtUtil.generateToken(person); + final String token = lemmyJwtUtil.generateToken(person); userDataService.checkAndAddIpRelation(person, request.getRemoteAddr(), token, request.getHeader("User-Agent")); - return LoginResponse.builder() - .jwt(token) - .registration_created(false) // @todo return true if application created + return LoginResponse.builder().jwt(token).registration_created( + false) // @todo return true if application created .verify_email_sent(false) // @todo return true if welcome email sent for verification .build(); } @@ -347,10 +339,9 @@ PasswordResetResponse passwordReset( params.put("person", foundPerson); - String url = localInstanceContext.instance() - .getDomain() - .substring(0, localInstanceContext.instance().getDomain().length() - 4) - + "/password_change/" + passwordReset.getToken(); + String url = localInstanceContext.instance().getDomain().substring(0, + localInstanceContext.instance().getDomain().length() - 4) + "/password_change/" + + passwordReset.getToken(); // #todo: implement the password reset in the frontend params.put("resetUrl", url); @@ -406,7 +397,7 @@ LoginResponse passwordChange(final HttpServletRequest request, personService.updatePerson(person); userDataService.invalidateAllUserData(person); - String token = jwtUtil.generateToken(person); + String token = lemmyJwtUtil.generateToken(person); userDataService.checkAndAddIpRelation(person, request.getRemoteAddr(), token, request.getHeader("User-Agent")); @@ -484,7 +475,7 @@ LoginResponse changePassword(final HttpServletRequest request, personService.updatePerson(person); userDataService.invalidateAllUserData(person); - String token = jwtUtil.generateToken(person); + String token = lemmyJwtUtil.generateToken(person); userDataService.checkAndAddIpRelation(person, request.getRemoteAddr(), token, request.getHeader("User-Agent")); @@ -566,9 +557,7 @@ SuccessResponse validate_auth(final JwtPerson principal) { Optional person = getOptionalPerson(principal); - return SuccessResponse.builder() - .success(person.isPresent()) - .error(person.isPresent() ? null : "not_logged_in") - .build(); + return SuccessResponse.builder().success(person.isPresent()).error( + person.isPresent() ? null : "not_logged_in").build(); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtFilter.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java similarity index 90% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtFilter.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java index 390db21c..fc1e59f8 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtFilter.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java @@ -23,9 +23,9 @@ @Component @RequiredArgsConstructor @Order(1) -public class JwtFilter extends OncePerRequestFilter { +public class SublinksJwtFilter extends OncePerRequestFilter { - private final JwtUtil jwtUtil; + private final SublinksJwtUtil sublinksJwtUtil; private final PersonRepository personRepository; private final UserDataService userDataService; @@ -56,7 +56,7 @@ protected void doFilterInternal(final HttpServletRequest request, } else { token = authorizingToken; } - userName = jwtUtil.extractUsername(token); + userName = sublinksJwtUtil.extractUsername(token); } } catch (ExpiredJwtException | SignatureException ex) { response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "invalid_token"); @@ -68,12 +68,12 @@ protected void doFilterInternal(final HttpServletRequest request, throw new UsernameNotFoundException("Invalid name"); } - if (jwtUtil.validateToken(token, person.get())) { + if (sublinksJwtUtil.validateToken(token, person.get())) { // Add a check if token and ip was changed? To give like a "warning" to the user that he has a new ip logged into his account userDataService.checkAndAddIpRelation(person.get(), request.getRemoteAddr(), token, request.getHeader("User-Agent")); - final JwtPerson authenticationToken = new JwtPerson(person.get(), + final SublinksJwtPerson authenticationToken = new SublinksJwtPerson(person.get(), person.get().getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authenticationToken); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtPerson.java similarity index 75% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtPerson.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtPerson.java index 4f49b78d..d38d9fa9 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtPerson.java @@ -5,11 +5,11 @@ import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; -public class JwtPerson extends AbstractAuthenticationToken { +public class SublinksJwtPerson extends AbstractAuthenticationToken { private final Person person; - public JwtPerson(final Person person, final Collection authorities) { + public SublinksJwtPerson(final Person person, final Collection authorities) { super(authorities); this.person = person; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtUtil.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtUtil.java similarity index 95% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtUtil.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtUtil.java index 2a68cad8..8e9aac68 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/JwtUtil.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtUtil.java @@ -17,14 +17,14 @@ import org.springframework.stereotype.Component; @Component -public class JwtUtil implements Serializable { +public class SublinksJwtUtil implements Serializable { public static final long JWT_TOKEN_VALIDITY = 24 * 60 * 60; @Serial private static final long serialVersionUID = -2550185165626007488L; private final String secret; - public JwtUtil(@Value("${jwt.secret}") final String secret) { + public SublinksJwtUtil(@Value("${jwt.secret}") final String secret) { this.secret = secret; } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SecurityConfig.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SecurityConfig.java index 215a511e..5fb9fd8f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SecurityConfig.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SecurityConfig.java @@ -1,6 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.authentication.config; -import com.sublinks.sublinksapi.api.sublinks.v1.authentication.JwtFilter; +import com.sublinks.sublinksapi.api.lemmy.v3.authentication.JwtFilter; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -16,21 +17,18 @@ @RequiredArgsConstructor public class SecurityConfig { - private final JwtFilter jwtFilter; + private final SublinksJwtFilter sublinksJwtFilter; + private final JwtFilter lemmyJwtFilter; @Bean public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception { - http - .csrf(AbstractHttpConfigurer::disable) - .authorizeHttpRequests((requests) -> requests - .anyRequest().permitAll() - ) - .sessionManagement( - (sessionManagement) -> sessionManagement.sessionCreationPolicy( - SessionCreationPolicy.STATELESS) - ) - .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); + http.csrf(AbstractHttpConfigurer::disable).authorizeHttpRequests( + (requests) -> requests.anyRequest().permitAll()).sessionManagement( + (sessionManagement) -> sessionManagement.sessionCreationPolicy( + SessionCreationPolicy.STATELESS)).addFilterBefore(sublinksJwtFilter, + UsernamePasswordAuthenticationFilter.class).addFilterBefore(lemmyJwtFilter, + UsernamePasswordAuthenticationFilter.class); return http.build(); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/CommentMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/LemmyCommentMapper.java similarity index 66% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/CommentMapper.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/LemmyCommentMapper.java index 202744cf..3b1daf70 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/CommentMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/LemmyCommentMapper.java @@ -1,7 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.comment.mappers; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.person.mappers.PersonMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.person.mappers.SublinksPersonMapper; import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; import com.sublinks.sublinksapi.comment.entities.Comment; import org.mapstruct.Mapper; @@ -10,19 +10,22 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {PersonMapper.class}) -public abstract class CommentMapper implements Converter { +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksPersonMapper.class}) +public abstract class LemmyCommentMapper implements Converter { + + SublinksPersonMapper personMapper; @Override @Mapping(target = "key", source = "comment.path") @Mapping(target = "activityPubId", source = "comment.activityPubId") @Mapping(target = "body", source = "comment.commentBody") @Mapping(target = "path", source = "comment.path") - @Mapping(target = "isLocal", source = "comment.isLocal") - @Mapping(target = "isDeleted", source = "comment.isDeleted") - @Mapping(target = "isFeatured", source = "comment.isFeatured") + @Mapping(target = "isLocal", source = "comment.local") + @Mapping(target = "isDeleted", source = "comment.deleted") + @Mapping(target = "isFeatured", source = "comment.featured") @Mapping(target = "isRemoved", expression = "java(comment.isRemoved())") - @Mapping(target = "creator", expression = "java(personMapper.convert(comment.getCreator()))") + // @Mapping(target = "creator", expression = "java(personMapper.convert(comment.getPerson()))") + @Mapping(target = "creator", source = "comment.person") @Mapping(target = "createdAt", source = "comment.createdAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) @Mapping(target = "updatedAt", source = "comment.updatedAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) public abstract CommentResponse convert(@Nullable Comment comment); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index 26d1bb35..c337cd49 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -6,6 +6,7 @@ import com.sublinks.sublinksapi.comment.repositories.CommentRepository; import com.sublinks.sublinksapi.comment.services.CommentService; import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; +import com.sublinks.sublinksapi.language.entities.Language; import com.sublinks.sublinksapi.language.repositories.LanguageRepository; import com.sublinks.sublinksapi.person.entities.Person; import com.sublinks.sublinksapi.post.entities.Post; @@ -29,18 +30,26 @@ public class SublinksCommentService { public CommentResponse createComment(CreateComment createComment, Person person) { final Post parentPost = postRepository.findByTitleSlugAndRemovedStateIs(createComment.postKey(), - RemovedState.NOT_REMOVED).orElseThrow( - () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "post_not_found")); + RemovedState.NOT_REMOVED) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "post_not_found")); final Comment parentComment = commentRepository.findByPath(createComment.parentKey()) .orElseThrow( () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); + Language language; + try { + language = languageRepository.findLanguageByCode(createComment.languageKey() + .orElse("und")); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "language_not_found"); + } Comment comment = Comment.builder() .commentBody(createComment.body()) .person(person) .post(parentPost) .path(parentComment.getPath()) + .language(language) .build(); commentService.createComment(comment, parentComment); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/controllers/AbstractSublinksApiController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/controllers/AbstractSublinksApiController.java index 58423dbf..44831e41 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/controllers/AbstractSublinksApiController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/controllers/AbstractSublinksApiController.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.common.controllers; -import com.sublinks.sublinksapi.api.sublinks.v1.authentication.JwtPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.person.entities.Person; import java.util.Optional; import java.util.function.Supplier; @@ -16,7 +16,7 @@ public abstract class AbstractSublinksApiController { * @return Person * @throws ResponseStatusException Exception thrown when Person not present */ - public Person getPersonOrThrowBadRequest(JwtPerson principal) throws ResponseStatusException { + public Person getPersonOrThrowBadRequest(SublinksJwtPerson principal) throws ResponseStatusException { return Optional.ofNullable(principal).map(p -> (Person) p.getPrincipal()).orElseThrow( () -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); @@ -29,8 +29,8 @@ public Person getPersonOrThrowBadRequest(JwtPerson principal) throws ResponseSta * @return Person * @throws ResponseStatusException Exception thrown when Person not present */ - public Person getPersonOrThrow(JwtPerson principal, - Supplier exceptionSupplier) throws X { + public Person getPersonOrThrow(SublinksJwtPerson principal, + Supplier exceptionSupplier) throws X { return Optional.ofNullable(principal).map(p -> (Person) p.getPrincipal()).orElseThrow( exceptionSupplier); @@ -44,18 +44,18 @@ public Person getPersonOrThrow(JwtPerson principal, * @return Person * @throws ResponseStatusException Exception thrown when Person not present */ - public Person getPersonOrThrowUnauthorized(JwtPerson principal) throws ResponseStatusException { + public Person getPersonOrThrowUnauthorized(SublinksJwtPerson principal) throws ResponseStatusException { return Optional.ofNullable(principal).map(p -> (Person) p.getPrincipal()).orElseThrow( () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED)); } - public Person getPerson(JwtPerson principal) { + public Person getPerson(SublinksJwtPerson principal) { return getOptionalPerson(principal).orElse(null); } - public Optional getOptionalPerson(JwtPerson principal) { + public Optional getOptionalPerson(SublinksJwtPerson principal) { return Optional.ofNullable(principal).map(p -> (Person) p.getPrincipal()); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmySortTypeMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/SublinksSortTypeMapper.java similarity index 93% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmySortTypeMapper.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/SublinksSortTypeMapper.java index 9d66e3da..aee77a82 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmySortTypeMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/SublinksSortTypeMapper.java @@ -7,7 +7,7 @@ import org.springframework.lang.Nullable; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING) -public class LemmySortTypeMapper implements +public class SublinksSortTypeMapper implements Converter { @Nullable diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/CommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/CommunityController.java index 4f8ca1a4..32ae6d75 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/CommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/CommunityController.java @@ -2,7 +2,7 @@ import com.sublinks.sublinksapi.api.lemmy.v3.enums.ListingType; import com.sublinks.sublinksapi.api.lemmy.v3.enums.SortType; -import com.sublinks.sublinksapi.api.sublinks.v1.authentication.JwtPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; @@ -44,9 +44,9 @@ public class CommunityController extends AbstractSublinksApiController { @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public List index(@Valid final IndexCommunity indexCommunityForm, - final JwtPerson jwtPerson) { + final SublinksJwtPerson sublinksJwtPerson) { - final Optional person = getOptionalPerson(jwtPerson); + final Optional person = getOptionalPerson(sublinksJwtPerson); return communityRepository.allCommunitiesBySearchCriteria( CommunitySearchCriteria.builder() @@ -81,9 +81,9 @@ public CommunityResponse show(@PathVariable final String key) { @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse create(@RequestBody @Valid final CreateCommunity createCommunity, - final JwtPerson jwtPerson) { + final SublinksJwtPerson sublinksJwtPerson) { - final Person person = getPersonOrThrowUnauthorized(jwtPerson); + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); return sublinksCommunityService.createCommunity(createCommunity, person); } @@ -93,7 +93,7 @@ public CommunityResponse create(@RequestBody @Valid final CreateCommunity create @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse update(@PathVariable String key, - @RequestBody @Valid UpdateCommunity updateCommunityForm, final JwtPerson principal) { + @RequestBody @Valid UpdateCommunity updateCommunityForm, final SublinksJwtPerson principal) { final Person person = getPersonOrThrowUnauthorized(principal); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/CommunityMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityMapper.java similarity index 61% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/CommunityMapper.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityMapper.java index 0409233a..341bb6c1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/CommunityMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityMapper.java @@ -1,8 +1,9 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.mappers; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.languages.mappers.LanguageMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.languages.mappers.SublinksLanguageMapper; import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; +import com.sublinks.sublinksapi.api.sublinks.v1.utils.mappers.OptionalStringMapper; import com.sublinks.sublinksapi.community.entities.Community; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @@ -10,8 +11,11 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {LanguageMapper.class}) -public abstract class CommunityMapper implements Converter { +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = { + SublinksLanguageMapper.class, OptionalStringMapper.class}) +public abstract class SublinksCommunityMapper implements Converter { + + SublinksLanguageMapper languageMapper; @Override @Mapping(target = "key", source = "community.titleSlug") @@ -20,13 +24,13 @@ public abstract class CommunityMapper implements Converter { +public abstract class SublinksLanguageMapper implements Converter { @Override @Mapping(target = "key", source = "language.name") @Mapping(target = "name", source = "language.name") @Mapping(target = "code", source = "language.code") - public abstract LangaugeResponse convert(@Nullable Language language); + public abstract LanguageResponse convert(@Nullable Language language); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java index 46ba2fc8..8bb5cdfb 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.controllers; -import com.sublinks.sublinksapi.api.sublinks.v1.authentication.JwtPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.CreatePerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.IndexPerson; @@ -90,7 +90,7 @@ public LoginResponse login(final HttpServletRequest request, @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public PersonResponse update(@PathVariable String key, - @RequestBody @Valid final UpdatePerson updatePersonForm, final JwtPerson principal) { + @RequestBody @Valid final UpdatePerson updatePersonForm, final SublinksJwtPerson principal) { final Person person = getPersonOrThrowUnauthorized(principal); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java similarity index 65% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java index 8bf4ab91..9ba3ad2d 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/PersonMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java @@ -1,10 +1,12 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.mappers; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.RoleMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksRoleMapper; import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; import com.sublinks.sublinksapi.authorization.services.RoleAuthorizingService; import com.sublinks.sublinksapi.person.entities.Person; +import java.text.SimpleDateFormat; +import java.util.Optional; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; @@ -12,20 +14,24 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {RoleMapper.class}) -public abstract class PersonMapper implements Converter { +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksRoleMapper.class}) +public abstract class SublinksPersonMapper implements Converter { + + SublinksRoleMapper roleMapper; @Override @Mapping(target = "key", source = "person.name") @Mapping(target = "name", source = "person.name") @Mapping(target = "displayName", source = "person", qualifiedByName = "display_name") - @Mapping(target = "avatar", source = "person", qualifiedByName = "avatar") - @Mapping(target = "banner", source = "person", qualifiedByName = "banner") @Mapping(target = "isBanned", source = "person", qualifiedByName = "is_banned") - @Mapping(target = "banExpiresAt", source = "person.role.expiresAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "banExpiresAt", source = "person", qualifiedByName = "banExpiresAt") @Mapping(target = "isDeleted", source = "person.deleted") + @Mapping(target = "avatarImageUrl", source = "person", qualifiedByName = "avatar") + @Mapping(target = "bannerImageUrl", source = "person", qualifiedByName = "banner") @Mapping(target = "isBotAccount", source = "person.botAccount") @Mapping(target = "role", expression = "java(roleMapper.convert(person.getRole()))") + @Mapping(target = "bio", source = "person.biography") + @Mapping(target = "isLocal", source = "person.local") @Mapping(target = "createdAt", source = "person.createdAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) @Mapping(target = "updatedAt", source = "person.updatedAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) public abstract PersonResponse convert(@Nullable Person person); @@ -53,4 +59,15 @@ String mapBanner(Person person) { return !person.getBannerImageUrl().isBlank() ? person.getBannerImageUrl() : null; } + + @Named("banExpiresAt") + Optional mapBanExpiresAt(Person person) { + + SimpleDateFormat simpleDateFormat = new SimpleDateFormat(); + + simpleDateFormat.applyPattern(DateUtils.FRONT_END_DATE_FORMAT); + + return Optional.ofNullable(person.getRole().getExpiresAt() != null ? simpleDateFormat.format( + person.getRole().getExpiresAt()) : null); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index 18b343c6..d5f02afb 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -1,7 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.services; import com.sublinks.sublinksapi.api.lemmy.v3.enums.RegistrationMode; -import com.sublinks.sublinksapi.api.sublinks.v1.authentication.JwtUtil; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtUtil; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.CreatePerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginResponse; @@ -38,7 +38,7 @@ public class SublinksPersonService { private final PersonService personService; - private final JwtUtil jwtUtil; + private final SublinksJwtUtil sublinksJwtUtil; private final PersonRepository personRepository; private final LocalInstanceContext localInstanceContext; private final EmailService emailService; @@ -72,7 +72,7 @@ public LoginResponse registerPerson(final CreatePerson createPersonForm, final S personService.createPerson(person); - String token = jwtUtil.generateToken(person); + String token = sublinksJwtUtil.generateToken(person); RegistrationState status = RegistrationState.CREATED; if (instanceConfig != null && instanceConfig.isRequireEmailVerification()) { @@ -178,7 +178,7 @@ public LoginResponse login(final LoginPerson loginPersonForm, final String ip, .build(); } - final String token = jwtUtil.generateToken(person); + final String token = sublinksJwtUtil.generateToken(person); userDataService.checkAndAddIpRelation(person, ip, token, userAgent); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/mappers/PrivateMessageMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/mappers/SublinksPrivateMessageMapper.java similarity index 68% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/mappers/PrivateMessageMapper.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/mappers/SublinksPrivateMessageMapper.java index fd4826af..3f5ca299 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/mappers/PrivateMessageMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/mappers/SublinksPrivateMessageMapper.java @@ -1,6 +1,6 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.mappers;https://github.com/sublinks/sublinks-docker +package com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.mappers; -import com.sublinks.sublinksapi.api.sublinks.v1.person.mappers.PersonMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.person.mappers.SublinksPersonMapper; import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.PrivateMessageResponse; import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; import com.sublinks.sublinksapi.privatemessages.entities.PrivateMessage; @@ -10,17 +10,22 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {PersonMapper.class}) -public abstract class PrivateMessageMapper implements +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksPersonMapper.class}) +public abstract class SublinksPrivateMessageMapper implements Converter { + SublinksPersonMapper sublinksPersonMapper; + @Override @Mapping(target = "key", source = "privateMessage.id") @Mapping(target = "content", source = "privateMessage.content") @Mapping(target = "isLocal", source = "privateMessage.local") @Mapping(target = "isDeleted", source = "privateMessage.deleted") - @Mapping(target = "sender", source = "privateMessage.sender", expression = "java(personMapper.convert(privateMessage.getSender()))") - @Mapping(target = "recipient", source = "privateMessage.recipient", expression = "java(personMapper.convert(privateMessage.getRecipient()))") + @Mapping(target = "isRead", source = "privateMessage.read") + //@Mapping(target = "sender", expression = "java(personMapper.convert(privateMessage.getSender()))") + //@Mapping(target = "recipient", expression = "java(personMapper.convert(privateMessage.getRecipient()))") + @Mapping(target = "sender", source = "privateMessage.sender") + @Mapping(target = "recipient", source = "privateMessage.recipient") @Mapping(target = "createdAt", source = "privateMessage.createdAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) @Mapping(target = "updatedAt", source = "privateMessage.updatedAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) public abstract PrivateMessageResponse convert(@Nullable PrivateMessage privateMessage); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/RoleMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java similarity index 86% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/RoleMapper.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java index 330968d2..bf048a97 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/RoleMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java @@ -13,18 +13,18 @@ @Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = { RoleAuthorizingService.class}) -public abstract class RoleMapper implements +public abstract class SublinksRoleMapper implements Converter { @Override @Mapping(target = "key", source = "role.name") @Mapping(target = "name", source = "role.name") @Mapping(target = "description", source = "role.description") - @Mapping(target = "isActive", source = "role.isActive") + @Mapping(target = "isActive", source = "role.active") @Mapping(target = "isExpired", source = "role", qualifiedByName = "is_expired") @Mapping(target = "expiresAt", source = "role.expiresAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) @Mapping(target = "createdAt", source = "role.createdAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) - @Mapping(target = "updatedAt", source = "role.updaetedAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "updatedAt", source = "role.updatedAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) public abstract com.sublinks.sublinksapi.api.sublinks.v1.roles.models.Role convert( @Nullable Role role); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/mappers/OptionalStringMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/mappers/OptionalStringMapper.java new file mode 100644 index 00000000..9f3192c9 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/mappers/OptionalStringMapper.java @@ -0,0 +1,19 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.utils.mappers; + +import com.sublinks.sublinksapi.api.sublinks.v1.languages.mappers.SublinksLanguageMapper; +import java.util.Optional; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksLanguageMapper.class}) +public class OptionalStringMapper implements Converter> { + + @Nullable + @Override + public Optional convert(@Nullable String source) { + + return Optional.ofNullable(source); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/comment/entities/Comment.java b/src/main/java/com/sublinks/sublinksapi/comment/entities/Comment.java index c2d6a61f..2724e7d0 100644 --- a/src/main/java/com/sublinks/sublinksapi/comment/entities/Comment.java +++ b/src/main/java/com/sublinks/sublinksapi/comment/entities/Comment.java @@ -131,7 +131,8 @@ public final boolean equals(Object o) { public final int hashCode() { return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer() - .getPersistentClass().hashCode() : getClass().hashCode(); + .getPersistentClass() + .hashCode() : getClass().hashCode(); } public boolean isRemoved() { diff --git a/src/main/java/com/sublinks/sublinksapi/language/entities/Language.java b/src/main/java/com/sublinks/sublinksapi/language/entities/Language.java index ef914703..af1fef18 100644 --- a/src/main/java/com/sublinks/sublinksapi/language/entities/Language.java +++ b/src/main/java/com/sublinks/sublinksapi/language/entities/Language.java @@ -3,6 +3,7 @@ import com.sublinks.sublinksapi.community.entities.Community; import com.sublinks.sublinksapi.instance.entities.Instance; import com.sublinks.sublinksapi.person.entities.Person; +import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; @@ -27,7 +28,7 @@ @NoArgsConstructor @Builder @Entity -@Table(name = "languagesKeys") +@Table(name = "languages") public class Language { /** @@ -52,8 +53,10 @@ public class Language { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(unique = true) private String code; + @Column(unique = true) private String name; @Override From ce6b5136ec0eb0a1cfb24d8208e9e80e71e7e4d1 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Sun, 5 May 2024 21:01:48 +0200 Subject: [PATCH 013/115] Refactor code for readability and consistency Removed irrelevant code block in PersonService and improved formatting across various methods in CommunityModActionsController and PostController to improve readability and maintain consistency. Arranged method calls and parameters to make the code cleaner and easier to understand. --- .../CommunityModActionsController.java | 95 ++++++------ .../v3/post/controllers/PostController.java | 42 ++--- .../person/services/PersonService.java | 145 +++--------------- 3 files changed, 97 insertions(+), 185 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java index 332e9cf0..aa71b748 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java @@ -80,12 +80,12 @@ public class CommunityModActionsController extends AbstractLemmyApiController { @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = CommunityResponse.class))})}) @PutMapping("hide") CommunityResponse hide(@Valid @RequestBody final HideCommunity hideCommunityForm, - JwtPerson principal) { + JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); - rolePermissionService.isPermitted(person, - RolePermissionCommunityTypes.ADMIN_REMOVE_COMMUNITY, + rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.ADMIN_REMOVE_COMMUNITY, () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); final Community community = communityRepository.findById(hideCommunityForm.community_id()) .orElseThrow( @@ -106,8 +106,9 @@ CommunityResponse hide(@Valid @RequestBody final HideCommunity hideCommunityForm .build(); moderationLogService.createModerationLog(moderationLog); - return CommunityResponse.builder().community_view( - lemmyCommunityService.communityViewFromCommunity(community)).build(); + return CommunityResponse.builder() + .community_view(lemmyCommunityService.communityViewFromCommunity(community)) + .build(); } @Operation(summary = "Delete a community.") @@ -119,24 +120,16 @@ CommunityResponse delete(@Valid final DeleteCommunity deleteCommunityForm, JwtPe final Person person = getPersonOrThrowUnauthorized(principal); final Community community = communityRepository.findById( - (long) deleteCommunityForm.community_id()).orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + (long) deleteCommunityForm.community_id()) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - if (!linkPersonCommunityService.hasLink(person, community, LinkPersonCommunityType.owner)) { - roleAuthorizingService.hasAdminOrPermissionOrThrow(person, RolePermission.DELETE_COMMUNITY, - () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); - } rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.DELETE_COMMUNITY, () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); RolePermissionService.isAdminElseThrow(person, () -> new ResponseStatusException(HttpStatus.FORBIDDEN)); - final Community community = communityRepository.findById( - (long) deleteCommunityForm.community_id()) - .orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - community.setDeleted(deleteCommunityForm.deleted()); communityRepository.save(community); @@ -151,8 +144,9 @@ CommunityResponse delete(@Valid final DeleteCommunity deleteCommunityForm, JwtPe .build(); moderationLogService.createModerationLog(moderationLog); - return CommunityResponse.builder().community_view( - lemmyCommunityService.communityViewFromCommunity(community)).build(); + return CommunityResponse.builder() + .community_view(lemmyCommunityService.communityViewFromCommunity(community)) + .build(); } @Operation(summary = "A moderator remove for a community.") @@ -160,7 +154,8 @@ CommunityResponse delete(@Valid final DeleteCommunity deleteCommunityForm, JwtPe @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = CommunityResponse.class))})}) @PostMapping("remove") CommunityResponse remove(@Valid @RequestBody final RemoveCommunity removeCommunityForm, - JwtPerson principal) { + JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -169,8 +164,9 @@ CommunityResponse remove(@Valid @RequestBody final RemoveCommunity removeCommuni () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); final Community community = communityRepository.findById( - (long) removeCommunityForm.community_id()).orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + (long) removeCommunityForm.community_id()) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); if (!linkPersonCommunityService.hasAnyLink(person, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { @@ -192,8 +188,9 @@ CommunityResponse remove(@Valid @RequestBody final RemoveCommunity removeCommuni .build(); moderationLogService.createModerationLog(moderationLog); - return CommunityResponse.builder().community_view( - lemmyCommunityService.communityViewFromCommunity(community)).build(); + return CommunityResponse.builder() + .community_view(lemmyCommunityService.communityViewFromCommunity(community)) + .build(); } @Operation(summary = "Transfer your community to an existing moderator.") @@ -201,7 +198,8 @@ CommunityResponse remove(@Valid @RequestBody final RemoveCommunity removeCommuni @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = GetCommunityResponse.class))})}) @PostMapping("transfer") GetCommunityResponse transfer(@Valid @RequestBody final TransferCommunity transferCommunityForm, - JwtPerson principal) { + JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -210,16 +208,12 @@ GetCommunityResponse transfer(@Valid @RequestBody final TransferCommunity transf () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); final Community community = communityRepository.findById( - (long) transferCommunityForm.community_id()).orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + (long) transferCommunityForm.community_id()) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); if (!linkPersonCommunityService.hasLinkOrAdmin(person, community, LinkPersonCommunityType.owner)) { - final boolean isAllowed = - RolePermissionService.isAdmin(person) || linkPersonCommunityService.hasLink(person, - community, LinkPersonCommunityType.owner); - - if (!isAllowed) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_allowed"); } @@ -232,8 +226,10 @@ GetCommunityResponse transfer(@Valid @RequestBody final TransferCommunity transf } final Person oldOwner = linkPersonCommunityService.getPersonsFromCommunityAndListTypes( - community, List.of(LinkPersonCommunityType.owner)).stream().findFirst().orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "owner_not_found")); + community, List.of(LinkPersonCommunityType.owner)) + .stream() + .findFirst() + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "owner_not_found")); linkPersonCommunityService.addLink(oldOwner, community, LinkPersonCommunityType.moderator); linkPersonCommunityService.removeLink(oldOwner, community, LinkPersonCommunityType.owner); @@ -251,8 +247,9 @@ GetCommunityResponse transfer(@Valid @RequestBody final TransferCommunity transf .build(); moderationLogService.createModerationLog(moderationLog); - return GetCommunityResponse.builder().community_view( - lemmyCommunityService.communityViewFromCommunity(community)).build(); + return GetCommunityResponse.builder() + .community_view(lemmyCommunityService.communityViewFromCommunity(community)) + .build(); } @Operation(summary = "Ban a user from a community.") @@ -260,7 +257,8 @@ GetCommunityResponse transfer(@Valid @RequestBody final TransferCommunity transf @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = BanFromCommunityResponse.class))})}) @PostMapping("ban_user") BanFromCommunityResponse banUser(@Valid @RequestBody final BanFromCommunity banPersonForm, - JwtPerson principal) { + JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -318,8 +316,10 @@ BanFromCommunityResponse banUser(@Valid @RequestBody final BanFromCommunity banP .build(); moderationLogService.createModerationLog(moderationLog); - return BanFromCommunityResponse.builder().banned(banPersonForm.ban()).person_view( - lemmyPersonService.getPersonView(personToBan)).build(); + return BanFromCommunityResponse.builder() + .banned(banPersonForm.ban()) + .person_view(lemmyPersonService.getPersonView(personToBan)) + .build(); } @Operation(summary = "Add a moderator to your community.") @@ -327,7 +327,8 @@ BanFromCommunityResponse banUser(@Valid @RequestBody final BanFromCommunity banP @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = AddModToCommunityResponse.class))})}) @PostMapping("mod") AddModToCommunityResponse addMod(@Valid @RequestBody AddModToCommunity addModToCommunityForm, - JwtPerson principal) { + JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -337,8 +338,9 @@ AddModToCommunityResponse addMod(@Valid @RequestBody AddModToCommunity addModToC () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); final Community community = communityRepository.findById( - (long) addModToCommunityForm.community_id()).orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + (long) addModToCommunityForm.community_id()) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); final boolean isAllowed = linkPersonCommunityService.hasLink(person, community, LinkPersonCommunityType.moderator) || linkPersonCommunityService.hasLink(person, community, @@ -368,13 +370,14 @@ AddModToCommunityResponse addMod(@Valid @RequestBody AddModToCommunity addModToC Collection moderators = linkPersonCommunityService.getPersonsFromCommunityAndListTypes( community, List.of(LinkPersonCommunityType.moderator)); - List moderatorsView = moderators.stream().map( - moderator -> CommunityModeratorView.builder() + List moderatorsView = moderators.stream() + .map(moderator -> CommunityModeratorView.builder() .moderator(conversionService.convert(moderator, com.sublinks.sublinksapi.api.lemmy.v3.user.models.Person.class)) .community(conversionService.convert(community, com.sublinks.sublinksapi.api.lemmy.v3.community.models.Community.class)) - .build()).toList(); + .build()) + .toList(); // Create Moderation Log ModerationLog moderationLog = ModerationLog.builder() @@ -388,6 +391,8 @@ AddModToCommunityResponse addMod(@Valid @RequestBody AddModToCommunity addModToC .build(); moderationLogService.createModerationLog(moderationLog); - return AddModToCommunityResponse.builder().moderators(moderatorsView).build(); + return AddModToCommunityResponse.builder() + .moderators(moderatorsView) + .build(); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostController.java index 12c6e58b..6fec0675 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostController.java @@ -117,8 +117,7 @@ GetPostResponse show(@Valid final GetPost getPostForm, final JwtPerson principal Optional person = getOptionalPerson(principal); - rolePermissionService.isPermitted(person.orElse(null), - RolePermissionPostTypes.READ_POST, + rolePermissionService.isPermitted(person.orElse(null), RolePermissionPostTypes.READ_POST, () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); PostView postView; @@ -134,8 +133,10 @@ GetPostResponse show(@Valid final GetPost getPostForm, final JwtPerson principal final List moderators = lemmyCommunityService.communityModeratorViewList( community); Set crossPosts = new LinkedHashSet<>(); - if (post.getCrossPost() != null && post.getCrossPost().getPosts() != null) { - for (Post crossPostPost : post.getCrossPost().getPosts()) { + if (post.getCrossPost() != null && post.getCrossPost() + .getPosts() != null) { + for (Post crossPostPost : post.getCrossPost() + .getPosts()) { if (post.equals(crossPostPost)) { continue; } @@ -162,7 +163,8 @@ GetPostResponse show(@Valid final GetPost getPostForm, final JwtPerson principal @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ApiError.class))})}) @PostMapping("mark_as_read") PostResponse markAsRead(@Valid @RequestBody final MarkPostAsRead markPostAsReadForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person person = getPersonOrThrowBadRequest(principal); @@ -187,8 +189,7 @@ public GetPostsResponse index(@Valid final GetPosts getPostsForm, final JwtPerso final Optional person = getOptionalPerson(principal); - rolePermissionService.isPermitted(person.orElse(null), - RolePermissionPostTypes.READ_POSTS, + rolePermissionService.isPermitted(person.orElse(null), RolePermissionPostTypes.READ_POSTS, () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); List communityIds = null; @@ -205,12 +206,14 @@ public GetPostsResponse index(@Valid final GetPosts getPostsForm, final JwtPerso if (person.isPresent() && getPostsForm.type_() != null) { switch (getPostsForm.type_()) { case Subscribed -> { - final Set personCommunities = person.get().getLinkPersonCommunity(); + final Set personCommunities = person.get() + .getLinkPersonCommunity(); if (!personCommunities.isEmpty()) { communityIds = new ArrayList<>(); for (LinkPersonCommunity l : personCommunities) { if (l.getLinkType() == LinkPersonCommunityType.follower) { - communityIds.add(l.getCommunity().getId()); + communityIds.add(l.getCommunity() + .getId()); } } } @@ -228,7 +231,8 @@ public GetPostsResponse index(@Valid final GetPosts getPostsForm, final JwtPerso } } - InstanceConfig config = localInstanceContext.instance().getInstanceConfig(); + InstanceConfig config = localInstanceContext.instance() + .getInstanceConfig(); SortType sortType = null; // @todo set to site default if (getPostsForm.sort() != null) { @@ -251,10 +255,8 @@ public GetPostsResponse index(@Valid final GetPosts getPostsForm, final JwtPerso final PostSearchCriteria postSearchCriteria = PostSearchCriteria.builder() .page(page) - .listingType(conversionService.convert( - listingType, - com.sublinks.sublinksapi.person.enums.ListingType.class) - ) + .listingType(conversionService.convert(listingType, + com.sublinks.sublinksapi.person.enums.ListingType.class)) .perPage(perPage + 1) .isSavedOnly(getPostsForm.saved_only() != null && getPostsForm.saved_only()) .isDislikedOnly(getPostsForm.disliked_only() != null && getPostsForm.disliked_only()) @@ -345,7 +347,9 @@ ListPostLikesResponse listLikes(@Valid ListPostLikes listPostLikesForm, JwtPerso voteViews.add(conversionService.convert(like, VoteView.class)); } - return ListPostLikesResponse.builder().post_likes(voteViews).build(); + return ListPostLikesResponse.builder() + .post_likes(voteViews) + .build(); } @Operation(summary = "Save a post.") @@ -354,10 +358,11 @@ ListPostLikesResponse listLikes(@Valid ListPostLikes listPostLikesForm, JwtPerso @ApiResponse(responseCode = "400", description = "Post Not Found", content = { @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ApiError.class))})}) @PutMapping("save") - public PostResponse saveForLater(@Valid @RequestBody SavePost savePostForm, JwtPerson JwtPerson) { + public PostResponse saveForLater(@Valid @RequestBody SavePost savePostForm, JwtPerson principal) { - final Person person = getPersonOrThrowUnauthorized(jwtPerson); + final Person person = getPersonOrThrowUnauthorized(principal); rolePermissionService.isPermitted(person, RolePermissionPostTypes.FAVORITE_POST, + () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); final Post post = postRepository.findById((long) savePostForm.post_id()) .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); @@ -379,7 +384,8 @@ public PostResponse saveForLater(@Valid @RequestBody SavePost savePostForm, JwtP @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ResponseStatusException.class))})}) @PostMapping("report") PostReportResponse report(@Valid @RequestBody final CreatePostReport createPostReportForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); diff --git a/src/main/java/com/sublinks/sublinksapi/person/services/PersonService.java b/src/main/java/com/sublinks/sublinksapi/person/services/PersonService.java index bdb4472e..002c7fc7 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/services/PersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/person/services/PersonService.java @@ -53,113 +53,6 @@ public class PersonService { private final PersonDeletedPublisher personDeletedPublisher; private final RoleService roleService; - public Set generateInitialRoles() { - - Role adminRole = roleRepository.save(Role.builder().description("Admin role for admins").name( - "Admin").isActive(true).build()); - - adminRole.setRolePermissions(Collections.singleton(rolePermissionsRepository.save( - com.sublinks.sublinksapi.authorization.entities.RolePermissions.builder() - .role(adminRole) - .permission(RolePermission.ADMIN) - .build()))); - - Set rolePermissions = new HashSet<>(); - rolePermissions.add(RolePermission.BANNED); - rolePermissions.add(RolePermission.READ_PRIVATE_MESSAGES); - rolePermissions.add(RolePermission.READ_POST); - rolePermissions.add(RolePermission.READ_POSTS); - rolePermissions.add(RolePermission.READ_COMMENT); - rolePermissions.add(RolePermission.READ_COMMUNITY); - rolePermissions.add(RolePermission.READ_COMMUNITIES); - rolePermissions.add(RolePermission.READ_USER); - rolePermissions.add(RolePermission.READ_MODLOG); - Role bannedRole = roleRepository.save(Role.builder() - .description("Banned role for banned users") - .name("Banned") - .isActive(true) - .build()); - - bannedRole.setRolePermissions(rolePermissions.stream() - .map(rolePermission -> rolePermissionsRepository.save(RolePermissions.builder().role( - bannedRole).permission(rolePermission).build())) - .collect(Collectors.toSet())); - - rolePermissions.remove(RolePermission.BANNED); - rolePermissions.add(RolePermission.DEFAULT); - - Role defaultUserRole = roleRepository.save(Role.builder().description( - "Default role for all users").name("User").isActive(true).build()); - - defaultUserRole.setRolePermissions(rolePermissions.stream() - .map(rolePermission -> rolePermissionsRepository.save(RolePermissions.builder().role( - defaultUserRole).permission(rolePermission).build())) - .collect(Collectors.toSet())); - - rolePermissions.remove(RolePermission.DEFAULT); - rolePermissions.add(RolePermission.REGISTERED); - - rolePermissions.add(RolePermission.POST_UPVOTE); - rolePermissions.add(RolePermission.POST_DOWNVOTE); - rolePermissions.add(RolePermission.POST_NEUTRALVOTE); - rolePermissions.add(RolePermission.COMMENT_UPVOTE); - rolePermissions.add(RolePermission.COMMENT_DOWNVOTE); - rolePermissions.add(RolePermission.COMMENT_NEUTRALVOTE); - - rolePermissions.add(RolePermission.CREATE_PRIVATE_MESSAGE); - rolePermissions.add(RolePermission.UPDATE_PRIVATE_MESSAGE); - rolePermissions.add(RolePermission.DELETE_PRIVATE_MESSAGE); - - rolePermissions.add(RolePermission.CREATE_COMMUNITY); - rolePermissions.add(RolePermission.UPDATE_COMMUNITY); - rolePermissions.add(RolePermission.DELETE_COMMUNITY); - - rolePermissions.add(RolePermission.CREATE_POST); - rolePermissions.add(RolePermission.UPDATE_POST); - rolePermissions.add(RolePermission.DELETE_POST); - - rolePermissions.add(RolePermission.CREATE_COMMENT); - rolePermissions.add(RolePermission.UPDATE_COMMENT); - rolePermissions.add(RolePermission.DELETE_COMMENT); - - rolePermissions.add(RolePermission.UPDATE_USER_SETTINGS); - rolePermissions.add(RolePermission.RESET_PASSWORD); - - rolePermissions.add(RolePermission.MODERATOR_REMOVE_POST); - rolePermissions.add(RolePermission.MODERATOR_REMOVE_COMMENT); - rolePermissions.add(RolePermission.MODERATOR_REMOVE_COMMUNITY); - rolePermissions.add(RolePermission.MODERATOR_BAN_USER); - rolePermissions.add(RolePermission.MODERATOR_SPEAK); - rolePermissions.add(RolePermission.MODERATOR_SHOW_DELETED_COMMENT); - rolePermissions.add(RolePermission.MODERATOR_SHOW_DELETED_POST); - rolePermissions.add(RolePermission.MODERATOR_ADD_MODERATOR); - rolePermissions.add(RolePermission.MODERATOR_REMOVE_MODERATOR); - rolePermissions.add(RolePermission.MODERATOR_PIN_POST); - rolePermissions.add(RolePermission.MODERATOR_TRANSFER_COMMUNITY); - - rolePermissions.add(RolePermission.COMMUNITY_FOLLOW); - rolePermissions.add(RolePermission.COMMUNITY_BLOCK); - - rolePermissions.add(RolePermission.USER_BLOCK); - rolePermissions.add(RolePermission.DELETE_USER); - - rolePermissions.add(RolePermission.REPORT_COMMENT); - rolePermissions.add(RolePermission.REPORT_POST); - rolePermissions.add(RolePermission.REPORT_USER); - rolePermissions.add(RolePermission.REPORT_COMMUNITY); - rolePermissions.add(RolePermission.REPORT_PRIVATE_MESSAGE); - - rolePermissions.add(RolePermission.REPORT_COMMUNITY_RESOLVE); - rolePermissions.add(RolePermission.REPORT_COMMUNITY_READ); - - Role registeredUserRole = roleRepository.save(Role.builder().description( - "Default Role for all registered users").name("Registered").isActive(true).build()); - - registeredUserRole.setRolePermissions(rolePermissions.stream() - .map(rolePermission -> rolePermissionsRepository.save(RolePermissions.builder().role( - registeredUserRole).permission(rolePermission).build())) - .collect(Collectors.toSet())); - /** * Retrieves the default language for posting for a given person and community. * @@ -170,10 +63,12 @@ public Set generateInitialRoles() { */ @Transactional public Optional getPersonDefaultPostLanguage(final Person person, - final Community community) { + final Community community) + { for (Language language : person.getLanguages()) { - if (community.getLanguages().contains(language)) { + if (community.getLanguages() + .contains(language)) { return Optional.of(language); } } @@ -211,28 +106,34 @@ public void createPerson(final Person person) { person.setLocal(true); person.setEmail(person.getEmail()); // @todo: add email verification and send verification email on registration - person.setEmailVerified(localInstanceContext.instance().getInstanceConfig() == null - || !localInstanceContext.instance().getInstanceConfig().isRequireEmailVerification()); - - Role role = localInstanceContext.instance().getDomain().isEmpty() ? roleService.getAdminRole( - () -> new RuntimeException("No Admin role found.") - ) : roleService.getDefaultRegisteredRole( - () -> new RuntimeException("No Registered role found.") - ); + person.setEmailVerified(localInstanceContext.instance() + .getInstanceConfig() == null || !localInstanceContext.instance() + .getInstanceConfig() + .isRequireEmailVerification()); + + Role role = localInstanceContext.instance() + .getDomain() + .isEmpty() ? roleService.getAdminRole(() -> new RuntimeException("No Admin role found.")) + : roleService.getDefaultRegisteredRole( + () -> new RuntimeException("No Registered role found.")); person.setRole(role); final String userActorId = baseUrlUtil.getBaseUrl() + "/u/" + person.getName(); person.setActorId(userActorId); - person.setLinkPersonInstance(LinkPersonInstance.builder().instance( - localInstanceContext.instance()).person(person).build()); + person.setLinkPersonInstance(LinkPersonInstance.builder() + .instance(localInstanceContext.instance()) + .person(person) + .build()); - final List languages = new ArrayList<>( - localInstanceContext.instance().getLanguages()); + final List languages = new ArrayList<>(localInstanceContext.instance() + .getLanguages()); person.setLanguages(languages); personRepository.save(person); - final PersonAggregate personAggregate = PersonAggregate.builder().person(person).build(); + final PersonAggregate personAggregate = PersonAggregate.builder() + .person(person) + .build(); personAggregateRepository.save(personAggregate); person.setPersonAggregate(personAggregate); From 123a9287f123762f83d0df2c6035f85c0a8660ee Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Mon, 6 May 2024 08:59:40 +0200 Subject: [PATCH 014/115] . --- .../community/services/SublinksCommunityService.java | 10 +++++----- .../v1/languages/mappers/SublinksLanguageMapper.java | 4 ++-- .../v1/person/mappers/SublinksPersonMapper.java | 4 ++-- .../sublinks/v1/roles/mappers/SublinksRoleMapper.java | 4 ++-- .../person/services/LinkPersonCommunityService.java | 6 +++--- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index 91619ca2..138076ec 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -5,7 +5,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.utils.ActorIdUtils; import com.sublinks.sublinksapi.authorization.enums.RolePermission; -import com.sublinks.sublinksapi.authorization.services.RoleAuthorizingService; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.community.entities.Community; import com.sublinks.sublinksapi.community.repositories.CommunityRepository; import com.sublinks.sublinksapi.community.services.CommunityService; @@ -26,7 +26,7 @@ public class SublinksCommunityService { private final ConversionService conversionService; private final CommunityService communityService; private final LocalInstanceContext localInstanceContext; - private final RoleAuthorizingService roleAuthorizingService; + private final RolePermissionService RolePermissionService; private final InstanceRepository instanceRepository; private final LinkPersonCommunityService linkPersonCommunityService; @@ -38,7 +38,7 @@ public CommunityResponse createCommunity(CreateCommunity createCommunity, Person throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "community_slug_already_exist"); } - if (roleAuthorizingService.hasAdminOrPermission(person, RolePermission.CREATE_COMMUNITY)) { + if (RolePermissionService.hasAdminOrPermission(person, RolePermission.CREATE_COMMUNITY)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_create_community"); } @@ -71,7 +71,7 @@ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommu } Community community = foundCommunity.get(); - if (!roleAuthorizingService.isModeratorOrAdmin(person, community)) { + if (!RolePermissionService.isModeratorOrAdmin(person, community)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_update_community"); } @@ -87,7 +87,7 @@ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommu if (isRemoved != null && isRemoved != community.isRemoved()) { - if (!roleAuthorizingService.isModeratorOrAdmin(person, community)) { + if (!RolePermissionService.isModeratorOrAdmin(person, community)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_remove_community"); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/mappers/SublinksLanguageMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/mappers/SublinksLanguageMapper.java index 06b41100..4538e3b1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/mappers/SublinksLanguageMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/mappers/SublinksLanguageMapper.java @@ -1,7 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.languages.mappers; import com.sublinks.sublinksapi.api.sublinks.v1.languages.models.LanguageResponse; -import com.sublinks.sublinksapi.authorization.services.RoleAuthorizingService; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.language.entities.Language; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @@ -10,7 +10,7 @@ import org.springframework.lang.Nullable; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = { - RoleAuthorizingService.class}) + RolePermissionService.class}) public abstract class SublinksLanguageMapper implements Converter { @Override diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java index 9ba3ad2d..f38887e4 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java @@ -3,7 +3,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksRoleMapper; import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; -import com.sublinks.sublinksapi.authorization.services.RoleAuthorizingService; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.person.entities.Person; import java.text.SimpleDateFormat; import java.util.Optional; @@ -39,7 +39,7 @@ public abstract class SublinksPersonMapper implements Converter { diff --git a/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java b/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java index 4fdaf072..f66c5beb 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.person.services; -import com.sublinks.sublinksapi.authorization.services.RoleAuthorizingService; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.community.entities.Community; import com.sublinks.sublinksapi.person.entities.LinkPersonCommunity; import com.sublinks.sublinksapi.person.entities.Person; @@ -27,13 +27,13 @@ public class LinkPersonCommunityService { public boolean hasLinkOrAdmin(Person person, Community community, LinkPersonCommunityType type) { - return RoleAuthorizingService.isAdmin(person) || hasLink(person, community, type); + return RolePermissionService.isAdmin(person) || hasLink(person, community, type); } public boolean hasAnyLinkOrAdmin(Person person, Community community, List types) { - return RoleAuthorizingService.isAdmin(person) || hasAnyLink(person, community, types); + return RolePermissionService.isAdmin(person) || hasAnyLink(person, community, types); } public boolean hasLink(Person person, Community community, LinkPersonCommunityType type) { From 0b8f6e20307ae55e1164eacd78c7547a53425cd6 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Mon, 6 May 2024 16:56:01 +0200 Subject: [PATCH 015/115] Refactor authentication and update APIs The authentication config has been refactored and the APIs related to JWT filter, person, and community have been updated. The changes include filename changes, code reformatting, function renaming, and improved access control checks. --- .../authentication/config/SecurityConfig.java | 19 ++-- .../v1/authentication/SublinksJwtFilter.java | 20 ++-- .../config/SublinksSecurityConfig.java | 46 +++++++++ .../services/SublinksCommunityService.java | 57 +++++++---- .../person/controllers/PersonController.java | 19 ++-- .../services/SublinksPersonService.java | 94 ++++++++++++------- 6 files changed, 179 insertions(+), 76 deletions(-) rename src/main/java/com/sublinks/sublinksapi/api/{sublinks/v1 => lemmy/v3}/authentication/config/SecurityConfig.java (69%) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SublinksSecurityConfig.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SecurityConfig.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java similarity index 69% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SecurityConfig.java rename to src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java index 31ea3301..d6d27969 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SecurityConfig.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java @@ -1,7 +1,6 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.authentication.config; +package com.sublinks.sublinksapi.api.lemmy.v3.authentication.config; import com.sublinks.sublinksapi.api.lemmy.v3.authentication.JwtFilter; -import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -21,7 +20,6 @@ @RequiredArgsConstructor public class SecurityConfig { - private final SublinksJwtFilter sublinksJwtFilter; private final JwtFilter lemmyJwtFilter; /** @@ -34,12 +32,15 @@ public class SecurityConfig { @Bean public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception { - http.csrf(AbstractHttpConfigurer::disable).authorizeHttpRequests( - (requests) -> requests.anyRequest().permitAll()).sessionManagement( - (sessionManagement) -> sessionManagement.sessionCreationPolicy( - SessionCreationPolicy.STATELESS)).addFilterBefore(sublinksJwtFilter, - UsernamePasswordAuthenticationFilter.class).addFilterBefore(lemmyJwtFilter, - UsernamePasswordAuthenticationFilter.class); + http.csrf(AbstractHttpConfigurer::disable) + .securityMatcher("/api/v3/**") + .authorizeHttpRequests((requests) -> requests.anyRequest() + .permitAll()) + .sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy( + SessionCreationPolicy.STATELESS)); + + http.addFilterBefore(lemmyJwtFilter, UsernamePasswordAuthenticationFilter.class); + return http.build(); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java index fc1e59f8..37233b26 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java @@ -32,13 +32,15 @@ public class SublinksJwtFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) - throws ServletException, IOException { + throws ServletException, IOException + { String authorizingToken = request.getHeader("Authorization"); if (authorizingToken == null && request.getCookies() != null) { for (Cookie cookie : request.getCookies()) { - if (cookie.getName().equals("jwt")) { + if (cookie.getName() + .equals("jwt")) { authorizingToken = cookie.getValue(); break; } @@ -62,8 +64,9 @@ protected void doFilterInternal(final HttpServletRequest request, response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "invalid_token"); } - if (userName != null && SecurityContextHolder.getContext().getAuthentication() == null) { - final Optional person = personRepository.findOneByName(userName); + if (userName != null && SecurityContextHolder.getContext() + .getAuthentication() == null) { + final Optional person = personRepository.findOneByNameIgnoreCase(userName); if (person.isEmpty()) { throw new UsernameNotFoundException("Invalid name"); } @@ -74,9 +77,11 @@ protected void doFilterInternal(final HttpServletRequest request, userDataService.checkAndAddIpRelation(person.get(), request.getRemoteAddr(), token, request.getHeader("User-Agent")); final SublinksJwtPerson authenticationToken = new SublinksJwtPerson(person.get(), - person.get().getAuthorities()); + person.get() + .getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authenticationToken); + SecurityContextHolder.getContext() + .setAuthentication(authenticationToken); } } filterChain.doFilter(request, response); @@ -85,6 +90,7 @@ protected void doFilterInternal(final HttpServletRequest request, @Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { - return !request.getServletPath().startsWith("/api/v3"); + return !request.getServletPath() + .startsWith("/api/v3"); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SublinksSecurityConfig.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SublinksSecurityConfig.java new file mode 100644 index 00000000..b162f9cf --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SublinksSecurityConfig.java @@ -0,0 +1,46 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.authentication.config; + +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtFilter; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +/** + * This class represents the configuration for the security of the application. It is responsible + * for setting up the security filters and rules. + */ +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SublinksSecurityConfig { + + private final SublinksJwtFilter sublinksJwtFilter; + + /** + * Returns a configured SecurityFilterChain object for the application's security. + * + * @param http The HttpSecurity object used to configure the security. + * @return The configured SecurityFilterChain object. + * @throws Exception If an error occurs during the configuration process. + */ + @Bean + public SecurityFilterChain sublinksFilterChain(final HttpSecurity http) throws Exception { + + http.csrf(AbstractHttpConfigurer::disable) + .securityMatcher("/api/v1/**") + .authorizeHttpRequests((requests) -> requests.anyRequest() + .permitAll()) + .sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy( + SessionCreationPolicy.STATELESS)); + + http.addFilterBefore(sublinksJwtFilter, UsernamePasswordAuthenticationFilter.class); + + return http.build(); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index 138076ec..b3a517a7 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -4,7 +4,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.utils.ActorIdUtils; -import com.sublinks.sublinksapi.authorization.enums.RolePermission; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommunityTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.community.entities.Community; import com.sublinks.sublinksapi.community.repositories.CommunityRepository; @@ -12,7 +12,9 @@ import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; import com.sublinks.sublinksapi.instance.repositories.InstanceRepository; import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.person.enums.LinkPersonCommunityType; import com.sublinks.sublinksapi.person.services.LinkPersonCommunityService; +import java.util.List; import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; @@ -26,7 +28,7 @@ public class SublinksCommunityService { private final ConversionService conversionService; private final CommunityService communityService; private final LocalInstanceContext localInstanceContext; - private final RolePermissionService RolePermissionService; + private final RolePermissionService rolePermissionService; private final InstanceRepository instanceRepository; private final LinkPersonCommunityService linkPersonCommunityService; @@ -38,15 +40,17 @@ public CommunityResponse createCommunity(CreateCommunity createCommunity, Person throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "community_slug_already_exist"); } - if (RolePermissionService.hasAdminOrPermission(person, RolePermission.CREATE_COMMUNITY)) { + if (rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.CREATE_COMMUNITY)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_create_community"); } Community community = Community.builder() .title(createCommunity.title()) .titleSlug(createCommunity.titleSlug()) - .bannerImageUrl(createCommunity.bannerImageUrl().orElse(null)) - .iconImageUrl(createCommunity.iconImageUrl().orElse(null)) + .bannerImageUrl(createCommunity.bannerImageUrl() + .orElse(null)) + .iconImageUrl(createCommunity.iconImageUrl() + .orElse(null)) .isNsfw(createCommunity.isNsfw()) .isPostingRestrictedToMods(createCommunity.isPostingRestrictedToMods()) .description(createCommunity.description()) @@ -58,10 +62,12 @@ public CommunityResponse createCommunity(CreateCommunity createCommunity, Person } public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommunityForm, - Person person) { + Person person) + { String domain = ActorIdUtils.getActorDomain(key); - if (domain != null && domain.equals(localInstanceContext.instance().getDomain())) { + if (domain != null && domain.equals(localInstanceContext.instance() + .getDomain())) { key = ActorIdUtils.getActorId(key); } Optional foundCommunity = communityRepository.findCommunityByTitleSlug(key); @@ -71,37 +77,48 @@ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommu } Community community = foundCommunity.get(); - if (!RolePermissionService.isModeratorOrAdmin(person, community)) { + if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_update_community"); } - final Boolean isDeleted = updateCommunityForm.deleted().orElse(null); + final Boolean isDeleted = updateCommunityForm.deleted() + .orElse(null); if (isDeleted != null && isDeleted != community.isDeleted()) { - community.setDeleted(updateCommunityForm.deleted().orElse(community.isDeleted())); + community.setDeleted(updateCommunityForm.deleted() + .orElse(community.isDeleted())); //@todo: do modlog } - final Boolean isRemoved = updateCommunityForm.removed().orElse(null); + final Boolean isRemoved = updateCommunityForm.removed() + .orElse(null); if (isRemoved != null && isRemoved != community.isRemoved()) { - if (!RolePermissionService.isModeratorOrAdmin(person, community)) { + if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_remove_community"); } - community.setRemoved(updateCommunityForm.removed().orElse(community.isRemoved())); + community.setRemoved(updateCommunityForm.removed() + .orElse(community.isRemoved())); //@todo: do modlog } - updateCommunityForm.title().ifPresent(community::setTitle); - updateCommunityForm.description().ifPresent(community::setDescription); - updateCommunityForm.isNsfw().ifPresent(community::setNsfw); - updateCommunityForm.iconImageUrl().ifPresent(community::setIconImageUrl); - updateCommunityForm.bannerImageUrl().ifPresent(community::setBannerImageUrl); - updateCommunityForm.isPostingRestrictedToMods().ifPresent( - community::setPostingRestrictedToMods); + updateCommunityForm.title() + .ifPresent(community::setTitle); + updateCommunityForm.description() + .ifPresent(community::setDescription); + updateCommunityForm.isNsfw() + .ifPresent(community::setNsfw); + updateCommunityForm.iconImageUrl() + .ifPresent(community::setIconImageUrl); + updateCommunityForm.bannerImageUrl() + .ifPresent(community::setBannerImageUrl); + updateCommunityForm.isPostingRestrictedToMods() + .ifPresent(community::setPostingRestrictedToMods); communityRepository.save(community); return conversionService.convert(community, CommunityResponse.class); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java index 8bb5cdfb..2f084b22 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java @@ -47,8 +47,10 @@ public class PersonController extends AbstractSublinksApiController { public List index(@Valid final IndexPerson indexPerson) { return personRepository.findAllByNameAndBiography(indexPerson.search(), - PageRequest.of(indexPerson.page(), indexPerson.limit())).stream().map( - person -> conversionService.convert(person, PersonResponse.class)).toList(); + PageRequest.of(indexPerson.page(), indexPerson.limit())) + .stream() + .map(person -> conversionService.convert(person, PersonResponse.class)) + .toList(); } @Operation(summary = "Get a specific person") @@ -57,8 +59,8 @@ public List index(@Valid final IndexPerson indexPerson) { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public PersonResponse show(@PathVariable String key) { - Optional personResponse = personRepository.findOneByName(key).map( - person -> conversionService.convert(person, PersonResponse.class)); + Optional personResponse = personRepository.findOneByNameIgnoreCase(key) + .map(person -> conversionService.convert(person, PersonResponse.class)); return personResponse.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); } @@ -68,7 +70,8 @@ public PersonResponse show(@PathVariable String key) { @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public LoginResponse create(final HttpServletRequest request, - @RequestBody @Valid final CreatePerson createPerson) { + @RequestBody @Valid final CreatePerson createPerson) + { return sublinksPersonService.registerPerson(createPerson, request.getRemoteAddr(), request.getHeader("User-Agent")); @@ -79,7 +82,8 @@ public LoginResponse create(final HttpServletRequest request, @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public LoginResponse login(final HttpServletRequest request, - @RequestBody @Valid final LoginPerson loginPerson) { + @RequestBody @Valid final LoginPerson loginPerson) + { return sublinksPersonService.login(loginPerson, request.getRemoteAddr(), request.getHeader("User-Agent")); @@ -90,7 +94,8 @@ public LoginResponse login(final HttpServletRequest request, @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public PersonResponse update(@PathVariable String key, - @RequestBody @Valid final UpdatePerson updatePersonForm, final SublinksJwtPerson principal) { + @RequestBody @Valid final UpdatePerson updatePersonForm, final SublinksJwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index d5f02afb..8742b3b5 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -50,12 +50,15 @@ public class SublinksPersonService { private final LanguageRepository languageRepository; public LoginResponse registerPerson(final CreatePerson createPersonForm, final String ip, - final String userAgent) { + final String userAgent) + { - final InstanceConfig instanceConfig = localInstanceContext.instance().getInstanceConfig(); + final InstanceConfig instanceConfig = localInstanceContext.instance() + .getInstanceConfig(); if (instanceConfig != null && instanceConfig.isRequireEmailVerification()) { - if (createPersonForm.email().isEmpty()) { + if (createPersonForm.email() + .isEmpty()) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "email_required"); } } @@ -63,10 +66,14 @@ public LoginResponse registerPerson(final CreatePerson createPersonForm, final S final Person.PersonBuilder personBuilder = Person.builder() .name(createPersonForm.name()) .displayName(createPersonForm.displayName()) - .avatarImageUrl(createPersonForm.avatarImageUrl().orElse(null)) - .bannerImageUrl(createPersonForm.bannerImageUrl().orElse(null)) - .biography(createPersonForm.bio().orElse(null)) - .matrixUserId(createPersonForm.matrixUserId().orElse(null)); + .avatarImageUrl(createPersonForm.avatarImageUrl() + .orElse(null)) + .bannerImageUrl(createPersonForm.bannerImageUrl() + .orElse(null)) + .biography(createPersonForm.bio() + .orElse(null)) + .matrixUserId(createPersonForm.matrixUserId() + .orElse(null)); final Person person = personBuilder.build(); @@ -84,13 +91,15 @@ public LoginResponse registerPerson(final CreatePerson createPersonForm, final S Map params = emailService.getDefaultEmailParameters(); params.put("person", person); - params.put("verificationUrl", localInstanceContext.instance().getDomain() + "/verify_email/" - + personEmailVerification.getToken()); + params.put("verificationUrl", localInstanceContext.instance() + .getDomain() + "/verify_email/" + personEmailVerification.getToken()); try { final String template_name = EmailTemplatesEnum.VERIFY_EMAIL.toString(); emailService.saveToQueue(Email.builder() .personRecipients(List.of(person)) - .subject(emailService.getSubjects().get(template_name).getAsString()) + .subject(emailService.getSubjects() + .get(template_name) + .getAsString()) .htmlContent(emailService.formatTextEmailTemplate(template_name, new Context(Locale.getDefault(), params))) .textContent(emailService.formatEmailTemplate(template_name, @@ -117,7 +126,8 @@ public LoginResponse registerPerson(final CreatePerson createPersonForm, final S : PersonRegistrationApplicationStatus.pending) .person(person) .question(instanceConfig.getRegistrationQuestion()) - .answer(createPersonForm.answer().orElse(null)) + .answer(createPersonForm.answer() + .orElse(null)) .build()); if (!instanceConfig.isRequireEmailVerification()) { status = RegistrationState.APPLICATION_CREATED; @@ -128,13 +138,18 @@ public LoginResponse registerPerson(final CreatePerson createPersonForm, final S userDataService.checkAndAddIpRelation(person, ip, token, userAgent); } - return LoginResponse.builder().token(Optional.ofNullable(token)).status(status).build(); + return LoginResponse.builder() + .token(Optional.ofNullable(token)) + .status(status) + .build(); } public LoginResponse login(final LoginPerson loginPersonForm, final String ip, - final String userAgent) { + final String userAgent) + { - final Optional foundPerson = personRepository.findOneByName(loginPersonForm.username()); + final Optional foundPerson = personRepository.findOneByNameIgnoreCase( + loginPersonForm.username()); if (foundPerson.isEmpty()) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found"); @@ -161,8 +176,8 @@ public LoginResponse login(final LoginPerson loginPersonForm, final String ip, Optional application = personRegistrationApplicationRepository.findOneByPerson( person); - if (application.isPresent() && application.get().getApplicationStatus() - != PersonRegistrationApplicationStatus.approved) { + if (application.isPresent() && application.get() + .getApplicationStatus() != PersonRegistrationApplicationStatus.approved) { return LoginResponse.builder() .token(Optional.empty()) .status(RegistrationState.UNCHANGED) @@ -170,7 +185,7 @@ public LoginResponse login(final LoginPerson loginPersonForm, final String ip, .build(); } - if (!personService.isPasswordEqual(person, loginPersonForm.password())) { + if (!personService.isValidPersonPassword(person, loginPersonForm.password())) { return LoginResponse.builder() .token(Optional.empty()) .status(RegistrationState.UNCHANGED) @@ -190,28 +205,41 @@ public LoginResponse login(final LoginPerson loginPersonForm, final String ip, public PersonResponse updatePerson(Person person, UpdatePerson updatePersonForm) { - if (updatePersonForm.languagesKeys().isPresent()) { - person.setLanguages( - languageRepository.findAllByCodeIsIn(updatePersonForm.languagesKeys().get())); + if (updatePersonForm.languagesKeys() + .isPresent()) { + person.setLanguages(languageRepository.findAllByCodeIsIn(updatePersonForm.languagesKeys() + .get())); } - updatePersonForm.displayName().ifPresent(person::setDisplayName); - updatePersonForm.email().ifPresent(person::setEmail); - updatePersonForm.avatarImageUrl().ifPresent(person::setAvatarImageUrl); - updatePersonForm.bannerImageUrl().ifPresent(person::setBannerImageUrl); - updatePersonForm.bio().ifPresent(person::setBiography); - updatePersonForm.matrixUserId().ifPresent(person::setMatrixUserId); - - if (updatePersonForm.oldPassword().isPresent() && updatePersonForm.password().isPresent() - && updatePersonForm.passwordConfirmation().isPresent()) { - if (!personService.isPasswordEqual(person, updatePersonForm.oldPassword().get())) { + updatePersonForm.displayName() + .ifPresent(person::setDisplayName); + updatePersonForm.email() + .ifPresent(person::setEmail); + updatePersonForm.avatarImageUrl() + .ifPresent(person::setAvatarImageUrl); + updatePersonForm.bannerImageUrl() + .ifPresent(person::setBannerImageUrl); + updatePersonForm.bio() + .ifPresent(person::setBiography); + updatePersonForm.matrixUserId() + .ifPresent(person::setMatrixUserId); + + if (updatePersonForm.oldPassword() + .isPresent() && updatePersonForm.password() + .isPresent() && updatePersonForm.passwordConfirmation() + .isPresent()) { + if (!personService.isValidPersonPassword(person, updatePersonForm.oldPassword() + .get())) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "password_incorrect"); } - if (!updatePersonForm.password().get().equals( - updatePersonForm.passwordConfirmation().get())) { + if (!updatePersonForm.password() + .get() + .equals(updatePersonForm.passwordConfirmation() + .get())) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "password_mismatch"); } - personService.updatePassword(person, updatePersonForm.password().get()); + personService.updatePassword(person, updatePersonForm.password() + .get()); } personRepository.save(person); From e2523b106f9823422fdc06fa344b5732812ac718 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Tue, 7 May 2024 07:34:18 +0200 Subject: [PATCH 016/115] Refactor controller classes and improve security This commit refactors several controller classes for better code organization and consistency. Classes were renamed, and the RestController annotation was added to each controller. Additionally, the implementation of JWT filter was updated to improve authentication security. Improvements to the build file were also made, adding additional dependencies. --- build.gradle | 3 +- .../authentication/config/SecurityConfig.java | 1 + ...va => SublinksAnnouncementController.java} | 4 +- .../v1/authentication/SublinksJwtFilter.java | 4 +- ...er.java => SublinksCommentController.java} | 4 +- .../mappers/LemmyListingTypeMapper.java | 2 + .../mappers/SublinksListingTypeMapper.java | 24 ++++++ ....java => SublinksCommunityController.java} | 74 +++++++++++++++---- .../v1/community/models/IndexCommunity.java | 3 + .../services/SublinksCommunityService.java | 2 + ...r.java => SublinksInstanceController.java} | 4 +- ...r.java => SublinksLanguageController.java} | 4 +- ...ler.java => SublinksPersonController.java} | 6 +- .../services/SublinksPersonService.java | 2 + ...oller.java => SublinksPostController.java} | 4 +- ... => SublinksPrivatemessageController.java} | 4 +- ...ller.java => SublinksRolesController.java} | 3 +- ...ler.java => SublinksSearchController.java} | 3 +- 18 files changed, 124 insertions(+), 27 deletions(-) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/{AnnouncementController.java => SublinksAnnouncementController.java} (91%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/{CommentController.java => SublinksCommentController.java} (91%) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/SublinksListingTypeMapper.java rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/{CommunityController.java => SublinksCommunityController.java} (60%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/{InstanceController.java => SublinksInstanceController.java} (91%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/{LanguageController.java => SublinksLanguageController.java} (94%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/{PersonController.java => SublinksPersonController.java} (95%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/{PostController.java => SublinksPostController.java} (91%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/{PrivatemessageController.java => SublinksPrivatemessageController.java} (91%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/{RolesController.java => SublinksRolesController.java} (93%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/{SearchController.java => SublinksSearchController.java} (87%) diff --git a/build.gradle b/build.gradle index 136a761f..af295fd1 100644 --- a/build.gradle +++ b/build.gradle @@ -32,7 +32,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-amqp' - testImplementation 'org.springframework.amqp:spring-rabbit-test' + implementation 'org.springframework.boot:spring-boot-starter-actuator' + testImplementation 'org.springframework.amqp:spring-rabbit-test' annotationProcessor "org.hibernate:hibernate-jpamodelgen:6.5.0.Final" diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java index d6d27969..bbcbc691 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java @@ -43,4 +43,5 @@ public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception return http.build(); } + } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/AnnouncementController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java similarity index 91% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/AnnouncementController.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java index 861a682a..30ace3f3 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/AnnouncementController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java @@ -9,10 +9,12 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +@RestController @RequestMapping("api/v1/announcement") @Tag(name = "Announcement", description = "Announcement API") -public class AnnouncementController extends AbstractSublinksApiController { +public class SublinksAnnouncementController extends AbstractSublinksApiController { @Operation(summary = "Get a list of announcements") @GetMapping diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java index 37233b26..36fe2a80 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java @@ -90,7 +90,7 @@ protected void doFilterInternal(final HttpServletRequest request, @Override protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException { - return !request.getServletPath() - .startsWith("/api/v3"); + String servletPath = request.getServletPath(); + return !servletPath.startsWith("/api/v1") && !servletPath.startsWith("/pictrs"); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/CommentController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java similarity index 91% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/CommentController.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java index a4d8ef11..d31a7772 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/CommentController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java @@ -9,10 +9,12 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +@RestController @RequestMapping("api/v1/comment") @Tag(name = "Comment", description = "Comment API") -public class CommentController extends AbstractSublinksApiController { +public class SublinksCommentController extends AbstractSublinksApiController { @Operation(summary = "Get a list of comments") @GetMapping @ApiResponses(value = { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmyListingTypeMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmyListingTypeMapper.java index 1041639b..508caf75 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmyListingTypeMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmyListingTypeMapper.java @@ -17,4 +17,6 @@ public ListingType convert( return ListingType.valueOf(listingType.name()); } + + } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/SublinksListingTypeMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/SublinksListingTypeMapper.java new file mode 100644 index 00000000..6f57883b --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/SublinksListingTypeMapper.java @@ -0,0 +1,24 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.common.mappers; + +import com.sublinks.sublinksapi.person.enums.ListingType; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +public class SublinksListingTypeMapper implements + Converter { + + @Nullable + @Override + public com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType convert( + ListingType listingType) + { + + return com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType.valueOf( + listingType.name()); + } + + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/CommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java similarity index 60% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/CommunityController.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index 32ae6d75..643926c9 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/CommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -11,6 +11,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; import com.sublinks.sublinksapi.community.models.CommunitySearchCriteria; import com.sublinks.sublinksapi.community.repositories.CommunityRepository; +import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -28,37 +29,80 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; +@RestController @AllArgsConstructor -@RequestMapping("api/v1/community") +@RequestMapping("/api/v1/community") @Tag(name = "Community", description = "Community API") -public class CommunityController extends AbstractSublinksApiController { +public class SublinksCommunityController extends AbstractSublinksApiController { private final CommunityRepository communityRepository; private final SublinksCommunityService sublinksCommunityService; private final ConversionService conversionService; + private final LocalInstanceContext localInstanceContext; @Operation(summary = "Get a list of communities") @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List index(@Valid final IndexCommunity indexCommunityForm, - final SublinksJwtPerson sublinksJwtPerson) { + public List index( + @RequestParam(required = false) final Optional indexCommunityParam, + final SublinksJwtPerson sublinksJwtPerson) + { final Optional person = getOptionalPerson(sublinksJwtPerson); - return communityRepository.allCommunitiesBySearchCriteria( - CommunitySearchCriteria.builder() + final IndexCommunity indexCommunityForm = indexCommunityParam.orElse(IndexCommunity.builder() + .build()); + + com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType sortType = indexCommunityForm.sortType(); + + if (sortType == null) { + if (person.isPresent() && person.get() + .getDefaultSortType() != null) { + sortType = conversionService.convert(person.get() + .getDefaultSortType(), + com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType.class); + } else { + sortType = com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType.New; + } + } + + com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType listingType = indexCommunityForm.listingType(); + + if (listingType == null) { + if (person.isPresent() && person.get() + .getDefaultListingType() != null) { + listingType = conversionService.convert(person.get() + .getDefaultListingType(), + com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType.class); + } else if (localInstanceContext.instance() + .getInstanceConfig() + .getDefaultPostListingType() != null) { + listingType = conversionService.convert(localInstanceContext.instance() + .getInstanceConfig() + .getDefaultPostListingType(), + com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType.class); + } else { + listingType = com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType.Local; + } + } + + return communityRepository.allCommunitiesBySearchCriteria(CommunitySearchCriteria.builder() .page(indexCommunityForm.page()) .perPage(indexCommunityForm.limit()) - .sortType(conversionService.convert(indexCommunityForm.sortType(), SortType.class)) - .listingType( - conversionService.convert(indexCommunityForm.listingType(), ListingType.class)) - .showNsfw(indexCommunityForm.showNsfw().orElse(false)) + .sortType(conversionService.convert(sortType, SortType.class)) + .listingType(conversionService.convert(listingType, ListingType.class)) + .showNsfw(indexCommunityForm.showNsfw() + .orElse(false)) .person(person.orElse(null)) - .build()).stream().map( - community -> conversionService.convert(community, CommunityResponse.class)).toList(); + .build()) + .stream() + .map(community -> conversionService.convert(community, CommunityResponse.class)) + .toList(); } @@ -81,7 +125,8 @@ public CommunityResponse show(@PathVariable final String key) { @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse create(@RequestBody @Valid final CreateCommunity createCommunity, - final SublinksJwtPerson sublinksJwtPerson) { + final SublinksJwtPerson sublinksJwtPerson) + { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); @@ -93,7 +138,8 @@ public CommunityResponse create(@RequestBody @Valid final CreateCommunity create @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse update(@PathVariable String key, - @RequestBody @Valid UpdateCommunity updateCommunityForm, final SublinksJwtPerson principal) { + @RequestBody @Valid UpdateCommunity updateCommunityForm, final SublinksJwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java index b9811409..daec4802 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java @@ -2,9 +2,12 @@ import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; import java.util.List; import java.util.Optional; +@Builder public record IndexCommunity(String search, SortType sortType, ListingType listingType, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index b3a517a7..f352d4fc 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -19,9 +19,11 @@ import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; @AllArgsConstructor +@Service public class SublinksCommunityService { private final CommunityRepository communityRepository; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/InstanceController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java similarity index 91% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/InstanceController.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java index d17cdc5d..bf9092ec 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/InstanceController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java @@ -9,10 +9,12 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +@RestController @RequestMapping("api/v1/instance") @Tag(name = "Instance", description = "Instance API") -public class InstanceController extends AbstractSublinksApiController { +public class SublinksInstanceController extends AbstractSublinksApiController { @Operation(summary = "Get a list of instances") @GetMapping @ApiResponses(value = { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/LanguageController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java similarity index 94% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/LanguageController.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java index 21c68bcc..67038c18 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/LanguageController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java @@ -16,12 +16,14 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; +@RestController @RequestMapping("api/v1/languages") @Tag(name = "Languages", description = "Languages API") @AllArgsConstructor -public class LanguageController extends AbstractSublinksApiController { +public class SublinksLanguageController extends AbstractSublinksApiController { private final LanguageService languageService; private final LocalInstanceContext localInstanceContext; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java similarity index 95% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java index 2f084b22..1e44315b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/PersonController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java @@ -29,12 +29,14 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; +@RestController @RequestMapping("api/v1/person") @Tag(name = "Person", description = "Person API") @AllArgsConstructor -public class PersonController extends AbstractSublinksApiController { +public class SublinksPersonController extends AbstractSublinksApiController { private final PersonRepository personRepository; private final SublinksPersonService sublinksPersonService; @@ -44,7 +46,7 @@ public class PersonController extends AbstractSublinksApiController { @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List index(@Valid final IndexPerson indexPerson) { + public List index(final IndexPerson indexPerson) { return personRepository.findAllByNameAndBiography(indexPerson.search(), PageRequest.of(indexPerson.page(), indexPerson.limit())) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index 8742b3b5..ebb49212 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -31,10 +31,12 @@ import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; import org.thymeleaf.context.Context; @AllArgsConstructor +@Service public class SublinksPersonService { private final PersonService personService; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/PostController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java similarity index 91% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/PostController.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java index 1d442ff1..80504d63 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/PostController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java @@ -9,10 +9,12 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +@RestController @RequestMapping("api/v1/post") @Tag(name = "Post", description = "Post API") -public class PostController extends AbstractSublinksApiController { +public class SublinksPostController extends AbstractSublinksApiController { @Operation(summary = "Get a list of posts") @GetMapping @ApiResponses(value = { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/PrivatemessageController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java similarity index 91% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/PrivatemessageController.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java index 4616035f..ffc33bbf 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/PrivatemessageController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java @@ -9,10 +9,12 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +@RestController @RequestMapping("api/v1/privatemessage") @Tag(name = "Privatemessage", description = "Privatemessage API") -public class PrivatemessageController extends AbstractSublinksApiController { +public class SublinksPrivatemessageController extends AbstractSublinksApiController { @Operation(summary = "Get a list of privatemessages") @GetMapping @ApiResponses(value = { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/RolesController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java similarity index 93% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/RolesController.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java index 69ca7c35..792556e6 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/RolesController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java @@ -6,9 +6,10 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.*; +@RestController @RequestMapping("api/v1/roles") @Tag(name = "Roles", description = "Roles API") -public class RolesController extends AbstractSublinksApiController { +public class SublinksRolesController extends AbstractSublinksApiController { @Operation(summary = "Get a list of roles") @GetMapping @ApiResponses(value = { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SearchController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java similarity index 87% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SearchController.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java index dd935bb5..d4e0ed0b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SearchController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java @@ -6,9 +6,10 @@ import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.*; +@RestController @RequestMapping("api/v1/search") @Tag(name = "Search", description = "Search API") -public class SearchController extends AbstractSublinksApiController { +public class SublinksSearchController extends AbstractSublinksApiController { @Operation(summary = "Get a list of privatemessages") @GetMapping @ApiResponses(value = { From ac83973ffe1bca7f8a83d5f36a746e94eb51c937 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Tue, 7 May 2024 10:58:38 +0200 Subject: [PATCH 017/115] Renamed Enum 'ListingType' to 'SublinksListingType' and updated references The enum 'ListingType' was renamed to 'SublinksListingType' to be more specific. All references in the code to the old 'ListingType' has been updated to 'SublinksListingType'. Additionally, the boolean 'showNsfw' in IndexCommunity class was updated to not use Optional. --- .../authentication/config/SecurityConfig.java | 7 ++--- .../config/SublinksSecurityConfig.java | 4 ++- .../v1/comment/models/IndexComment.java | 4 +-- ...tingType.java => SublinksListingType.java} | 2 +- .../mappers/EntityListingTypeMapper.java | 25 ++++++++++++++++++ .../mappers/LemmyListingTypeMapper.java | 7 ++--- .../mappers/SublinksListingTypeMapper.java | 10 +++---- .../SublinksCommunityController.java | 26 +++++++++---------- .../v1/community/models/IndexCommunity.java | 8 +++--- .../v1/person/models/IndexPerson.java | 4 +-- 10 files changed, 62 insertions(+), 35 deletions(-) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/{ListingType.java => SublinksListingType.java} (77%) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/EntityListingTypeMapper.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java index bbcbc691..01f755d4 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; @@ -18,6 +19,7 @@ @Configuration @EnableWebSecurity @RequiredArgsConstructor +@Order(2) public class SecurityConfig { private final JwtFilter lemmyJwtFilter; @@ -32,6 +34,7 @@ public class SecurityConfig { @Bean public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception { + http.addFilterBefore(lemmyJwtFilter, UsernamePasswordAuthenticationFilter.class); http.csrf(AbstractHttpConfigurer::disable) .securityMatcher("/api/v3/**") .authorizeHttpRequests((requests) -> requests.anyRequest() @@ -39,9 +42,7 @@ public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception .sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy( SessionCreationPolicy.STATELESS)); - http.addFilterBefore(lemmyJwtFilter, UsernamePasswordAuthenticationFilter.class); - return http.build(); } - + } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SublinksSecurityConfig.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SublinksSecurityConfig.java index b162f9cf..61f67e8d 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SublinksSecurityConfig.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SublinksSecurityConfig.java @@ -4,6 +4,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; @@ -18,6 +19,7 @@ @Configuration @EnableWebSecurity @RequiredArgsConstructor +@Order(1) public class SublinksSecurityConfig { private final SublinksJwtFilter sublinksJwtFilter; @@ -32,6 +34,7 @@ public class SublinksSecurityConfig { @Bean public SecurityFilterChain sublinksFilterChain(final HttpSecurity http) throws Exception { + http.addFilterBefore(sublinksJwtFilter, UsernamePasswordAuthenticationFilter.class); http.csrf(AbstractHttpConfigurer::disable) .securityMatcher("/api/v1/**") .authorizeHttpRequests((requests) -> requests.anyRequest() @@ -39,7 +42,6 @@ public SecurityFilterChain sublinksFilterChain(final HttpSecurity http) throws E .sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy( SessionCreationPolicy.STATELESS)); - http.addFilterBefore(sublinksJwtFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java index c3205188..192ef540 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.comment.models; -import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; import java.util.List; import java.util.Optional; @@ -9,7 +9,7 @@ @Builder public record IndexComment(String search, SortType sortType, - ListingType listingType, + SublinksListingType sublinksListingType, Optional> communityKeys, Optional> postKeys, Optional showNsfw, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/ListingType.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SublinksListingType.java similarity index 77% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/ListingType.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SublinksListingType.java index f9fdb0cf..0091b83a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/ListingType.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SublinksListingType.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.common.enums; -public enum ListingType { +public enum SublinksListingType { All, Local, Subscribed, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/EntityListingTypeMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/EntityListingTypeMapper.java new file mode 100644 index 00000000..3b4ba309 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/EntityListingTypeMapper.java @@ -0,0 +1,25 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.common.mappers; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; +import com.sublinks.sublinksapi.person.enums.ListingType; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +public class EntityListingTypeMapper implements + Converter { + + @Nullable + @Override + public SublinksListingType convert( + ListingType listingType) + { + + return SublinksListingType.valueOf( + listingType.name()); + } + + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmyListingTypeMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmyListingTypeMapper.java index 508caf75..94c9efda 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmyListingTypeMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmyListingTypeMapper.java @@ -1,6 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.common.mappers; import com.sublinks.sublinksapi.api.lemmy.v3.enums.ListingType; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; import org.springframework.core.convert.converter.Converter; @@ -8,14 +9,14 @@ @Mapper(componentModel = MappingConstants.ComponentModel.SPRING) public class LemmyListingTypeMapper implements - Converter { + Converter { @Nullable @Override public ListingType convert( - com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType listingType) { + SublinksListingType sublinksListingType) { - return ListingType.valueOf(listingType.name()); + return ListingType.valueOf(sublinksListingType.name()); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/SublinksListingTypeMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/SublinksListingTypeMapper.java index 6f57883b..11f612bb 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/SublinksListingTypeMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/SublinksListingTypeMapper.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.common.mappers; -import com.sublinks.sublinksapi.person.enums.ListingType; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import org.mapstruct.Mapper; import org.mapstruct.MappingConstants; import org.springframework.core.convert.converter.Converter; @@ -8,15 +8,15 @@ @Mapper(componentModel = MappingConstants.ComponentModel.SPRING) public class SublinksListingTypeMapper implements - Converter { + Converter { @Nullable @Override - public com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType convert( - ListingType listingType) + public SublinksListingType convert( + com.sublinks.sublinksapi.api.lemmy.v3.enums.ListingType listingType) { - return com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType.valueOf( + return SublinksListingType.valueOf( listingType.name()); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index 643926c9..62f84a7f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -4,6 +4,7 @@ import com.sublinks.sublinksapi.api.lemmy.v3.enums.SortType; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.IndexCommunity; @@ -71,33 +72,32 @@ public List index( } } - com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType listingType = indexCommunityForm.listingType(); + SublinksListingType sublinksListingType = indexCommunityForm.sublinksListingType(); - if (listingType == null) { + if (sublinksListingType == null) { if (person.isPresent() && person.get() .getDefaultListingType() != null) { - listingType = conversionService.convert(person.get() - .getDefaultListingType(), - com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType.class); + sublinksListingType = conversionService.convert(person.get() + .getDefaultListingType(), SublinksListingType.class); } else if (localInstanceContext.instance() .getInstanceConfig() .getDefaultPostListingType() != null) { - listingType = conversionService.convert(localInstanceContext.instance() - .getInstanceConfig() - .getDefaultPostListingType(), - com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType.class); + sublinksListingType = conversionService.convert(localInstanceContext.instance() + .getInstanceConfig() + .getDefaultPostListingType(), SublinksListingType.class); } else { - listingType = com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType.Local; + sublinksListingType = SublinksListingType.Local; } } + boolean showNsfw = indexCommunityForm.showNsfw() != null && indexCommunityForm.showNsfw(); + return communityRepository.allCommunitiesBySearchCriteria(CommunitySearchCriteria.builder() .page(indexCommunityForm.page()) .perPage(indexCommunityForm.limit()) .sortType(conversionService.convert(sortType, SortType.class)) - .listingType(conversionService.convert(listingType, ListingType.class)) - .showNsfw(indexCommunityForm.showNsfw() - .orElse(false)) + .listingType(conversionService.convert(sublinksListingType, ListingType.class)) + .showNsfw(showNsfw) .person(person.orElse(null)) .build()) .stream() diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java index daec4802..6443aa05 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java @@ -1,8 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models; -import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; -import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; import java.util.List; import java.util.Optional; @@ -10,9 +9,8 @@ @Builder public record IndexCommunity(String search, SortType sortType, - ListingType listingType, - Optional> communityKeys, - Optional showNsfw, + SublinksListingType sublinksListingType, + Boolean showNsfw, int limit, int page) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java index 222653ea..7e6b1d80 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java @@ -1,12 +1,12 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.models; -import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.ListingType; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; import lombok.Builder; @Builder public record IndexPerson(String search, - ListingType listingType, + SublinksListingType sublinksListingType, SortType sortType, int limit, int page) { From 505d895b217e6a9f95b4ac9fe085587adcffbebd Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Tue, 7 May 2024 14:21:17 +0200 Subject: [PATCH 018/115] Made the community controller work --- .../SublinksCommunityController.java | 32 +++++++++++++------ .../mappers/SublinksCommunityMapper.java | 4 +-- 2 files changed, 23 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index 62f84a7f..5483c5f0 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -92,14 +92,24 @@ public List index( boolean showNsfw = indexCommunityForm.showNsfw() != null && indexCommunityForm.showNsfw(); - return communityRepository.allCommunitiesBySearchCriteria(CommunitySearchCriteria.builder() - .page(indexCommunityForm.page()) - .perPage(indexCommunityForm.limit()) - .sortType(conversionService.convert(sortType, SortType.class)) - .listingType(conversionService.convert(sublinksListingType, ListingType.class)) - .showNsfw(showNsfw) - .person(person.orElse(null)) - .build()) + final CommunitySearchCriteria.CommunitySearchCriteriaBuilder criteria = CommunitySearchCriteria.builder() + .perPage(indexCommunityForm.limit()) + .page(indexCommunityForm.page()) + .sortType(conversionService.convert(sortType, SortType.class)) + .listingType(conversionService.convert(sublinksListingType, ListingType.class)) + .showNsfw(showNsfw) + .person(person.orElse(null)); + + if (indexCommunityForm.limit() == 0) { + criteria.perPage(20); + } + if (indexCommunityForm.page() == 0) { + criteria.page(1); + } + + final CommunitySearchCriteria communitySearchCriteria = criteria.build(); + + return communityRepository.allCommunitiesBySearchCriteria(communitySearchCriteria) .stream() .map(community -> conversionService.convert(community, CommunityResponse.class)) .toList(); @@ -113,8 +123,10 @@ public List index( public CommunityResponse show(@PathVariable final String key) { try { - return conversionService.convert(communityRepository.findCommunityByTitleSlug(key), - CommunityResponse.class); + return communityRepository.findCommunityByTitleSlug(key) + .map(comm -> conversionService.convert(comm, CommunityResponse.class)) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Community not found")); } catch (Exception e) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Community not found"); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityMapper.java index 341bb6c1..7da47b64 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityMapper.java @@ -15,8 +15,6 @@ SublinksLanguageMapper.class, OptionalStringMapper.class}) public abstract class SublinksCommunityMapper implements Converter { - SublinksLanguageMapper languageMapper; - @Override @Mapping(target = "key", source = "community.titleSlug") @Mapping(target = "title", source = "community.title") @@ -25,7 +23,7 @@ public abstract class SublinksCommunityMapper implements Converter Date: Wed, 8 May 2024 18:52:08 +0200 Subject: [PATCH 019/115] Fixed some more --- .../SublinksLanguageController.java | 19 ++++++++++++------- .../repositories/CommunityRepository.java | 2 ++ .../language/services/LanguageService.java | 7 ------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java index 67038c18..3bae62c5 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java @@ -35,10 +35,12 @@ public class SublinksLanguageController extends AbstractSublinksApiController { @ApiResponse(responseCode = "200", description = "List of languagesKeys", useReturnTypeSchema = true)}) public List index() { - List languages = languageService.instanceLanguages(localInstanceContext.instance()); + List languages = localInstanceContext.instance() + .getLanguages(); - return languages.stream().map( - language -> conversionService.convert(language, LanguageResponse.class)).toList(); + return languages.stream() + .map(language -> conversionService.convert(language, LanguageResponse.class)) + .toList(); } @@ -49,11 +51,14 @@ public List index() { @ApiResponse(responseCode = "404", description = "Language not found")}) public LanguageResponse show(@PathVariable String id) { - List languages = languageService.instanceLanguages(localInstanceContext.instance()); + List languages = localInstanceContext.instance() + .getLanguages(); - Language foundLanguage = languages.stream().filter( - language -> language.getId().equals(Long.valueOf(id))).findFirst().orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "language_not_found")); + Language foundLanguage = languages.stream() + .filter(language -> language.getId() + .equals(Long.valueOf(id))) + .findFirst() + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "language_not_found")); return conversionService.convert(foundLanguage, LanguageResponse.class); } diff --git a/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepository.java b/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepository.java index 93643c4b..d5819e20 100644 --- a/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepository.java @@ -15,6 +15,8 @@ public interface CommunityRepository extends JpaRepository, Optional findCommunityByTitleSlug(String titleSlug); + boolean existsByTitleSlug(String titleSlug); + List findCommunitiesByInstance(Instance instance); Community findCommunityByPublicKey(String publicKey); diff --git a/src/main/java/com/sublinks/sublinksapi/language/services/LanguageService.java b/src/main/java/com/sublinks/sublinksapi/language/services/LanguageService.java index 1bce7c65..b4d9acda 100644 --- a/src/main/java/com/sublinks/sublinksapi/language/services/LanguageService.java +++ b/src/main/java/com/sublinks/sublinksapi/language/services/LanguageService.java @@ -34,11 +34,4 @@ public List languageIdsToEntity(final Collection languageIds) } return languages; } - - public List instanceLanguages(final Instance instance) { - - final List languages = new ArrayList<>(); - languages.addAll(instance.getLanguages()); - return languages; - } } From 0d47e472c494a7fa404ce7ef0871f022351a74e3 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Wed, 8 May 2024 21:58:32 +0200 Subject: [PATCH 020/115] Add CommunityView and CommunityAggregates models New models, CommunityView and CommunityAggregates, were introduced to provide comprehensive information about communities. The SublinksCommunityController was also updated to use these models, thereby improving the details provided when fetching a community or list of communities. --- .../v3/site/controllers/SiteController.java | 3 +- .../SublinksCommunityController.java | 46 +++++++++++++++---- .../community/models/CommunityAggregates.java | 12 +++++ .../v1/community/models/CommunityView.java | 12 +++++ 4 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityView.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java index 51010492..2341d032 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java @@ -68,7 +68,8 @@ public class SiteController extends AbstractLemmyApiController { private final MyUserInfoService myUserInfoService; private final RolePermissionService rolePermissionService; private final InstanceConfigService instanceConfigService; - private final SlurFilterService slurFilterService; + private final SlurFil List languages +terService slurFilterService; private final AnnouncementRepository announcementRepository; private final ConversionService conversionService; private final LemmyPersonService lemmyPersonService; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index 5483c5f0..10d7c975 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -5,20 +5,26 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityAggregates; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityView; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.IndexCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.community.models.CommunitySearchCriteria; import com.sublinks.sublinksapi.community.repositories.CommunityRepository; import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.person.enums.LinkPersonCommunityType; +import com.sublinks.sublinksapi.person.services.LinkPersonCommunityService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; 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 java.util.Optional; import lombok.AllArgsConstructor; @@ -44,12 +50,13 @@ public class SublinksCommunityController extends AbstractSublinksApiController { private final SublinksCommunityService sublinksCommunityService; private final ConversionService conversionService; private final LocalInstanceContext localInstanceContext; + private final LinkPersonCommunityService linkPersonCommunityService; @Operation(summary = "Get a list of communities") @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List index( + public List index( @RequestParam(required = false) final Optional indexCommunityParam, final SublinksJwtPerson sublinksJwtPerson) { @@ -109,26 +116,45 @@ public List index( final CommunitySearchCriteria communitySearchCriteria = criteria.build(); - return communityRepository.allCommunitiesBySearchCriteria(communitySearchCriteria) - .stream() - .map(community -> conversionService.convert(community, CommunityResponse.class)) - .toList(); - + List communities = new ArrayList<>(); + + communityRepository.allCommunitiesBySearchCriteria(communitySearchCriteria) + .forEach(community -> communities.add(CommunityView.builder() + .communityResponse(conversionService.convert(community, CommunityResponse.class)) + .communityAggregates(conversionService.convert(community.getCommunityAggregate(), + CommunityAggregates.class)) + .moderators(linkPersonCommunityService.getPersonsFromCommunityAndListTypes(community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) + .stream() + .map(pers -> conversionService.convert(pers, PersonResponse.class)) + .toList()) + .build())); + + return communities; } @Operation(summary = "Get a specific community") @GetMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommunityResponse show(@PathVariable final String key) { + public CommunityView show(@PathVariable final String key) { try { return communityRepository.findCommunityByTitleSlug(key) - .map(comm -> conversionService.convert(comm, CommunityResponse.class)) + .map(comm -> CommunityView.builder() + .communityResponse(conversionService.convert(comm, CommunityResponse.class)) + .communityAggregates(conversionService.convert(comm.getCommunityAggregate(), + CommunityAggregates.class)) + .moderators(linkPersonCommunityService.getPersonsFromCommunityAndListTypes(comm, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) + .stream() + .map(pers -> conversionService.convert(pers, PersonResponse.class)) + .toList()) + .build()) .orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Community not found")); + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); } catch (Exception e) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Community not found"); + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "community_error"); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java new file mode 100644 index 00000000..a6dacc96 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java @@ -0,0 +1,12 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models; + +public record CommunityAggregates(Long id, + int subscribers, + int posts, + int comments, + int activeDailyUsers, + int activeWeeklyUsers, + int activeMonthlyUsers, + int activeHalfYearlyUsers) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityView.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityView.java new file mode 100644 index 00000000..f56aa572 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityView.java @@ -0,0 +1,12 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models; + +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; +import java.util.List; +import lombok.Builder; + +@Builder +public record CommunityView(CommunityResponse communityResponse, + CommunityAggregates communityAggregates, + List moderators) { + +} From 05a030bc70ed10a859809aa0af00645149b09496 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Wed, 8 May 2024 21:58:46 +0200 Subject: [PATCH 021/115] Fixed a hickup --- .../api/lemmy/v3/site/controllers/SiteController.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java index 2341d032..51010492 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java @@ -68,8 +68,7 @@ public class SiteController extends AbstractLemmyApiController { private final MyUserInfoService myUserInfoService; private final RolePermissionService rolePermissionService; private final InstanceConfigService instanceConfigService; - private final SlurFil List languages -terService slurFilterService; + private final SlurFilterService slurFilterService; private final AnnouncementRepository announcementRepository; private final ConversionService conversionService; private final LemmyPersonService lemmyPersonService; From a76268e1109ce7c7521f88ad88e345239bf93118 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Wed, 8 May 2024 21:59:50 +0200 Subject: [PATCH 022/115] Renamed some attributes --- .../controllers/SublinksCommunityController.java | 6 +++--- .../v1/community/models/CommunityAggregates.java | 12 ------------ .../models/CommunityAggregatesResponse.java | 12 ++++++++++++ .../sublinks/v1/community/models/CommunityView.java | 4 ++-- 4 files changed, 17 insertions(+), 17 deletions(-) delete mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index 10d7c975..71139c11 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -5,7 +5,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; -import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityAggregates; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityAggregatesResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityView; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; @@ -122,7 +122,7 @@ public List index( .forEach(community -> communities.add(CommunityView.builder() .communityResponse(conversionService.convert(community, CommunityResponse.class)) .communityAggregates(conversionService.convert(community.getCommunityAggregate(), - CommunityAggregates.class)) + CommunityAggregatesResponse.class)) .moderators(linkPersonCommunityService.getPersonsFromCommunityAndListTypes(community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) .stream() @@ -144,7 +144,7 @@ public CommunityView show(@PathVariable final String key) { .map(comm -> CommunityView.builder() .communityResponse(conversionService.convert(comm, CommunityResponse.class)) .communityAggregates(conversionService.convert(comm.getCommunityAggregate(), - CommunityAggregates.class)) + CommunityAggregatesResponse.class)) .moderators(linkPersonCommunityService.getPersonsFromCommunityAndListTypes(comm, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) .stream() diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java deleted file mode 100644 index a6dacc96..00000000 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.community.models; - -public record CommunityAggregates(Long id, - int subscribers, - int posts, - int comments, - int activeDailyUsers, - int activeWeeklyUsers, - int activeMonthlyUsers, - int activeHalfYearlyUsers) { - -} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java new file mode 100644 index 00000000..c8169737 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java @@ -0,0 +1,12 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models; + +public record CommunityAggregatesResponse(Long id, + int subscribers, + int posts, + int comments, + int activeDailyUsers, + int activeWeeklyUsers, + int activeMonthlyUsers, + int activeHalfYearlyUsers) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityView.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityView.java index f56aa572..4998680e 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityView.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityView.java @@ -5,8 +5,8 @@ import lombok.Builder; @Builder -public record CommunityView(CommunityResponse communityResponse, - CommunityAggregates communityAggregates, +public record CommunityView(CommunityResponse community, + CommunityAggregatesResponse communityAggregates, List moderators) { } From 171f0b7a2272e145cf22e68ded4cc7a35bdc6987 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Wed, 8 May 2024 22:00:51 +0200 Subject: [PATCH 023/115] Renamed some attributes --- .../v1/community/controllers/SublinksCommunityController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index 71139c11..abd8f020 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -120,7 +120,7 @@ public List index( communityRepository.allCommunitiesBySearchCriteria(communitySearchCriteria) .forEach(community -> communities.add(CommunityView.builder() - .communityResponse(conversionService.convert(community, CommunityResponse.class)) + .community(conversionService.convert(community, CommunityResponse.class)) .communityAggregates(conversionService.convert(community.getCommunityAggregate(), CommunityAggregatesResponse.class)) .moderators(linkPersonCommunityService.getPersonsFromCommunityAndListTypes(community, @@ -142,7 +142,7 @@ public CommunityView show(@PathVariable final String key) { try { return communityRepository.findCommunityByTitleSlug(key) .map(comm -> CommunityView.builder() - .communityResponse(conversionService.convert(comm, CommunityResponse.class)) + .community(conversionService.convert(comm, CommunityResponse.class)) .communityAggregates(conversionService.convert(comm.getCommunityAggregate(), CommunityAggregatesResponse.class)) .moderators(linkPersonCommunityService.getPersonsFromCommunityAndListTypes(comm, From 95a7861d55e71621b7a1ca442935e60a1818a4d0 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Fri, 10 May 2024 11:15:46 +0200 Subject: [PATCH 024/115] Revert "Renamed some attributes" This reverts commit a76268e1 --- .../v1/community/models/CommunityAggregates.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java new file mode 100644 index 00000000..a6dacc96 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java @@ -0,0 +1,12 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models; + +public record CommunityAggregates(Long id, + int subscribers, + int posts, + int comments, + int activeDailyUsers, + int activeWeeklyUsers, + int activeMonthlyUsers, + int activeHalfYearlyUsers) { + +} From 94d73720e90fb38814be88d9a2b446fd4e7e012f Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Fri, 10 May 2024 11:15:46 +0200 Subject: [PATCH 025/115] Revert "Fixed a hickup" This reverts commit 05a030bc70ed10a859809aa0af00645149b09496. --- .../api/lemmy/v3/site/controllers/SiteController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java index 51010492..2341d032 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java @@ -68,7 +68,8 @@ public class SiteController extends AbstractLemmyApiController { private final MyUserInfoService myUserInfoService; private final RolePermissionService rolePermissionService; private final InstanceConfigService instanceConfigService; - private final SlurFilterService slurFilterService; + private final SlurFil List languages +terService slurFilterService; private final AnnouncementRepository announcementRepository; private final ConversionService conversionService; private final LemmyPersonService lemmyPersonService; From a15c903a989ba27e10ccdc70e3e9240547f4f1a4 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Fri, 10 May 2024 11:15:46 +0200 Subject: [PATCH 026/115] Revert "Add CommunityView and CommunityAggregates models" This reverts commit 0d47e472c494a7fa404ce7ef0871f022351a74e3. --- .../lemmy/v3/site/controllers/SiteController.java | 3 +-- .../v1/community/models/CommunityAggregates.java | 12 ------------ 2 files changed, 1 insertion(+), 14 deletions(-) delete mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java index 2341d032..51010492 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java @@ -68,8 +68,7 @@ public class SiteController extends AbstractLemmyApiController { private final MyUserInfoService myUserInfoService; private final RolePermissionService rolePermissionService; private final InstanceConfigService instanceConfigService; - private final SlurFil List languages -terService slurFilterService; + private final SlurFilterService slurFilterService; private final AnnouncementRepository announcementRepository; private final ConversionService conversionService; private final LemmyPersonService lemmyPersonService; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java deleted file mode 100644 index a6dacc96..00000000 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.community.models; - -public record CommunityAggregates(Long id, - int subscribers, - int posts, - int comments, - int activeDailyUsers, - int activeWeeklyUsers, - int activeMonthlyUsers, - int activeHalfYearlyUsers) { - -} From 9a5e686217da3919149ba6eca5a65597e8784bc7 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Sat, 11 May 2024 17:28:56 +0200 Subject: [PATCH 027/115] Revert "Renamed some attributes" This reverts commit 171f0b7a2272e145cf22e68ded4cc7a35bdc6987. --- .../v1/community/controllers/SublinksCommunityController.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index abd8f020..71139c11 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -120,7 +120,7 @@ public List index( communityRepository.allCommunitiesBySearchCriteria(communitySearchCriteria) .forEach(community -> communities.add(CommunityView.builder() - .community(conversionService.convert(community, CommunityResponse.class)) + .communityResponse(conversionService.convert(community, CommunityResponse.class)) .communityAggregates(conversionService.convert(community.getCommunityAggregate(), CommunityAggregatesResponse.class)) .moderators(linkPersonCommunityService.getPersonsFromCommunityAndListTypes(community, @@ -142,7 +142,7 @@ public CommunityView show(@PathVariable final String key) { try { return communityRepository.findCommunityByTitleSlug(key) .map(comm -> CommunityView.builder() - .community(conversionService.convert(comm, CommunityResponse.class)) + .communityResponse(conversionService.convert(comm, CommunityResponse.class)) .communityAggregates(conversionService.convert(comm.getCommunityAggregate(), CommunityAggregatesResponse.class)) .moderators(linkPersonCommunityService.getPersonsFromCommunityAndListTypes(comm, From bddbff7d9092c2d61cabbbb051fd2b8287b94c78 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Sat, 11 May 2024 17:28:56 +0200 Subject: [PATCH 028/115] Revert "Renamed some attributes" This reverts commit a76268e1109ce7c7521f88ad88e345239bf93118. --- .../controllers/SublinksCommunityController.java | 6 +++--- .../v1/community/models/CommunityAggregates.java | 12 ++++++++++++ .../models/CommunityAggregatesResponse.java | 12 ------------ .../sublinks/v1/community/models/CommunityView.java | 4 ++-- 4 files changed, 17 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java delete mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index 71139c11..10d7c975 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -5,7 +5,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; -import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityAggregatesResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityAggregates; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityView; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; @@ -122,7 +122,7 @@ public List index( .forEach(community -> communities.add(CommunityView.builder() .communityResponse(conversionService.convert(community, CommunityResponse.class)) .communityAggregates(conversionService.convert(community.getCommunityAggregate(), - CommunityAggregatesResponse.class)) + CommunityAggregates.class)) .moderators(linkPersonCommunityService.getPersonsFromCommunityAndListTypes(community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) .stream() @@ -144,7 +144,7 @@ public CommunityView show(@PathVariable final String key) { .map(comm -> CommunityView.builder() .communityResponse(conversionService.convert(comm, CommunityResponse.class)) .communityAggregates(conversionService.convert(comm.getCommunityAggregate(), - CommunityAggregatesResponse.class)) + CommunityAggregates.class)) .moderators(linkPersonCommunityService.getPersonsFromCommunityAndListTypes(comm, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) .stream() diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java new file mode 100644 index 00000000..a6dacc96 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java @@ -0,0 +1,12 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models; + +public record CommunityAggregates(Long id, + int subscribers, + int posts, + int comments, + int activeDailyUsers, + int activeWeeklyUsers, + int activeMonthlyUsers, + int activeHalfYearlyUsers) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java deleted file mode 100644 index c8169737..00000000 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.community.models; - -public record CommunityAggregatesResponse(Long id, - int subscribers, - int posts, - int comments, - int activeDailyUsers, - int activeWeeklyUsers, - int activeMonthlyUsers, - int activeHalfYearlyUsers) { - -} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityView.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityView.java index 4998680e..f56aa572 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityView.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityView.java @@ -5,8 +5,8 @@ import lombok.Builder; @Builder -public record CommunityView(CommunityResponse community, - CommunityAggregatesResponse communityAggregates, +public record CommunityView(CommunityResponse communityResponse, + CommunityAggregates communityAggregates, List moderators) { } From 904475b03ff14362e740c5cd3f72e2adb294b576 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Sat, 11 May 2024 17:28:57 +0200 Subject: [PATCH 029/115] Revert "Fixed a hickup" This reverts commit 05a030bc70ed10a859809aa0af00645149b09496. --- .../api/lemmy/v3/site/controllers/SiteController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java index 51010492..2341d032 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java @@ -68,7 +68,8 @@ public class SiteController extends AbstractLemmyApiController { private final MyUserInfoService myUserInfoService; private final RolePermissionService rolePermissionService; private final InstanceConfigService instanceConfigService; - private final SlurFilterService slurFilterService; + private final SlurFil List languages +terService slurFilterService; private final AnnouncementRepository announcementRepository; private final ConversionService conversionService; private final LemmyPersonService lemmyPersonService; From ae145c4e68c7ab28aedf7961f23ea5331908bf3d Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Sat, 11 May 2024 17:28:57 +0200 Subject: [PATCH 030/115] Revert "Add CommunityView and CommunityAggregates models" This reverts commit 0d47e472c494a7fa404ce7ef0871f022351a74e3. --- .../v3/site/controllers/SiteController.java | 3 +- .../SublinksCommunityController.java | 46 ++++--------------- .../community/models/CommunityAggregates.java | 12 ----- .../v1/community/models/CommunityView.java | 12 ----- 4 files changed, 11 insertions(+), 62 deletions(-) delete mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java delete mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityView.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java index 2341d032..51010492 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/site/controllers/SiteController.java @@ -68,8 +68,7 @@ public class SiteController extends AbstractLemmyApiController { private final MyUserInfoService myUserInfoService; private final RolePermissionService rolePermissionService; private final InstanceConfigService instanceConfigService; - private final SlurFil List languages -terService slurFilterService; + private final SlurFilterService slurFilterService; private final AnnouncementRepository announcementRepository; private final ConversionService conversionService; private final LemmyPersonService lemmyPersonService; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index 10d7c975..5483c5f0 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -5,26 +5,20 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; -import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityAggregates; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityView; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.IndexCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; -import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.community.models.CommunitySearchCriteria; import com.sublinks.sublinksapi.community.repositories.CommunityRepository; import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; import com.sublinks.sublinksapi.person.entities.Person; -import com.sublinks.sublinksapi.person.enums.LinkPersonCommunityType; -import com.sublinks.sublinksapi.person.services.LinkPersonCommunityService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; 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 java.util.Optional; import lombok.AllArgsConstructor; @@ -50,13 +44,12 @@ public class SublinksCommunityController extends AbstractSublinksApiController { private final SublinksCommunityService sublinksCommunityService; private final ConversionService conversionService; private final LocalInstanceContext localInstanceContext; - private final LinkPersonCommunityService linkPersonCommunityService; @Operation(summary = "Get a list of communities") @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List index( + public List index( @RequestParam(required = false) final Optional indexCommunityParam, final SublinksJwtPerson sublinksJwtPerson) { @@ -116,45 +109,26 @@ public List index( final CommunitySearchCriteria communitySearchCriteria = criteria.build(); - List communities = new ArrayList<>(); - - communityRepository.allCommunitiesBySearchCriteria(communitySearchCriteria) - .forEach(community -> communities.add(CommunityView.builder() - .communityResponse(conversionService.convert(community, CommunityResponse.class)) - .communityAggregates(conversionService.convert(community.getCommunityAggregate(), - CommunityAggregates.class)) - .moderators(linkPersonCommunityService.getPersonsFromCommunityAndListTypes(community, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) - .stream() - .map(pers -> conversionService.convert(pers, PersonResponse.class)) - .toList()) - .build())); - - return communities; + return communityRepository.allCommunitiesBySearchCriteria(communitySearchCriteria) + .stream() + .map(community -> conversionService.convert(community, CommunityResponse.class)) + .toList(); + } @Operation(summary = "Get a specific community") @GetMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommunityView show(@PathVariable final String key) { + public CommunityResponse show(@PathVariable final String key) { try { return communityRepository.findCommunityByTitleSlug(key) - .map(comm -> CommunityView.builder() - .communityResponse(conversionService.convert(comm, CommunityResponse.class)) - .communityAggregates(conversionService.convert(comm.getCommunityAggregate(), - CommunityAggregates.class)) - .moderators(linkPersonCommunityService.getPersonsFromCommunityAndListTypes(comm, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) - .stream() - .map(pers -> conversionService.convert(pers, PersonResponse.class)) - .toList()) - .build()) + .map(comm -> conversionService.convert(comm, CommunityResponse.class)) .orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Community not found")); } catch (Exception e) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "community_error"); + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Community not found"); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java deleted file mode 100644 index a6dacc96..00000000 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregates.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.community.models; - -public record CommunityAggregates(Long id, - int subscribers, - int posts, - int comments, - int activeDailyUsers, - int activeWeeklyUsers, - int activeMonthlyUsers, - int activeHalfYearlyUsers) { - -} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityView.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityView.java deleted file mode 100644 index f56aa572..00000000 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityView.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.community.models; - -import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; -import java.util.List; -import lombok.Builder; - -@Builder -public record CommunityView(CommunityResponse communityResponse, - CommunityAggregates communityAggregates, - List moderators) { - -} From 19a10ecd3b62a2ac89ec794ef02b5232d3428b4d Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Sat, 11 May 2024 17:39:17 +0200 Subject: [PATCH 031/115] Renamed some attributes --- ...ublinksCommunityAggregationController.java | 44 +++++++++++++++++++ .../SublinksCommunityAggregatesResponse.java | 26 +++++++++++ .../models/CommunityAggregatesResponse.java | 12 +++++ .../CommunityAggregateRepository.java | 4 ++ .../services/LanguageServiceUnitTests.java | 13 +++--- 5 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityAggregatesResponse.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java new file mode 100644 index 00000000..069131bc --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java @@ -0,0 +1,44 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityAggregatesResponse; +import com.sublinks.sublinksapi.community.entities.CommunityAggregate; +import com.sublinks.sublinksapi.community.repositories.CommunityAggregateRepository; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.ConversionService; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +@RestController +@AllArgsConstructor +@RequestMapping("/api/v1/community/{key}") +@Tag(name = "Community", description = "Community API") +public class SublinksCommunityAggregationController extends AbstractSublinksApiController { + + private final CommunityAggregateRepository communityAggregateRepository; + private final ConversionService conversionService; + + @Operation(summary = "Get a community aggregate") + @GetMapping("/aggregate") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public CommunityAggregatesResponse show(@PathVariable final String key) + { + + final CommunityAggregate communityAggregate = communityAggregateRepository.findByCommunityKey( + key); + if (communityAggregate == null) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found"); + } + + return conversionService.convert(communityAggregate, CommunityAggregatesResponse.class); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityAggregatesResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityAggregatesResponse.java new file mode 100644 index 00000000..84ee012e --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityAggregatesResponse.java @@ -0,0 +1,26 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.mappers; + +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityAggregatesResponse; +import com.sublinks.sublinksapi.community.entities.CommunityAggregate; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +public abstract class SublinksCommunityAggregatesResponse implements + Converter { + + @Override + @Mapping(target = "key", source = "communityAggregate.id") + @Mapping(target = "subscriberCount", source = "communityAggregate.subscriberCount") + @Mapping(target = "postCount", source = "communityAggregate.postCount") + @Mapping(target = "commentCount", source = "communityAggregate.commentCount") + @Mapping(target = "activeDailyUserCount", source = "communityAggregate.activeDailyUserCount") + @Mapping(target = "activeWeeklyUserCount", source = "communityAggregate.activeWeeklyUserCount") + @Mapping(target = "activeMonthlyUserCount", source = "communityAggregate.activeMonthlyUserCount") + @Mapping(target = "activeHalfYearUserCount", source = "communityAggregate.activeHalfYearUserCount") + public abstract CommunityAggregatesResponse convert( + @Nullable CommunityAggregate communityAggregate); +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java new file mode 100644 index 00000000..8da6a326 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java @@ -0,0 +1,12 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models; + +public record CommunityAggregatesResponse(String key, + String subscriberCount, + String postCount, + String commentCount, + String activeDailyUserCount, + String activeWeeklyUserCount, + String activeMonthlyUserCount, + String activeHalfYearUserCount) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityAggregateRepository.java b/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityAggregateRepository.java index 549d0685..22df87b1 100644 --- a/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityAggregateRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityAggregateRepository.java @@ -2,7 +2,11 @@ import com.sublinks.sublinksapi.community.entities.CommunityAggregate; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; public interface CommunityAggregateRepository extends JpaRepository { + @Query("SELECT ca FROM CommunityAggregate ca WHERE ca.community.titleSlug = :key") + CommunityAggregate findByCommunityKey(String key); + } diff --git a/src/test/java/com/sublinks/sublinksapi/language/services/LanguageServiceUnitTests.java b/src/test/java/com/sublinks/sublinksapi/language/services/LanguageServiceUnitTests.java index d5e45c24..16d406c4 100644 --- a/src/test/java/com/sublinks/sublinksapi/language/services/LanguageServiceUnitTests.java +++ b/src/test/java/com/sublinks/sublinksapi/language/services/LanguageServiceUnitTests.java @@ -52,9 +52,8 @@ void givenInstance_whenInstanceLanguageIds_thenReturnListOfIds() { List expectedList = Arrays.asList(1L, 2L); - assertTrue(expectedList.size() == discussionLanguages.size() - && expectedList.containsAll(discussionLanguages) - && discussionLanguages.containsAll(expectedList), + assertTrue(expectedList.size() == discussionLanguages.size() && expectedList.containsAll( + discussionLanguages) && discussionLanguages.containsAll(expectedList), "List of language ids returned did not match expected"); } @@ -65,14 +64,16 @@ void givenCollectionOfLanguageIds_whenLanguageIdsToEntity_thenReturnListOfLangua Optional optionalEnglish = Optional.of(english); Optional optionalAfrikaans = Optional.of(afrikaans); - Mockito.when(languageRepository.findById(1L)).thenReturn(optionalEnglish); - Mockito.when(languageRepository.findById(2L)).thenReturn(optionalAfrikaans); + Mockito.when(languageRepository.findById(1L)) + .thenReturn(optionalEnglish); + Mockito.when(languageRepository.findById(2L)) + .thenReturn(optionalAfrikaans); LanguageService languageService = new LanguageService(languageRepository); List languages = languageService.languageIdsToEntity(languageIds); assertEquals(2, languages.size(), - "Number of languagesKeys instances returned did not match expected"); + "Number of languages instances returned did not match expected"); } } From 92b6f1c000534619639a2eda92ead06efde535d2 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Sat, 11 May 2024 21:58:00 +0200 Subject: [PATCH 032/115] Add community moderation features Designed and implemented moderation functionalities in the Sublinks community. This includes obtaining community moderators, adding and deleting moderators, and the capacity to ban users. Fixed the API paths for some services and introduced new types and mapper classes to align with the new moderation features. --- ...ublinksCommunityAggregationController.java | 31 ++--- ...SublinksCommunityModerationController.java | 122 ++++++++++++++++++ .../enums/SublinksPersonCommunityType.java | 10 ++ .../mappers/SublinksCommunityTypeMapper.java | 23 ++++ .../SublinksPersonCommunityTypeMapper.java | 28 ++++ .../models/Moderation/CommunityBanPerson.java | 8 ++ .../CommunityModeratorResponse.java | 11 ++ .../services/SublinksCommunityService.java | 13 ++ .../person/repositories/PersonRepository.java | 4 +- .../services/LinkPersonCommunityService.java | 44 +++++-- 10 files changed, 263 insertions(+), 31 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/enums/SublinksPersonCommunityType.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityTypeMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksPersonCommunityTypeMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityModeratorResponse.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java index 069131bc..ed94f80c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java @@ -19,26 +19,23 @@ @RestController @AllArgsConstructor -@RequestMapping("/api/v1/community/{key}") -@Tag(name = "Community", description = "Community API") +@RequestMapping("/api/v1/community/{key}/aggregate") +@Tag(name = "Community", description = "Community Aggregation API") public class SublinksCommunityAggregationController extends AbstractSublinksApiController { - private final CommunityAggregateRepository communityAggregateRepository; - private final ConversionService conversionService; + private final CommunityAggregateRepository communityAggregateRepository; + private final ConversionService conversionService; - @Operation(summary = "Get a community aggregate") - @GetMapping("/aggregate") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommunityAggregatesResponse show(@PathVariable final String key) - { + @Operation(summary = "Get a community aggregate") + @GetMapping + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public CommunityAggregatesResponse show(@PathVariable final String key) { - final CommunityAggregate communityAggregate = communityAggregateRepository.findByCommunityKey( - key); - if (communityAggregate == null) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found"); - } + final CommunityAggregate communityAggregate = communityAggregateRepository.findByCommunityKey(key); + if (communityAggregate == null) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found"); + } - return conversionService.convert(communityAggregate, CommunityAggregatesResponse.class); - } + return conversionService.convert(communityAggregate, CommunityAggregatesResponse.class); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java new file mode 100644 index 00000000..57a7ba71 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -0,0 +1,122 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityModeratorResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; +import com.sublinks.sublinksapi.community.entities.Community; +import com.sublinks.sublinksapi.community.repositories.CommunityRepository; +import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.person.enums.LinkPersonCommunityType; +import com.sublinks.sublinksapi.person.repositories.PersonRepository; +import com.sublinks.sublinksapi.person.services.LinkPersonCommunityService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.ConversionService; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; + +@RestController +@AllArgsConstructor +@RequestMapping("/api/v1/community/{key}/moderation") +@Tag(name = "Community", description = "Community Moderation API") +public class SublinksCommunityModerationController extends AbstractSublinksApiController { + + private final LinkPersonCommunityService linkPersonCommunityService; + private final SublinksCommunityService sublinksCommunityService; + private final CommunityRepository communityRepository; + private final PersonRepository personRepository; + private final ConversionService conversionService; + + @Operation(summary = "Get moderators of the community") + @GetMapping("/moderators") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List show(@PathVariable final String key) { + + return sublinksCommunityService.getCommunityModerators(key) + .stream() + .map(communityModerator -> conversionService.convert(communityModerator, + CommunityModeratorResponse.class)) + .toList(); + } + + @Operation(summary = "Add a moderator to the community") + @GetMapping("/moderator/add/{personKey}") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List add(@PathVariable final String key, + @PathVariable final String personKey, final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + + if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_add_moderator"); + } + + final Person newModerator = personRepository.findOneByName(personKey) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + + if (linkPersonCommunityService.hasAnyLinkOrAdmin(newModerator, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "person_already_moderator"); + } + + linkPersonCommunityService.addLink(newModerator, community, LinkPersonCommunityType.moderator); + + return sublinksCommunityService.getCommunityModerators(key) + .stream() + .map(communityModerator -> conversionService.convert(communityModerator, + CommunityModeratorResponse.class)) + .toList(); + } + + @Operation(summary = "Remove a moderator from the community") + @GetMapping("/moderator/remove/{personKey}") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List remove(@PathVariable final String key, + @PathVariable final String personKey) + { + + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + + final Person person = personRepository.findOneByName(personKey) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + + if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_remove_moderator"); + } + + linkPersonCommunityService.removeLink(person, community, LinkPersonCommunityType.moderator); + + return sublinksCommunityService.getCommunityModerators(key) + .stream() + .map(communityModerator -> conversionService.convert(communityModerator, + CommunityModeratorResponse.class)) + .toList(); + } + + @Operation(summary = "Ban a user from a community") + @GetMapping("/ban/{personKey}") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/enums/SublinksPersonCommunityType.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/enums/SublinksPersonCommunityType.java new file mode 100644 index 00000000..903b8520 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/enums/SublinksPersonCommunityType.java @@ -0,0 +1,10 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.enums; + +public enum SublinksPersonCommunityType { + owner, + moderator, + follower, + pending_follow, + blocked, + banned +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityTypeMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityTypeMapper.java new file mode 100644 index 00000000..8cb920ff --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityTypeMapper.java @@ -0,0 +1,23 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.mappers; + +import com.sublinks.sublinksapi.api.sublinks.v1.community.enums.SublinksPersonCommunityType; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityModeratorResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.mappers.SublinksPersonMapper; +import com.sublinks.sublinksapi.person.entities.LinkPersonCommunity; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksPersonMapper.class, + SublinksPersonCommunityType.class}) +public abstract class SublinksCommunityTypeMapper implements + Converter { + + @Override + @Mapping(target = "person", source = "linkPersonCommunity.person") + @Mapping(target = "linkType", source = "linkPersonCommunity.linkType") + public abstract CommunityModeratorResponse convert( + @Nullable LinkPersonCommunity linkPersonCommunity); +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksPersonCommunityTypeMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksPersonCommunityTypeMapper.java new file mode 100644 index 00000000..37ecbb31 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksPersonCommunityTypeMapper.java @@ -0,0 +1,28 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.mappers; + +import com.sublinks.sublinksapi.api.sublinks.v1.community.enums.SublinksPersonCommunityType; +import com.sublinks.sublinksapi.api.sublinks.v1.languages.mappers.SublinksLanguageMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.utils.mappers.OptionalStringMapper; +import com.sublinks.sublinksapi.person.enums.LinkPersonCommunityType; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = { + SublinksLanguageMapper.class, OptionalStringMapper.class}) +public abstract class SublinksPersonCommunityTypeMapper implements + Converter { + + @Override + public SublinksPersonCommunityType convert( + @Nullable LinkPersonCommunityType linkPersonCommunityType) + { + + if (linkPersonCommunityType == null) { + return null; + } + return SublinksPersonCommunityType.valueOf(linkPersonCommunityType.name()); + + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java new file mode 100644 index 00000000..b92c93b5 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java @@ -0,0 +1,8 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; + +public record CommunityBanPerson( + String personId, + String reason, +) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityModeratorResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityModeratorResponse.java new file mode 100644 index 00000000..cf61de78 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityModeratorResponse.java @@ -0,0 +1,11 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; + +import com.sublinks.sublinksapi.api.sublinks.v1.community.enums.SublinksPersonCommunityType; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; +import lombok.Builder; + +@Builder +public record CommunityModeratorResponse(PersonResponse person, + SublinksPersonCommunityType linkType) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index f352d4fc..d03c650f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -11,6 +11,7 @@ import com.sublinks.sublinksapi.community.services.CommunityService; import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; import com.sublinks.sublinksapi.instance.repositories.InstanceRepository; +import com.sublinks.sublinksapi.person.entities.LinkPersonCommunity; import com.sublinks.sublinksapi.person.entities.Person; import com.sublinks.sublinksapi.person.enums.LinkPersonCommunityType; import com.sublinks.sublinksapi.person.services.LinkPersonCommunityService; @@ -124,6 +125,18 @@ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommu communityRepository.save(community); return conversionService.convert(community, CommunityResponse.class); + } + + public List getCommunityModerators(String key) { + + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + return linkPersonCommunityService.getLinkPersonCommunitiesByCommunityAndPersonAndLinkTypeIsIn( + community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) + .stream() + .toList(); } } diff --git a/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java b/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java index d9bd568b..d7125e34 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java @@ -2,8 +2,8 @@ import com.sublinks.sublinksapi.authorization.entities.Role; import com.sublinks.sublinksapi.person.entities.Person; -import java.util.List; import java.util.HashSet; +import java.util.List; import java.util.Optional; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; @@ -22,6 +22,8 @@ public interface PersonRepository extends JpaRepository { HashSet findAllByRole(Role role); + Optional findOneByName(String name); + @Query(value = "SELECT p FROM people p WHERE p.search_vector @@ to_tsquery('keyword', :keyword)", countQuery = "SELECT COUNT(p.id) FROM people p WHERE p.search_vector @@ to_tsquery('english', :keyword)", nativeQuery = true) List findAllByNameAndBiography(@Param("keyword") String keyword, Pageable pageable); } diff --git a/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java b/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java index f66c5beb..d3c16eab 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java @@ -31,7 +31,8 @@ public boolean hasLinkOrAdmin(Person person, Community community, LinkPersonComm } public boolean hasAnyLinkOrAdmin(Person person, Community community, - List types) { + List types) + { return RolePermissionService.isAdmin(person) || hasAnyLink(person, community, types); } @@ -43,8 +44,8 @@ public boolean hasLink(Person person, Community community, LinkPersonCommunityTy return linkPersonCommunity.isPresent(); } - public boolean hasAnyLink(Person person, Community community, - List types) { + public boolean hasAnyLink(Person person, Community community, List types) + { final List linkPersonCommunity = linkPersonCommunityRepository.getLinkPersonCommunityByCommunityAndPersonAndLinkTypeIsIn( community, person, types); @@ -54,10 +55,15 @@ public boolean hasAnyLink(Person person, Community community, @Transactional public void addLink(Person person, Community community, LinkPersonCommunityType type) { - final LinkPersonCommunity newLink = LinkPersonCommunity.builder().community(community).person( - person).linkType(type).build(); - person.getLinkPersonCommunity().add(newLink); - community.getLinkPersonCommunity().add(newLink); + final LinkPersonCommunity newLink = LinkPersonCommunity.builder() + .community(community) + .person(person) + .linkType(type) + .build(); + person.getLinkPersonCommunity() + .add(newLink); + community.getLinkPersonCommunity() + .add(newLink); linkPersonCommunityRepository.save(newLink); linkPersonCommunityCreatedPublisher.publish(newLink); } @@ -70,10 +76,12 @@ public void removeLink(Person person, Community community, LinkPersonCommunityTy if (linkPersonCommunity.isEmpty()) { return; } - person.getLinkPersonCommunity().removeIf( - l -> Objects.equals(l.getId(), linkPersonCommunity.get().getId())); - community.getLinkPersonCommunity().removeIf( - l -> Objects.equals(l.getId(), linkPersonCommunity.get().getId())); + person.getLinkPersonCommunity() + .removeIf(l -> Objects.equals(l.getId(), linkPersonCommunity.get() + .getId())); + community.getLinkPersonCommunity() + .removeIf(l -> Objects.equals(l.getId(), linkPersonCommunity.get() + .getId())); linkPersonCommunityRepository.delete(linkPersonCommunity.get()); linkPersonCommunityDeletedPublisher.publish(linkPersonCommunity.get()); } @@ -91,12 +99,22 @@ public Collection getPersonLinkByType(Person person, LinkPersonCommun } public Collection getPersonsFromCommunityAndListTypes(Community community, - List types) { + List types) + { Collection linkPersonCommunities = linkPersonCommunityRepository.getLinkPersonCommunitiesByCommunityAndLinkTypeIsIn( community, types); - return linkPersonCommunities.stream().map(LinkPersonCommunity::getPerson).toList(); + return linkPersonCommunities.stream() + .map(LinkPersonCommunity::getPerson) + .toList(); } + public Collection getLinkPersonCommunitiesByCommunityAndPersonAndLinkTypeIsIn( + Community community, List types) + { + + return linkPersonCommunityRepository.getLinkPersonCommunitiesByCommunityAndLinkTypeIsIn( + community, types); + } } From bc0c69f485ea1cac05437255a2732e73b425479f Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Mon, 13 May 2024 11:18:02 +0200 Subject: [PATCH 033/115] Implement user ban and unban functionality The commit introduces methods to ban and unban users from a community. To implement this change, a new import for PersonRepository was added to the SublinksCommunityService class. New endpoints for "ban" and "unban" were added to the SublinksCommunityModerationController. The findOneByName method in PersonRepository was replaced with findOneByNameIgnoreCase to ensure the case insensitivity of username in ban/unban requests. In addition, The LinkPersonCommunityService class was updated to handle the removal of any user link in case of banning. --- ...SublinksCommunityModerationController.java | 51 +++++++++++- .../services/SublinksCommunityService.java | 77 ++++++++++++++++++- .../person/repositories/PersonRepository.java | 2 - .../services/LinkPersonCommunityService.java | 19 +++++ 4 files changed, 143 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java index 57a7ba71..9638dbd3 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -4,6 +4,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityModeratorResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.community.entities.Community; import com.sublinks.sublinksapi.community.repositories.CommunityRepository; import com.sublinks.sublinksapi.person.entities.Person; @@ -68,7 +69,7 @@ public List add(@PathVariable final String key, throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_add_moderator"); } - final Person newModerator = personRepository.findOneByName(personKey) + final Person newModerator = personRepository.findOneByNameIgnoreCase(personKey) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); if (linkPersonCommunityService.hasAnyLinkOrAdmin(newModerator, community, @@ -97,7 +98,7 @@ public List remove(@PathVariable final String key, .orElseThrow( () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - final Person person = personRepository.findOneByName(personKey) + final Person person = personRepository.findOneByNameIgnoreCase(personKey) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, @@ -118,5 +119,51 @@ public List remove(@PathVariable final String key, @GetMapping("/ban/{personKey}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PersonResponse ban(@PathVariable final String key, @PathVariable final String personKey, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + + if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_ban_user"); + } + + final Person bannedPerson = sublinksCommunityService.banPerson(key, personKey, person); + + // @todo: Modlog + + return conversionService.convert(bannedPerson, PersonResponse.class); + } + + @Operation(summary = "Unban a user from a community") + @GetMapping("/unban/{personKey}") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PersonResponse unban(@PathVariable final String key, @PathVariable final String personKey, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + + if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_unban_user"); + } + + final Person unbannedPerson = sublinksCommunityService.unbanPerson(key, personKey, person); + // @todo: Modlog + + return conversionService.convert(unbannedPerson, PersonResponse.class); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index d03c650f..35753af1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -14,6 +14,7 @@ import com.sublinks.sublinksapi.person.entities.LinkPersonCommunity; import com.sublinks.sublinksapi.person.entities.Person; import com.sublinks.sublinksapi.person.enums.LinkPersonCommunityType; +import com.sublinks.sublinksapi.person.repositories.PersonRepository; import com.sublinks.sublinksapi.person.services.LinkPersonCommunityService; import java.util.List; import java.util.Optional; @@ -34,6 +35,7 @@ public class SublinksCommunityService { private final RolePermissionService rolePermissionService; private final InstanceRepository instanceRepository; private final LinkPersonCommunityService linkPersonCommunityService; + private final PersonRepository personRepository; public CommunityResponse createCommunity(CreateCommunity createCommunity, Person person) { @@ -134,9 +136,80 @@ public List getCommunityModerators(String key) { () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); return linkPersonCommunityService.getLinkPersonCommunitiesByCommunityAndPersonAndLinkTypeIsIn( - community, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) + community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) .stream() .toList(); } + + /** + * Ban a person from a community. + * + * @param key the community key + * @param personKey the person key + * @param person the person making the request + * @return the banned person + * @throws ResponseStatusException if the community or person is not found, or if the person is + * not authorized to ban (details in the exception message) + */ + public Person banPerson(String key, String personKey, Person person) { + + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + + final Person personToBan = personRepository.findOneByNameIgnoreCase(personKey) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + + if (linkPersonCommunityService.hasAnyLinkOrAdmin(personToBan, community, + List.of(LinkPersonCommunityType.banned))) { + return personToBan; + } + + if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_ban_person"); + } + + linkPersonCommunityService.removeAnyLink(personToBan, community, + List.of(LinkPersonCommunityType.owner, LinkPersonCommunityType.moderator, + LinkPersonCommunityType.follower, LinkPersonCommunityType.pending_follow)); + + linkPersonCommunityService.addLink(personToBan, community, LinkPersonCommunityType.banned); + + return personToBan; + } + + /** + * Unban a person from a community. + * + * @param key the community key + * @param personKey the person key + * @param person the person making the request + * @return the unbanned person + * @throws ResponseStatusException if the community or person is not found, or if the person is + * not authorized to unban (details in the exception message) + */ + public Person unbanPerson(String key, String personKey, Person person) { + + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + + final Person personToUnban = personRepository.findOneByNameIgnoreCase(personKey) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + + if (!linkPersonCommunityService.hasAnyLinkOrAdmin(personToUnban, community, + List.of(LinkPersonCommunityType.banned))) { + return personToUnban; + } + + if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_unban_person"); + } + + linkPersonCommunityService.removeLink(personToUnban, community, LinkPersonCommunityType.banned); + + return personToUnban; + } } diff --git a/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java b/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java index d7125e34..6ab329d4 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java @@ -22,8 +22,6 @@ public interface PersonRepository extends JpaRepository { HashSet findAllByRole(Role role); - Optional findOneByName(String name); - @Query(value = "SELECT p FROM people p WHERE p.search_vector @@ to_tsquery('keyword', :keyword)", countQuery = "SELECT COUNT(p.id) FROM people p WHERE p.search_vector @@ to_tsquery('english', :keyword)", nativeQuery = true) List findAllByNameAndBiography(@Param("keyword") String keyword, Pageable pageable); } diff --git a/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java b/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java index d3c16eab..ccdbd1f3 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java @@ -86,6 +86,25 @@ public void removeLink(Person person, Community community, LinkPersonCommunityTy linkPersonCommunityDeletedPublisher.publish(linkPersonCommunity.get()); } + public void removeAnyLink(Person person, Community community, List types) + { + + final List linkPersonCommunity = linkPersonCommunityRepository.getLinkPersonCommunityByCommunityAndPersonAndLinkTypeIsIn( + community, person, types); + if (linkPersonCommunity.isEmpty()) { + return; + } + + linkPersonCommunity.forEach(l -> { + person.getLinkPersonCommunity() + .removeIf(link -> Objects.equals(link.getId(), l.getId())); + community.getLinkPersonCommunity() + .removeIf(link -> Objects.equals(link.getId(), l.getId())); + linkPersonCommunityRepository.delete(l); + linkPersonCommunityDeletedPublisher.publish(l); + }); + } + public Collection getPersonLinkByType(Person person, LinkPersonCommunityType type) { Collection linkPersonCommunities = linkPersonCommunityRepository.getLinkPersonCommunitiesByPersonAndLinkType( From 99d0a69629dba4507e4163b6ceeda52fb3af30ac Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Fri, 17 May 2024 10:05:13 +0200 Subject: [PATCH 034/115] Add CRUD features for communities and initial post functionality In this update, several features were added to the community services including the ability to create, update, and retrieve communities. In addition, functionality to manage moderation including getting community moderators and banning/unbanning persons was introduced. Also introduced initial pieces for posting functionality with new models and mapping methods. Necessary documentation for these features was added in addition to minor formatting changes. --- .../SublinksCommunityController.java | 24 ++++---- ...SublinksCommunityModerationController.java | 5 +- .../v1/community/models/IndexCommunity.java | 12 ++-- .../models/Moderation/CommunityBanPerson.java | 5 +- .../services/SublinksCommunityService.java | 25 ++++++++ .../controllers/SublinksPostController.java | 4 +- .../mappers/SublinksLinkMetaDataMapper.java | 24 ++++++++ .../SublinksPostAggregationMapper.java | 26 ++++++++ .../v1/post/mappers/SublinksPostMapper.java | 59 +++++++++++++++++++ .../sublinks/v1/post/models/LinkMetaData.java | 12 ++++ .../v1/post/models/PostAggregateResponse.java | 11 ++++ .../sublinks/v1/post/models/PostResponse.java | 27 +++++++++ 12 files changed, 210 insertions(+), 24 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksLinkMetaDataMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostAggregationMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/LinkMetaData.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostAggregateResponse.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostResponse.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index 5483c5f0..41013a63 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -47,8 +47,8 @@ public class SublinksCommunityController extends AbstractSublinksApiController { @Operation(summary = "Get a list of communities") @GetMapping - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public List index( @RequestParam(required = false) final Optional indexCommunityParam, final SublinksJwtPerson sublinksJwtPerson) @@ -90,7 +90,9 @@ public List index( } } - boolean showNsfw = indexCommunityForm.showNsfw() != null && indexCommunityForm.showNsfw(); + boolean showNsfw = (indexCommunityForm.showNsfw() != null && indexCommunityForm.showNsfw()) || ( + person.isPresent() && person.get() + .isShowNsfw()); final CommunitySearchCriteria.CommunitySearchCriteriaBuilder criteria = CommunitySearchCriteria.builder() .perPage(indexCommunityForm.limit()) @@ -118,8 +120,8 @@ public List index( @Operation(summary = "Get a specific community") @GetMapping("/{key}") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse show(@PathVariable final String key) { try { @@ -134,8 +136,8 @@ public CommunityResponse show(@PathVariable final String key) { @Operation(summary = "Create a new community") @PostMapping - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse create(@RequestBody @Valid final CreateCommunity createCommunity, final SublinksJwtPerson sublinksJwtPerson) { @@ -147,8 +149,8 @@ public CommunityResponse create(@RequestBody @Valid final CreateCommunity create @Operation(summary = "Update an community") @PostMapping("/{key}") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse update(@PathVariable String key, @RequestBody @Valid UpdateCommunity updateCommunityForm, final SublinksJwtPerson principal) { @@ -160,8 +162,8 @@ public CommunityResponse update(@PathVariable String key, @Operation(summary = "Purge an community") @DeleteMapping("/{key}") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + @ApiResponses( + value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public void delete(@PathVariable String key) { // @TODO: implement } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java index 9638dbd3..e94e27c7 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -2,6 +2,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityBanPerson; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityModeratorResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; @@ -120,7 +121,7 @@ public List remove(@PathVariable final String key, @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public PersonResponse ban(@PathVariable final String key, @PathVariable final String personKey, - final SublinksJwtPerson sublinksJwtPerson) + CommunityBanPerson communityBanPersonForm, final SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); @@ -146,7 +147,7 @@ public PersonResponse ban(@PathVariable final String key, @PathVariable final St @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public PersonResponse unban(@PathVariable final String key, @PathVariable final String personKey, - final SublinksJwtPerson sublinksJwtPerson) + CommunityBanPerson communityBanPersonForm, final SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java index 6443aa05..32cf8c3a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java @@ -1,16 +1,16 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models; -import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import lombok.Builder; -import java.util.List; -import java.util.Optional; @Builder public record IndexCommunity(String search, - SortType sortType, - SublinksListingType sublinksListingType, - Boolean showNsfw, + @Schema(description = "Sort type", example = "Hot", requiredMode = RequiredMode.NOT_REQUIRED) SortType sortType, + @Schema(description = "Sublinks listing type", example = "All", requiredMode = RequiredMode.NOT_REQUIRED) SublinksListingType sublinksListingType, + @Schema(description = "Show NSFW", example = "false", requiredMode = RequiredMode.NOT_REQUIRED) Boolean showNsfw, int limit, int page) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java index b92c93b5..955fe04f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java @@ -1,8 +1,5 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; -public record CommunityBanPerson( - String personId, - String reason, -) { +public record CommunityBanPerson(String reason) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index 35753af1..8e43ffa9 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -37,6 +37,15 @@ public class SublinksCommunityService { private final LinkPersonCommunityService linkPersonCommunityService; private final PersonRepository personRepository; + /** + * Creates a new community based on the provided community data and person. + * + * @param createCommunity The data for the new community. + * @param person The person creating the community. + * @return The response containing the newly created community. + * @throws ResponseStatusException If the community slug already exists or if the person is not + * authorized to create a community. + */ public CommunityResponse createCommunity(CreateCommunity createCommunity, Person person) { final Optional oldCommunity = communityRepository.findCommunityByTitleSlug( @@ -66,6 +75,15 @@ public CommunityResponse createCommunity(CreateCommunity createCommunity, Person return conversionService.convert(community, CommunityResponse.class); } + /** + * Updates a community based on the provided key, update form, and person. + * + * @param key The key of the community to update. + * @param updateCommunityForm The update form containing the new community data. + * @param person The person performing the update. + * @return The updated community response. + * @throws ResponseStatusException If the community is not found, or the person is not authorized to perform the update. + */ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommunityForm, Person person) { @@ -129,6 +147,13 @@ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommu return conversionService.convert(community, CommunityResponse.class); } + /** + * Retrieves the moderators of a community. + * + * @param key The key of the community. + * @return The list of moderators in the community. + * @throws ResponseStatusException If the community is not found. + */ public List getCommunityModerators(String key) { final Community community = communityRepository.findCommunityByTitleSlug(key) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java index 80504d63..592539f4 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java @@ -15,13 +15,15 @@ @RequestMapping("api/v1/post") @Tag(name = "Post", description = "Post API") public class SublinksPostController extends AbstractSublinksApiController { - @Operation(summary = "Get a list of posts") + + @Operation(summary = "Get a list of posts") @GetMapping @ApiResponses(value = { // TODO: add responses }) public void index() { // TODO: implement + // @todo: implement cursor based pagination } @Operation(summary = "Get a specific post") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksLinkMetaDataMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksLinkMetaDataMapper.java new file mode 100644 index 00000000..672acbe5 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksLinkMetaDataMapper.java @@ -0,0 +1,24 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.mappers; + +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.LinkMetaData; +import com.sublinks.sublinksapi.post.entities.Post; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +public abstract class SublinksLinkMetaDataMapper implements Converter { + + @Override + @Mapping(target = "key", source = "post.linkUrl") + @Mapping(target = "linkUrl", source = "post.linkUrl") + @Mapping(target = "linkTitle", source = "post.linkTitle") + @Mapping(target = "linkDescription", source = "post.linkDescription") + @Mapping(target = "linkThumbnailUrl", source = "post.linkThumbnailUrl") + @Mapping(target = "LinkVideoUrl", source = "post.linkVideoUrl") + public abstract LinkMetaData convert(@Nullable Post post); + + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostAggregationMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostAggregationMapper.java new file mode 100644 index 00000000..996f998b --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostAggregationMapper.java @@ -0,0 +1,26 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.mappers; + +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostAggregateResponse; +import com.sublinks.sublinksapi.post.entities.PostAggregate; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +public abstract class SublinksPostAggregationMapper implements + Converter { + + @Override + @Mapping(target = "key", source = "postAggregate.id") + @Mapping(target = "commentCount", source = "postAggregate.commentCount") + @Mapping(target = "downvoteCount", source = "postAggregate.downvoteCount") + @Mapping(target = "upvoteCount", source = "postAggregate.upvoteCount") + @Mapping(target = "score", source = "postAggregate.score") + @Mapping(target = "hotScore", source = "postAggregate.hotRank") + @Mapping(target = "controversyScore", source = "postAggregate.controversyScore") + public abstract PostAggregateResponse convert(@Nullable PostAggregate postAggregate); + + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostMapper.java new file mode 100644 index 00000000..2392ef25 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostMapper.java @@ -0,0 +1,59 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.mappers; + +import com.sublinks.sublinksapi.api.sublinks.v1.community.mappers.SublinksCommunityMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.person.mappers.SublinksPersonMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; +import com.sublinks.sublinksapi.person.entities.LinkPersonPost; +import com.sublinks.sublinksapi.person.enums.LinkPersonPostType; +import com.sublinks.sublinksapi.post.entities.Post; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.Named; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = { + SublinksLinkMetaDataMapper.class, SublinksPostAggregationMapper.class, + SublinksPersonMapper.class, SublinksCommunityMapper.class}) +public abstract class SublinksPostMapper implements Converter { + + SublinksPostAggregationMapper sublinksPostAggregationMapper; + SublinksLinkMetaDataMapper sublinksLinkMetaDataMapper; + SublinksPersonMapper sublinksPersonMapper; + SublinksCommunityMapper sublinksCommunityMapper; + + @Override + @Mapping(target = "key", source = "post.titleSlug") + @Mapping(target = "title", source = "post.title") + @Mapping(target = "titleSlug", source = "post.titleSlug") + @Mapping(target = "body", source = "post.postBody") + @Mapping(target = "linkMetaData", source = "post") + @Mapping(target = "isLocal", source = "post.local") + @Mapping(target = "isDeleted", source = "post.deleted") + @Mapping(target = "isNsfw", source = "post.nsfw") + @Mapping(target = "isLocked", source = "post.locked") + @Mapping(target = "isFeatured", source = "post.featured") + @Mapping(target = "isFeaturedInCommunity", source = "post.featuredInCommunity") + @Mapping(target = "community", source = "post.community") + @Mapping(target = "creator", source = "post", qualifiedByName = "getCreator") + @Mapping(target = "postAggregate", source = "post.postAggregate") + @Mapping(target = "activityPubId", source = "post.activityPubId") + @Mapping(target = "publicKey", source = "post.publicKey") + @Mapping(target = "isRemoved", expression = "java(post.isRemoved())") + public abstract PostResponse convert(@Nullable Post post); + + + @Named("getCreator") + public PersonResponse getCreator(Post post) { + + for (LinkPersonPost linkPersonPost : post.getLinkPersonPost()) { + if (linkPersonPost.getLinkType() + .equals(LinkPersonPostType.creator)) { + return sublinksPersonMapper.convert(linkPersonPost.getPerson()); + } + } + return null; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/LinkMetaData.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/LinkMetaData.java new file mode 100644 index 00000000..4f33b40b --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/LinkMetaData.java @@ -0,0 +1,12 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.models; + +import lombok.Builder; + +@Builder +public record LinkMetaData(String linkUrl, + String linkTitle, + String linkDescription, + String linkThumbnailUrl, + String LinkVideoUrl) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostAggregateResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostAggregateResponse.java new file mode 100644 index 00000000..f0dfadfb --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostAggregateResponse.java @@ -0,0 +1,11 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.models; + +public record PostAggregateResponse(String key, + String commentCount, + String downvoteCount, + String upvoteCount, + String score, + String hotScore, + String controversyScore) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostResponse.java new file mode 100644 index 00000000..5cd8d46e --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostResponse.java @@ -0,0 +1,27 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.models; + +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; + +public record PostResponse(String key, + String title, + String titleSlug, + String body, + LinkMetaData linkMetaData, + String type, + Boolean isLocal, + Boolean isDeleted, + Boolean isRemoved, + Boolean isNsfw, + Boolean isLocked, + Boolean isFeatured, + Boolean isFeaturedInCommunity, + CommunityResponse community, + PersonResponse creator, + PostAggregateResponse postAggregate, + String activityPubId, + String publicKey, + String createdAt, + String updatedAt) { + +} From 4b873d12606cd88265f4b4c0d603a6f35e81e932 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Sun, 19 May 2024 12:32:31 +0200 Subject: [PATCH 035/115] Refactor UserController to improve person update logic This commit breaks down the original combined setters and condition checks in UserController when updating the Person object. This makes the code more readable and ensures that each field is properly checked before assignment. The code is now more maintainable, as each operation is clearly isolated. --- .../services/LemmyPersonMentionService.java | 12 +- .../SublinksAnnouncementController.java | 10 +- .../SublinksCommentController.java | 39 ++++-- .../v1/comment/models/CreateComment.java | 9 +- .../v1/comment/models/IndexComment.java | 15 ++- .../v1/comment/models/UpdateComment.java | 13 ++ .../services/SublinksCommentService.java | 117 +++++++++++++++++- .../v1/community/models/CreateCommunity.java | 6 +- .../v1/community/models/IndexCommunity.java | 4 +- .../v1/community/models/UpdateCommunity.java | 15 +-- .../services/SublinksCommunityService.java | 68 ++++------ .../SublinksInstanceController.java | 10 +- .../v1/person/models/IndexPerson.java | 4 +- .../services/SublinksPersonService.java | 28 +++++ .../controllers/SublinksPostController.java | 10 +- .../SublinksPrivatemessageController.java | 10 +- .../controllers/SublinksRolesController.java | 10 +- .../controllers/SublinksSearchController.java | 2 +- .../comment/models/CommentSearchCriteria.java | 20 ++- .../language/services/LanguageService.java | 9 ++ .../post/repositories/PostRepository.java | 2 + 21 files changed, 281 insertions(+), 132 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/UpdateComment.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/user/services/LemmyPersonMentionService.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/user/services/LemmyPersonMentionService.java index 4194f1c7..27ca5ff5 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/user/services/LemmyPersonMentionService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/user/services/LemmyPersonMentionService.java @@ -31,7 +31,8 @@ public PersonMentionView getPersonMentionView(PersonMention personMention) { final CommentAggregate commentAggregates = comment.getCommentAggregate(); final CommentLike commentLike = commentLikeService.getCommentLike(comment, - personMention.getRecipient()).orElse(null); + personMention.getRecipient()) + .orElse(null); //@todo: Check if person is blocked and comment is saved return PersonMentionView.builder() @@ -42,9 +43,9 @@ public PersonMentionView getPersonMentionView(PersonMention personMention) { com.sublinks.sublinksapi.api.lemmy.v3.user.models.Person.class)) .creator_blocked(false) // @todo check if user is blocked .comment(conversionService.convert(comment, - com.sublinks.sublinksapi.api.lemmy.v3.comment.models.Comment.class)).post( - conversionService.convert(comment.getPost(), - com.sublinks.sublinksapi.api.lemmy.v3.post.models.Post.class)) + com.sublinks.sublinksapi.api.lemmy.v3.comment.models.Comment.class)) + .post(conversionService.convert(comment.getPost(), + com.sublinks.sublinksapi.api.lemmy.v3.post.models.Post.class)) .saved(false) // @todo check if comment is saved .recipient(conversionService.convert(personMention.getRecipient(), com.sublinks.sublinksapi.api.lemmy.v3.user.models.Person.class)) @@ -53,7 +54,8 @@ public PersonMentionView getPersonMentionView(PersonMention personMention) { .my_vote(commentLike == null ? 0 : commentLike.getScore()) .creator_banned_from_community( linkPersonCommunityService.hasLink(creator, comment.getCommunity(), - LinkPersonCommunityType.banned)).subscribed( + LinkPersonCommunityType.banned)) + .subscribed( lemmyCommunityService.getPersonCommunitySubscribeType(creator, comment.getCommunity())) .creator_is_admin(false) // @todo check if user is admin .creator_is_moderator(false) // @todo check if user is moderator diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java index 30ace3f3..bbb50880 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java @@ -19,7 +19,7 @@ public class SublinksAnnouncementController extends AbstractSublinksApiControlle @Operation(summary = "Get a list of announcements") @GetMapping @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void index() { // TODO: implement @@ -28,7 +28,7 @@ public void index() { @Operation(summary = "Get a specific announcement") @GetMapping("/{id}") @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void show(@PathVariable String id) { // TODO: implement @@ -37,7 +37,7 @@ public void show(@PathVariable String id) { @Operation(summary = "Create a new announcement") @PostMapping @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void create() { // TODO: implement @@ -46,7 +46,7 @@ public void create() { @Operation(summary = "Update an announcement") @PostMapping("/{id}") @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void update(@PathVariable String id) { // TODO: implement @@ -55,7 +55,7 @@ public void update(@PathVariable String id) { @Operation(summary = "Delete an announcement") @DeleteMapping("/{id}") @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void delete(@PathVariable String id) { // TODO: implement diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java index d31a7772..ca4903c8 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java @@ -1,9 +1,17 @@ package com.sublinks.sublinksapi.api.sublinks.v1.comment.controllers; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.IndexComment; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.Optional; +import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -12,23 +20,31 @@ import org.springframework.web.bind.annotation.RestController; @RestController +@AllArgsConstructor @RequestMapping("api/v1/comment") @Tag(name = "Comment", description = "Comment API") public class SublinksCommentController extends AbstractSublinksApiController { - @Operation(summary = "Get a list of comments") + + private final SublinksCommentService sublinksCommentService; + + + @Operation(summary = "Get a list of comments") @GetMapping @ApiResponses(value = { - // TODO: add responses - }) - public void index() { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public CommentResponse index(final IndexComment indexCommentForm, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Optional person = getOptionalPerson(sublinksJwtPerson); + + return sublinksCommentService.index(indexCommentForm, person.orElse(null)); } @Operation(summary = "Get a specific comment") @GetMapping("/{id}") @ApiResponses(value = { - // TODO: add responses - }) + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public void show(@PathVariable String id) { // TODO: implement } @@ -36,8 +52,7 @@ public void show(@PathVariable String id) { @Operation(summary = "Create a new comment") @PostMapping @ApiResponses(value = { - // TODO: add responses - }) + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public void create() { // TODO: implement } @@ -45,8 +60,7 @@ public void create() { @Operation(summary = "Update an comment") @PostMapping("/{id}") @ApiResponses(value = { - // TODO: add responses - }) + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public void update(@PathVariable String id) { // TODO: implement } @@ -54,8 +68,7 @@ public void update(@PathVariable String id) { @Operation(summary = "Delete an comment") @DeleteMapping("/{id}") @ApiResponses(value = { - // TODO: add responses - }) + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public void delete(@PathVariable String id) { // TODO: implement } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CreateComment.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CreateComment.java index 7eeef173..bed7d357 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CreateComment.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CreateComment.java @@ -1,13 +1,14 @@ package com.sublinks.sublinksapi.api.sublinks.v1.comment.models; -import java.util.Optional; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import lombok.Builder; @Builder public record CreateComment(String body, - String parentKey, + @Schema(description = "The parent key of the comment", requiredMode = RequiredMode.NOT_REQUIRED) String parentKey, String postKey, - Optional languageKey, - Optional featured) { + @Schema(description = "The language key of the comment", defaultValue = "und", example = "und", requiredMode = RequiredMode.NOT_REQUIRED) String languageKey, + @Schema(description = "Whether the comment is featured ( Requires permission to do so. )", defaultValue = "false", example = "false", requiredMode = RequiredMode.NOT_REQUIRED) Boolean featured) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java index 192ef540..bfb52aac 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java @@ -1,19 +1,18 @@ package com.sublinks.sublinksapi.api.sublinks.v1.comment.models; -import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; -import java.util.List; -import java.util.Optional; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import lombok.Builder; @Builder public record IndexComment(String search, SortType sortType, SublinksListingType sublinksListingType, - Optional> communityKeys, - Optional> postKeys, - Optional showNsfw, - int limit, - int page) { + String communityKey, + String postKey, + Boolean showNsfw, + Boolean savedOnly, + Integer limit, + Integer page) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/UpdateComment.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/UpdateComment.java new file mode 100644 index 00000000..ec2b1968 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/UpdateComment.java @@ -0,0 +1,13 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.comment.models; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import lombok.Builder; + +@Builder +public record UpdateComment(String commentKey, + String body, + @Schema(description = "The language key of the comment", defaultValue = "und", example = "und", requiredMode = RequiredMode.NOT_REQUIRED) String languageKey, + @Schema(description = "Whether the comment is featured ( Requires Moderator or Admin )", defaultValue = "false", example = "false", requiredMode = RequiredMode.NOT_REQUIRED) Boolean featured) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index c337cd49..8e2c786f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -2,31 +2,118 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CreateComment; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.IndexComment; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.UpdateComment; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import com.sublinks.sublinksapi.comment.entities.Comment; +import com.sublinks.sublinksapi.comment.enums.CommentSortType; +import com.sublinks.sublinksapi.comment.models.CommentSearchCriteria; import com.sublinks.sublinksapi.comment.repositories.CommentRepository; import com.sublinks.sublinksapi.comment.services.CommentService; +import com.sublinks.sublinksapi.community.entities.Community; +import com.sublinks.sublinksapi.community.repositories.CommunityRepository; import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; import com.sublinks.sublinksapi.language.entities.Language; import com.sublinks.sublinksapi.language.repositories.LanguageRepository; +import com.sublinks.sublinksapi.language.services.LanguageService; import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.person.enums.ListingType; import com.sublinks.sublinksapi.post.entities.Post; import com.sublinks.sublinksapi.post.repositories.PostRepository; import com.sublinks.sublinksapi.shared.RemovedState; +import java.util.List; +import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; @AllArgsConstructor +@Service public class SublinksCommentService { private final LocalInstanceContext localInstanceContext; private final ConversionService conversionService; private final LanguageRepository languageRepository; + private final CommunityRepository communityRepository; private final CommentRepository commentRepository; private final CommentService commentService; private final PostRepository postRepository; + private final LanguageService languageService; + /** + * Performs a search for comments based on the provided criteria. + * + * @param indexCommentForm The object representing the search criteria for comments. + * @param person The person performing the search. + * @return A CommentResponse object representing the search results. + */ + public List index(IndexComment indexCommentForm, Person person) { + + Optional parentComment = Optional.empty(); + if (indexCommentForm.postKey() != null) { + parentComment = commentRepository.findByPath(indexCommentForm.postKey()); + } + + Optional community = Optional.empty(); + + if (indexCommentForm.communityKey() != null) { + community = communityRepository.findCommunityByTitleSlug(indexCommentForm.communityKey()); + } + + Optional post = Optional.empty(); + + if (indexCommentForm.postKey() != null) { + post = postRepository.findByTitleSlug(indexCommentForm.postKey()); + } + + SortType sortType = indexCommentForm.sortType(); + if (sortType == null) { + if (person.getDefaultSortType() != null) { + sortType = conversionService.convert(person.getDefaultSortType(), SortType.class); + } else { + sortType = SortType.New; + } + } + + SublinksListingType sublinksListingType = indexCommentForm.sublinksListingType(); + + if (sublinksListingType == null) { + if (person.getDefaultListingType() != null) { + sublinksListingType = conversionService.convert(person.getDefaultListingType(), + SublinksListingType.class); + } else { + sublinksListingType = SublinksListingType.Local; + } + } + + CommentSearchCriteria.CommentSearchCriteriaBuilder commentSearchCriteria = CommentSearchCriteria.builder() + .page(indexCommentForm.page()) + .commentSortType(conversionService.convert(sortType, CommentSortType.class)) + .perPage(indexCommentForm.limit()) + .savedOnly(indexCommentForm.savedOnly()) + .listingType(conversionService.convert(sublinksListingType, ListingType.class)) + .community(community.orElse(null)) + .parent(parentComment.orElse(null)) + .post(post.orElse(null)) + .person(person); + + return commentRepository.allCommentsBySearchCriteria(commentSearchCriteria.build()) + .stream() + .map(comment -> conversionService.convert(comment, CommentResponse.class)) + .toList(); + } + + /** + * Creates a comment based on the provided information. + * + * @param createComment The object representing the data of the comment to be created. + * @param person The person who is creating the comment. + * @return A CommentResponse object representing the created comment. + * @throws ResponseStatusException If the post, comment, or language is not found. + */ public CommentResponse createComment(CreateComment createComment, Person person) { final Post parentPost = postRepository.findByTitleSlugAndRemovedStateIs(createComment.postKey(), @@ -39,8 +126,10 @@ public CommentResponse createComment(CreateComment createComment, Person person) Language language; try { - language = languageRepository.findLanguageByCode(createComment.languageKey() - .orElse("und")); + language = languageRepository.findLanguageByCode( + createComment.languageKey() != null ? createComment.languageKey() + : languageService.getLanguageOfCommunityOrUndefined(parentPost.getCommunity()) + .getCode()); } catch (Exception e) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "language_not_found"); } @@ -56,5 +145,29 @@ public CommentResponse createComment(CreateComment createComment, Person person) return conversionService.convert(comment, CommentResponse.class); } + public CommentResponse updateComment(UpdateComment updateCommentForm, Person person) { + + Comment comment = commentRepository.findByPath(updateCommentForm.commentKey()) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); + if (updateCommentForm.featured() != null) { + comment.setFeatured(updateCommentForm.featured()); + } + if (updateCommentForm.body() != null) { + comment.setCommentBody(updateCommentForm.body()); + } + if (updateCommentForm.languageKey() != null) { + Language language; + try { + language = languageRepository.findLanguageByCode(updateCommentForm.languageKey()); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "language_not_found"); + } + comment.setLanguage(language); + } + + commentRepository.save(comment); + return conversionService.convert(comment, CommentResponse.class); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CreateCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CreateCommunity.java index 0d898f33..645a55d7 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CreateCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CreateCommunity.java @@ -1,12 +1,10 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models; -import java.util.Optional; - public record CreateCommunity(String title, String titleSlug, String description, - Optional iconImageUrl, - Optional bannerImageUrl, + String iconImageUrl, + String bannerImageUrl, Boolean isNsfw, Boolean isPostingRestrictedToMods) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java index 32cf8c3a..5d0a6e1b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java @@ -11,7 +11,7 @@ public record IndexCommunity(String search, @Schema(description = "Sort type", example = "Hot", requiredMode = RequiredMode.NOT_REQUIRED) SortType sortType, @Schema(description = "Sublinks listing type", example = "All", requiredMode = RequiredMode.NOT_REQUIRED) SublinksListingType sublinksListingType, @Schema(description = "Show NSFW", example = "false", requiredMode = RequiredMode.NOT_REQUIRED) Boolean showNsfw, - int limit, - int page) { + Integer limit, + Integer page) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java index ade18f09..14cd56b2 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java @@ -1,14 +1,9 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models; -import java.util.Optional; - -public record UpdateCommunity(Optional title, - Optional description, - Optional iconImageUrl, - Optional bannerImageUrl, - Optional isNsfw, - Optional deleted, - Optional removed, - Optional isPostingRestrictedToMods) { +public record UpdateCommunity(String description, + String iconImageUrl, + String bannerImageUrl, + Boolean isNsfw, + Boolean isPostingRestrictedToMods) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index 8e43ffa9..1d73d7c3 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -10,7 +10,6 @@ import com.sublinks.sublinksapi.community.repositories.CommunityRepository; import com.sublinks.sublinksapi.community.services.CommunityService; import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; -import com.sublinks.sublinksapi.instance.repositories.InstanceRepository; import com.sublinks.sublinksapi.person.entities.LinkPersonCommunity; import com.sublinks.sublinksapi.person.entities.Person; import com.sublinks.sublinksapi.person.enums.LinkPersonCommunityType; @@ -33,7 +32,6 @@ public class SublinksCommunityService { private final CommunityService communityService; private final LocalInstanceContext localInstanceContext; private final RolePermissionService rolePermissionService; - private final InstanceRepository instanceRepository; private final LinkPersonCommunityService linkPersonCommunityService; private final PersonRepository personRepository; @@ -61,11 +59,11 @@ public CommunityResponse createCommunity(CreateCommunity createCommunity, Person Community community = Community.builder() .title(createCommunity.title()) .titleSlug(createCommunity.titleSlug()) - .bannerImageUrl(createCommunity.bannerImageUrl() - .orElse(null)) - .iconImageUrl(createCommunity.iconImageUrl() - .orElse(null)) - .isNsfw(createCommunity.isNsfw()) + .bannerImageUrl( + createCommunity.bannerImageUrl() != null ? createCommunity.bannerImageUrl() : null) + .iconImageUrl( + createCommunity.iconImageUrl() != null ? createCommunity.iconImageUrl() : null) + .isNsfw(createCommunity.isNsfw() != null ? createCommunity.isNsfw() : false) .isPostingRestrictedToMods(createCommunity.isPostingRestrictedToMods()) .description(createCommunity.description()) .instance(localInstanceContext.instance()) @@ -78,11 +76,12 @@ public CommunityResponse createCommunity(CreateCommunity createCommunity, Person /** * Updates a community based on the provided key, update form, and person. * - * @param key The key of the community to update. + * @param key The key of the community to update. * @param updateCommunityForm The update form containing the new community data. - * @param person The person performing the update. + * @param person The person performing the update. * @return The updated community response. - * @throws ResponseStatusException If the community is not found, or the person is not authorized to perform the update. + * @throws ResponseStatusException If the community is not found, or the person is not authorized + * to perform the update. */ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommunityForm, Person person) @@ -105,44 +104,23 @@ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommu throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_update_community"); } - final Boolean isDeleted = updateCommunityForm.deleted() - .orElse(null); - - if (isDeleted != null && isDeleted != community.isDeleted()) { - - community.setDeleted(updateCommunityForm.deleted() - .orElse(community.isDeleted())); - //@todo: do modlog + if (updateCommunityForm.description() != null) { + community.setDescription(updateCommunityForm.description()); } - - final Boolean isRemoved = updateCommunityForm.removed() - .orElse(null); - - if (isRemoved != null && isRemoved != community.isRemoved()) { - - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_remove_community"); - } - community.setRemoved(updateCommunityForm.removed() - .orElse(community.isRemoved())); - //@todo: do modlog + if (updateCommunityForm.iconImageUrl() != null) { + community.setIconImageUrl(updateCommunityForm.iconImageUrl()); + } + if (updateCommunityForm.bannerImageUrl() != null) { + community.setBannerImageUrl(updateCommunityForm.bannerImageUrl()); + } + if (updateCommunityForm.isNsfw() != null) { + community.setNsfw(updateCommunityForm.isNsfw()); + } + if (updateCommunityForm.isPostingRestrictedToMods() != null) { + community.setPostingRestrictedToMods(updateCommunityForm.isPostingRestrictedToMods()); } - updateCommunityForm.title() - .ifPresent(community::setTitle); - updateCommunityForm.description() - .ifPresent(community::setDescription); - updateCommunityForm.isNsfw() - .ifPresent(community::setNsfw); - updateCommunityForm.iconImageUrl() - .ifPresent(community::setIconImageUrl); - updateCommunityForm.bannerImageUrl() - .ifPresent(community::setBannerImageUrl); - updateCommunityForm.isPostingRestrictedToMods() - .ifPresent(community::setPostingRestrictedToMods); - communityRepository.save(community); + communityService.updateCommunity(community); return conversionService.convert(community, CommunityResponse.class); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java index bf9092ec..c437d0db 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java @@ -18,7 +18,7 @@ public class SublinksInstanceController extends AbstractSublinksApiController { @Operation(summary = "Get a list of instances") @GetMapping @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void index() { // TODO: implement @@ -27,7 +27,7 @@ public void index() { @Operation(summary = "Get a specific instance") @GetMapping("/{id}") @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void show(@PathVariable String id) { // TODO: implement @@ -36,7 +36,7 @@ public void show(@PathVariable String id) { @Operation(summary = "Create a new instance") @PostMapping @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void create() { // TODO: implement @@ -45,7 +45,7 @@ public void create() { @Operation(summary = "Update an instance") @PostMapping("/{id}") @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void update(@PathVariable String id) { // TODO: implement @@ -54,7 +54,7 @@ public void update(@PathVariable String id) { @Operation(summary = "Delete an instance") @DeleteMapping("/{id}") @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void delete(@PathVariable String id) { // TODO: implement diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java index 7e6b1d80..171b5b12 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java @@ -8,7 +8,7 @@ public record IndexPerson(String search, SublinksListingType sublinksListingType, SortType sortType, - int limit, - int page) { + Integer limit, + Integer page) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index ebb49212..83bdbf02 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -51,6 +51,15 @@ public class SublinksPersonService { private final ConversionService conversionService; private final LanguageRepository languageRepository; + /** + * Registers a person. + * + * @param createPersonForm The details of the person to be registered. + * @param ip The IP address of the client making the registration request. + * @param userAgent The user agent string of the client making the registration request. + * @return The login response containing the token, registration status, and error (if any). + * @throws ResponseStatusException if email is required but not provided. + */ public LoginResponse registerPerson(final CreatePerson createPersonForm, final String ip, final String userAgent) { @@ -146,6 +155,18 @@ public LoginResponse registerPerson(final CreatePerson createPersonForm, final S .build(); } + /** + * Logs in a person with the given credentials. + * + * @param loginPersonForm The login details of the person. + * @param ip The IP address of the client making the login request. + * @param userAgent The user agent string of the client making the login request. + * @return The login response containing the token, registration status, and error (if any). + * @throws ResponseStatusException if the person is not found, or if the person is deleted, or if + * the person's email is not verified, or if the person's + * registration application is not approved, or if the password is + * incorrect. + */ public LoginResponse login(final LoginPerson loginPersonForm, final String ip, final String userAgent) { @@ -205,6 +226,13 @@ public LoginResponse login(final LoginPerson loginPersonForm, final String ip, .build(); } + /** + * Updates a person's information. + * + * @param person The person object to be updated. + * @param updatePersonForm The form containing updated person information. + * @return The updated person response. + */ public PersonResponse updatePerson(Person person, UpdatePerson updatePersonForm) { if (updatePersonForm.languagesKeys() diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java index 592539f4..8dae8099 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java @@ -19,7 +19,7 @@ public class SublinksPostController extends AbstractSublinksApiController { @Operation(summary = "Get a list of posts") @GetMapping @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void index() { // TODO: implement @@ -29,7 +29,7 @@ public void index() { @Operation(summary = "Get a specific post") @GetMapping("/{id}") @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void show(@PathVariable String id) { // TODO: implement @@ -38,7 +38,7 @@ public void show(@PathVariable String id) { @Operation(summary = "Create a new post") @PostMapping @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void create() { // TODO: implement @@ -47,7 +47,7 @@ public void create() { @Operation(summary = "Update an post") @PostMapping("/{id}") @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void update(@PathVariable String id) { // TODO: implement @@ -56,7 +56,7 @@ public void update(@PathVariable String id) { @Operation(summary = "Delete an post") @DeleteMapping("/{id}") @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void delete(@PathVariable String id) { // TODO: implement diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java index ffc33bbf..1587058f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java @@ -18,7 +18,7 @@ public class SublinksPrivatemessageController extends AbstractSublinksApiControl @Operation(summary = "Get a list of privatemessages") @GetMapping @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void index() { // TODO: implement @@ -27,7 +27,7 @@ public void index() { @Operation(summary = "Get a specific privatemessage") @GetMapping("/{id}") @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void show(@PathVariable String id) { // TODO: implement @@ -36,7 +36,7 @@ public void show(@PathVariable String id) { @Operation(summary = "Create a new privatemessage") @PostMapping @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void create() { // TODO: implement @@ -45,7 +45,7 @@ public void create() { @Operation(summary = "Update an privatemessage") @PostMapping("/{id}") @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void update(@PathVariable String id) { // TODO: implement @@ -54,7 +54,7 @@ public void update(@PathVariable String id) { @Operation(summary = "Delete an privatemessage") @DeleteMapping("/{id}") @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void delete(@PathVariable String id) { // TODO: implement diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java index 792556e6..34d817bf 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java @@ -13,7 +13,7 @@ public class SublinksRolesController extends AbstractSublinksApiController { @Operation(summary = "Get a list of roles") @GetMapping @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void index() { // TODO: implement @@ -22,7 +22,7 @@ public void index() { @Operation(summary = "Get a specific role") @GetMapping("/{id}") @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void show(@PathVariable String id) { // TODO: implement @@ -31,7 +31,7 @@ public void show(@PathVariable String id) { @Operation(summary = "Create a new post") @PostMapping @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void create() { // TODO: implement @@ -40,7 +40,7 @@ public void create() { @Operation(summary = "Update an post") @PostMapping("/{id}") @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void update(@PathVariable String id) { // TODO: implement @@ -49,7 +49,7 @@ public void update(@PathVariable String id) { @Operation(summary = "Delete an post") @DeleteMapping("/{id}") @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void delete(@PathVariable String id) { // TODO: implement diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java index d4e0ed0b..13d529c4 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java @@ -13,7 +13,7 @@ public class SublinksSearchController extends AbstractSublinksApiController { @Operation(summary = "Get a list of privatemessages") @GetMapping @ApiResponses(value = { - // TODO: add responses + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) }) public void index() { // TODO: implement diff --git a/src/main/java/com/sublinks/sublinksapi/comment/models/CommentSearchCriteria.java b/src/main/java/com/sublinks/sublinksapi/comment/models/CommentSearchCriteria.java index 80887314..e8dca405 100644 --- a/src/main/java/com/sublinks/sublinksapi/comment/models/CommentSearchCriteria.java +++ b/src/main/java/com/sublinks/sublinksapi/comment/models/CommentSearchCriteria.java @@ -9,16 +9,14 @@ import lombok.Builder; @Builder -public record CommentSearchCriteria( - ListingType listingType, - CommentSortType commentSortType, - int perPage, - int page, - Community community, - Post post, - Comment parent, - boolean savedOnly, - Person person -) { +public record CommentSearchCriteria(ListingType listingType, + CommentSortType commentSortType, + Integer perPage, + Integer page, + Community community, + Post post, + Comment parent, + Boolean savedOnly, + Person person) { } diff --git a/src/main/java/com/sublinks/sublinksapi/language/services/LanguageService.java b/src/main/java/com/sublinks/sublinksapi/language/services/LanguageService.java index b4d9acda..7fc660fe 100644 --- a/src/main/java/com/sublinks/sublinksapi/language/services/LanguageService.java +++ b/src/main/java/com/sublinks/sublinksapi/language/services/LanguageService.java @@ -1,5 +1,6 @@ package com.sublinks.sublinksapi.language.services; +import com.sublinks.sublinksapi.community.entities.Community; import com.sublinks.sublinksapi.instance.entities.Instance; import com.sublinks.sublinksapi.language.entities.Language; import com.sublinks.sublinksapi.language.repositories.LanguageRepository; @@ -16,6 +17,14 @@ public class LanguageService { private final LanguageRepository languageRepository; + public Language getLanguageOfCommunityOrUndefined(final Community community) { + + return community.getLanguages() + .stream() + .findFirst() + .orElse(languageRepository.findLanguageByCode("und")); + } + public List instanceLanguageIds(final Instance instance) { final List discussionLanguages = new ArrayList<>(); diff --git a/src/main/java/com/sublinks/sublinksapi/post/repositories/PostRepository.java b/src/main/java/com/sublinks/sublinksapi/post/repositories/PostRepository.java index e7dbf4d4..1880261c 100644 --- a/src/main/java/com/sublinks/sublinksapi/post/repositories/PostRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/post/repositories/PostRepository.java @@ -8,4 +8,6 @@ public interface PostRepository extends JpaRepository, PostRepositorySearch { Optional findByTitleSlugAndRemovedStateIs(String titleSlug, RemovedState removedState); + + Optional findByTitleSlug(String titleSlug); } From 800de621436de1b05fba05ee47a01a8a01b3b628 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Sun, 19 May 2024 12:33:41 +0200 Subject: [PATCH 036/115] Update comment controller to return a list of responses The change have been made in the SublinksCommentController class, specifically in the index method, to return a list of CommentResponse objects instead of a single object. This is a more efficient way to handle multiple comments. --- .../v1/comment/controllers/SublinksCommentController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java index ca4903c8..33588107 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java @@ -10,6 +10,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; @@ -32,7 +33,7 @@ public class SublinksCommentController extends AbstractSublinksApiController { @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommentResponse index(final IndexComment indexCommentForm, + public List index(final IndexComment indexCommentForm, final SublinksJwtPerson sublinksJwtPerson) { From f46797162bfa04f27385ff21a54d44ea362804a2 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Sun, 19 May 2024 16:23:41 +0200 Subject: [PATCH 037/115] Enhance comment and community features The commit adds the ability to view, create and edit comments. Updates were made to the community service to provide functionality for retrieving a list of communities based on certain criteria, creating a comment, and updating comments. The addition of authorization checks was also made in several places to ensure only authorized users can perform certain operations. Additionally, functionalities were created to interact with community moderators and community aggregation. --- .../SublinksCommentController.java | 40 ++++++--- .../services/SublinksCommentService.java | 63 ++++++++++++-- ...ublinksCommunityAggregationController.java | 39 ++++++--- .../SublinksCommunityController.java | 85 +++---------------- ...SublinksCommunityModerationController.java | 41 ++++++++- .../services/SublinksCommunityService.java | 64 ++++++++++++++ .../enums/RolePermissionCommentTypes.java | 1 + .../enums/RolePermissionCommunityTypes.java | 2 + 8 files changed, 230 insertions(+), 105 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java index 33588107..eb376652 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java @@ -2,7 +2,9 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CreateComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.IndexComment; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.UpdateComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.person.entities.Person; @@ -18,6 +20,7 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -33,37 +36,54 @@ public class SublinksCommentController extends AbstractSublinksApiController { @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List index(final IndexComment indexCommentForm, + public List index( + @RequestParam(required = false) Optional indexCommentParam, final SublinksJwtPerson sublinksJwtPerson) { final Optional person = getOptionalPerson(sublinksJwtPerson); - return sublinksCommentService.index(indexCommentForm, person.orElse(null)); + return sublinksCommentService.index(indexCommentParam.orElse(IndexComment.builder() + .build()), person.orElse(null)); } @Operation(summary = "Get a specific comment") - @GetMapping("/{id}") + @GetMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void show(@PathVariable String id) { - // TODO: implement + public CommentResponse show(@PathVariable final String key, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Optional person = getOptionalPerson(sublinksJwtPerson); + + return sublinksCommentService.show(key, person.orElse(null)); } @Operation(summary = "Create a new comment") @PostMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void create() { - // TODO: implement + public CommentResponse create(final CreateComment createCommentForm, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksCommentService.createComment(createCommentForm, person); } @Operation(summary = "Update an comment") - @PostMapping("/{id}") + @PostMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void update(@PathVariable String id) { - // TODO: implement + public CommentResponse update(@PathVariable String key, final UpdateComment createCommentForm, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksCommentService.updateComment(createCommentForm, person); } @Operation(summary = "Delete an comment") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index 8e2c786f..bd35a18f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -6,6 +6,8 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.UpdateComment; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommentTypes; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.comment.entities.Comment; import com.sublinks.sublinksapi.comment.enums.CommentSortType; import com.sublinks.sublinksapi.comment.models.CommentSearchCriteria; @@ -42,16 +44,23 @@ public class SublinksCommentService { private final CommentService commentService; private final PostRepository postRepository; private final LanguageService languageService; + private final RolePermissionService rolePermissionService; /** - * Performs a search for comments based on the provided criteria. + * Retrieves a list of CommentResponse objects based on the provided IndexComment and Person + * objects. * - * @param indexCommentForm The object representing the search criteria for comments. - * @param person The person performing the search. - * @return A CommentResponse object representing the search results. + * @param indexCommentForm The IndexComment object representing the search criteria for retrieving + * comments. + * @param person The Person object representing the user performing the operation. + * @return A list of CommentResponse objects representing the retrieved comments. + * @throws ResponseStatusException If the user does not have permission to read comments. */ public List index(IndexComment indexCommentForm, Person person) { + rolePermissionService.isPermitted(person, RolePermissionCommentTypes.READ_COMMENTS, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_update_not_permitted")); + Optional parentComment = Optional.empty(); if (indexCommentForm.postKey() != null) { parentComment = commentRepository.findByPath(indexCommentForm.postKey()); @@ -107,15 +116,40 @@ public List index(IndexComment indexCommentForm, Person person) } /** - * Creates a comment based on the provided information. + * Retrieves a comment based on the provided key. + * + * @param key The key of the comment to retrieve. + * @param person The Person object representing the user performing the operation. + * @return A CommentResponse object representing the retrieved comment. + * @throws ResponseStatusException If the user does not have permission to read the comment or the + * comment is not found. + */ + public CommentResponse show(String key, Person person) { + + rolePermissionService.isPermitted(person, RolePermissionCommentTypes.READ_COMMENT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_view_not_permitted")); + + return commentRepository.findByPath(key) + .map(comment -> conversionService.convert(comment, CommentResponse.class)) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); + } + + /** + * Creates a new comment based on the provided data. * - * @param createComment The object representing the data of the comment to be created. - * @param person The person who is creating the comment. + * @param createComment The object representing the data for creating a comment. + * @param person The person creating the comment. * @return A CommentResponse object representing the created comment. - * @throws ResponseStatusException If the post, comment, or language is not found. + * @throws ResponseStatusException If the user does not have permission to create a comment, the + * post or parent comment is not found, or the language is not + * found. */ public CommentResponse createComment(CreateComment createComment, Person person) { + rolePermissionService.isPermitted(person, RolePermissionCommentTypes.CREATE_COMMENT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_create_not_permitted")); + final Post parentPost = postRepository.findByTitleSlugAndRemovedStateIs(createComment.postKey(), RemovedState.NOT_REMOVED) .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "post_not_found")); @@ -145,8 +179,21 @@ public CommentResponse createComment(CreateComment createComment, Person person) return conversionService.convert(comment, CommentResponse.class); } + /** + * Updates a comment based on the provided form and person. + * + * @param updateCommentForm The UpdateComment object representing the updated data for the + * comment. + * @param person The Person object representing the person performing the update. + * @return A CommentResponse object representing the updated comment. + * @throws ResponseStatusException If the user does not have permission to update the comment, the + * comment is not found, or the language is not found. + */ public CommentResponse updateComment(UpdateComment updateCommentForm, Person person) { + rolePermissionService.isPermitted(person, RolePermissionCommentTypes.UPDATE_COMMENT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_update_not_permitted")); + Comment comment = commentRepository.findByPath(updateCommentForm.commentKey()) .orElseThrow( () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java index ed94f80c..ca9c88ed 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java @@ -1,13 +1,18 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.controllers; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityAggregatesResponse; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommunityTypes; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.community.entities.CommunityAggregate; import com.sublinks.sublinksapi.community.repositories.CommunityAggregateRepository; +import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpStatus; @@ -23,19 +28,31 @@ @Tag(name = "Community", description = "Community Aggregation API") public class SublinksCommunityAggregationController extends AbstractSublinksApiController { - private final CommunityAggregateRepository communityAggregateRepository; - private final ConversionService conversionService; + private final CommunityAggregateRepository communityAggregateRepository; + private final ConversionService conversionService; + private final RolePermissionService rolePermissionService; - @Operation(summary = "Get a community aggregate") - @GetMapping - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommunityAggregatesResponse show(@PathVariable final String key) { + @Operation(summary = "Get a community aggregate") + @GetMapping + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public CommunityAggregatesResponse show(@PathVariable final String key, + final SublinksJwtPerson sublinksJwtPerson) + { - final CommunityAggregate communityAggregate = communityAggregateRepository.findByCommunityKey(key); - if (communityAggregate == null) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found"); - } + final Optional person = getOptionalPerson(sublinksJwtPerson); - return conversionService.convert(communityAggregate, CommunityAggregatesResponse.class); + rolePermissionService.isPermitted(person.orElse(null), + RolePermissionCommunityTypes.READ_COMMUNITY_AGGREGATION, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_read_community_aggregation")); + + final CommunityAggregate communityAggregate = communityAggregateRepository.findByCommunityKey( + key); + if (communityAggregate == null) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found"); } + + return conversionService.convert(communityAggregate, CommunityAggregatesResponse.class); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index 41013a63..bb95b335 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -1,16 +1,12 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.controllers; -import com.sublinks.sublinksapi.api.lemmy.v3.enums.ListingType; -import com.sublinks.sublinksapi.api.lemmy.v3.enums.SortType; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; -import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.IndexCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; -import com.sublinks.sublinksapi.community.models.CommunitySearchCriteria; import com.sublinks.sublinksapi.community.repositories.CommunityRepository; import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; import com.sublinks.sublinksapi.person.entities.Person; @@ -47,8 +43,8 @@ public class SublinksCommunityController extends AbstractSublinksApiController { @Operation(summary = "Get a list of communities") @GetMapping - @ApiResponses( - value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public List index( @RequestParam(required = false) final Optional indexCommunityParam, final SublinksJwtPerson sublinksJwtPerson) @@ -56,72 +52,15 @@ public List index( final Optional person = getOptionalPerson(sublinksJwtPerson); - final IndexCommunity indexCommunityForm = indexCommunityParam.orElse(IndexCommunity.builder() - .build()); - - com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType sortType = indexCommunityForm.sortType(); - - if (sortType == null) { - if (person.isPresent() && person.get() - .getDefaultSortType() != null) { - sortType = conversionService.convert(person.get() - .getDefaultSortType(), - com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType.class); - } else { - sortType = com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType.New; - } - } - - SublinksListingType sublinksListingType = indexCommunityForm.sublinksListingType(); - - if (sublinksListingType == null) { - if (person.isPresent() && person.get() - .getDefaultListingType() != null) { - sublinksListingType = conversionService.convert(person.get() - .getDefaultListingType(), SublinksListingType.class); - } else if (localInstanceContext.instance() - .getInstanceConfig() - .getDefaultPostListingType() != null) { - sublinksListingType = conversionService.convert(localInstanceContext.instance() - .getInstanceConfig() - .getDefaultPostListingType(), SublinksListingType.class); - } else { - sublinksListingType = SublinksListingType.Local; - } - } - - boolean showNsfw = (indexCommunityForm.showNsfw() != null && indexCommunityForm.showNsfw()) || ( - person.isPresent() && person.get() - .isShowNsfw()); - - final CommunitySearchCriteria.CommunitySearchCriteriaBuilder criteria = CommunitySearchCriteria.builder() - .perPage(indexCommunityForm.limit()) - .page(indexCommunityForm.page()) - .sortType(conversionService.convert(sortType, SortType.class)) - .listingType(conversionService.convert(sublinksListingType, ListingType.class)) - .showNsfw(showNsfw) - .person(person.orElse(null)); - - if (indexCommunityForm.limit() == 0) { - criteria.perPage(20); - } - if (indexCommunityForm.page() == 0) { - criteria.page(1); - } - - final CommunitySearchCriteria communitySearchCriteria = criteria.build(); - - return communityRepository.allCommunitiesBySearchCriteria(communitySearchCriteria) - .stream() - .map(community -> conversionService.convert(community, CommunityResponse.class)) - .toList(); + return sublinksCommunityService.index(indexCommunityParam.orElse(IndexCommunity.builder() + .build()), person.orElse(null)); } @Operation(summary = "Get a specific community") @GetMapping("/{key}") - @ApiResponses( - value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse show(@PathVariable final String key) { try { @@ -136,8 +75,8 @@ public CommunityResponse show(@PathVariable final String key) { @Operation(summary = "Create a new community") @PostMapping - @ApiResponses( - value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse create(@RequestBody @Valid final CreateCommunity createCommunity, final SublinksJwtPerson sublinksJwtPerson) { @@ -149,8 +88,8 @@ public CommunityResponse create(@RequestBody @Valid final CreateCommunity create @Operation(summary = "Update an community") @PostMapping("/{key}") - @ApiResponses( - value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse update(@PathVariable String key, @RequestBody @Valid UpdateCommunity updateCommunityForm, final SublinksJwtPerson principal) { @@ -162,8 +101,8 @@ public CommunityResponse update(@PathVariable String key, @Operation(summary = "Purge an community") @DeleteMapping("/{key}") - @ApiResponses( - value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public void delete(@PathVariable String key) { // @TODO: implement } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java index e94e27c7..de82c666 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -6,6 +6,8 @@ import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityModeratorResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommunityTypes; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.community.entities.Community; import com.sublinks.sublinksapi.community.repositories.CommunityRepository; import com.sublinks.sublinksapi.person.entities.Person; @@ -17,6 +19,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; +import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpStatus; @@ -37,12 +40,23 @@ public class SublinksCommunityModerationController extends AbstractSublinksApiCo private final CommunityRepository communityRepository; private final PersonRepository personRepository; private final ConversionService conversionService; + private final RolePermissionService rolePermissionService; @Operation(summary = "Get moderators of the community") @GetMapping("/moderators") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List show(@PathVariable final String key) { + public List show(@PathVariable final String key, + final SublinksJwtPerson sublinksJwtPerson) + { + + Optional person = getOptionalPerson(sublinksJwtPerson); + + rolePermissionService.isPermitted(person.orElse(null), + RolePermissionCommunityTypes.READ_COMMUNITY_MODERATORS, () -> { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_read_moderators"); + }); return sublinksCommunityService.getCommunityModerators(key) .stream() @@ -65,8 +79,12 @@ public List add(@PathVariable final String key, .orElseThrow( () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + if (!(linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) + && rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.MODERATOR_ADD_MODERATOR)) + && !rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.ADMIN_ADD_COMMUNITY_MODERATOR)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_add_moderator"); } @@ -167,4 +185,21 @@ public PersonResponse unban(@PathVariable final String key, @PathVariable final return conversionService.convert(unbannedPerson, PersonResponse.class); } + + @Operation(summary = "Get banned users from a community") + @GetMapping("/banned") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List banned(@PathVariable final String key) { + + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + + return linkPersonCommunityService.getPersonsFromCommunityAndListTypes(community, + List.of(LinkPersonCommunityType.banned)) + .stream() + .map(person -> conversionService.convert(person, PersonResponse.class)) + .toList(); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index 1d73d7c3..b0cf5c59 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -1,12 +1,17 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.services; +import com.sublinks.sublinksapi.api.lemmy.v3.enums.ListingType; +import com.sublinks.sublinksapi.api.lemmy.v3.enums.SortType; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.IndexCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.utils.ActorIdUtils; import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommunityTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.community.entities.Community; +import com.sublinks.sublinksapi.community.models.CommunitySearchCriteria; import com.sublinks.sublinksapi.community.repositories.CommunityRepository; import com.sublinks.sublinksapi.community.services.CommunityService; import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; @@ -35,6 +40,65 @@ public class SublinksCommunityService { private final LinkPersonCommunityService linkPersonCommunityService; private final PersonRepository personRepository; + + public List index(IndexCommunity indexCommunityParam, Person person) + { + + com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType sortType = indexCommunityParam.sortType(); + + if (sortType == null) { + if (person != null && person.getDefaultSortType() != null) { + sortType = conversionService.convert(person.getDefaultSortType(), + com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType.class); + } else { + sortType = com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType.New; + } + } + + SublinksListingType sublinksListingType = indexCommunityParam.sublinksListingType(); + + if (sublinksListingType == null) { + if (person != null && person.getDefaultListingType() != null) { + sublinksListingType = conversionService.convert(person.getDefaultListingType(), + SublinksListingType.class); + } else if (localInstanceContext.instance() + .getInstanceConfig() + .getDefaultPostListingType() != null) { + sublinksListingType = conversionService.convert(localInstanceContext.instance() + .getInstanceConfig() + .getDefaultPostListingType(), SublinksListingType.class); + } else { + sublinksListingType = SublinksListingType.Local; + } + } + + boolean showNsfw = + (indexCommunityParam.showNsfw() != null && indexCommunityParam.showNsfw()) || ( + person != null && person.isShowNsfw()); + + final CommunitySearchCriteria.CommunitySearchCriteriaBuilder criteria = CommunitySearchCriteria.builder() + .perPage(indexCommunityParam.limit()) + .page(indexCommunityParam.page()) + .sortType(conversionService.convert(sortType, SortType.class)) + .listingType(conversionService.convert(sublinksListingType, ListingType.class)) + .showNsfw(showNsfw) + .person(person); + + if (indexCommunityParam.limit() == 0) { + criteria.perPage(20); + } + if (indexCommunityParam.page() == 0) { + criteria.page(1); + } + + final CommunitySearchCriteria communitySearchCriteria = criteria.build(); + + return communityRepository.allCommunitiesBySearchCriteria(communitySearchCriteria) + .stream() + .map(community -> conversionService.convert(community, CommunityResponse.class)) + .toList(); + } + /** * Creates a new community based on the provided community data and person. * diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommentTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommentTypes.java index 08fee988..65a6d51f 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommentTypes.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommentTypes.java @@ -4,6 +4,7 @@ public enum RolePermissionCommentTypes implements RolePermissionInterface { // Person permissions READ_COMMENT("comment", AuthorizeAction.READ), + READ_COMMENTS("comments", AuthorizeAction.READ), MARK_COMMENT_AS_READ("comment-read", AuthorizeAction.UPDATE), CREATE_COMMENT("comment", AuthorizeAction.CREATE), UPDATE_COMMENT("comment", AuthorizeAction.UPDATE), diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommunityTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommunityTypes.java index 11524526..1eaf2937 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommunityTypes.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommunityTypes.java @@ -5,6 +5,8 @@ public enum RolePermissionCommunityTypes implements RolePermissionInterface { // Person permissions READ_COMMUNITY("community", AuthorizeAction.READ), READ_COMMUNITIES("communities", AuthorizeAction.READ), + READ_COMMUNITY_MODERATORS("communities-moderator", AuthorizeAction.READ), + READ_COMMUNITY_AGGREGATION("communities-aggregation", AuthorizeAction.READ), CREATE_COMMUNITY("community", AuthorizeAction.CREATE), UPDATE_COMMUNITY("community", AuthorizeAction.UPDATE), DELETE_COMMUNITY("community", AuthorizeAction.DELETE), From 24b5209dbe0be98b4cc448c83810648c5c9c6588 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Tue, 21 May 2024 16:22:27 +0200 Subject: [PATCH 038/115] Enhance ban logic and add community remove feature This commit significantly enhances the ban logic and introduces the community remove feature in the community moderation functionality. Specifically, the ban logic has been updated to allow both banning and unbanning of users. The commit adds a remove method that enables moderators to remove communities based on roles and permissions for both admin and moderator roles have been updated accordingly. As part of this enhancement, several record models related to community moderation have been updated or created. --- ...SublinksCommunityModerationController.java | 50 +++----- .../models/Moderation/CommunityBanPerson.java | 3 +- .../models/Moderation/CommunityDelete.java | 6 + .../models/Moderation/CommunityRemove.java | 6 + .../services/SublinksCommunityService.java | 109 ++++++++++++------ .../enums/RolePermissionCommunityTypes.java | 2 + .../enums/RolePermissionPersonTypes.java | 1 + .../services/InitialRoleSetupService.java | 39 +++---- 8 files changed, 125 insertions(+), 91 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityDelete.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityRemove.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java index de82c666..d7193b72 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -2,8 +2,10 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityBanPerson; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityModeratorResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityRemove; import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommunityTypes; @@ -42,6 +44,16 @@ public class SublinksCommunityModerationController extends AbstractSublinksApiCo private final ConversionService conversionService; private final RolePermissionService rolePermissionService; + public CommunityResponse remove(@PathVariable final String key, + CommunityRemove communityRemoveForm, SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksCommunityService.remove(key, communityRemoveForm, person); + } + + @Operation(summary = "Get moderators of the community") @GetMapping("/moderators") @ApiResponses(value = { @@ -58,7 +70,7 @@ public List show(@PathVariable final String key, "not_authorized_to_read_moderators"); }); - return sublinksCommunityService.getCommunityModerators(key) + return sublinksCommunityService.getCommunityModerators(key, person.orElse(null)) .stream() .map(communityModerator -> conversionService.convert(communityModerator, CommunityModeratorResponse.class)) @@ -98,7 +110,7 @@ public List add(@PathVariable final String key, linkPersonCommunityService.addLink(newModerator, community, LinkPersonCommunityType.moderator); - return sublinksCommunityService.getCommunityModerators(key) + return sublinksCommunityService.getCommunityModerators(key, person) .stream() .map(communityModerator -> conversionService.convert(communityModerator, CommunityModeratorResponse.class)) @@ -127,14 +139,14 @@ public List remove(@PathVariable final String key, linkPersonCommunityService.removeLink(person, community, LinkPersonCommunityType.moderator); - return sublinksCommunityService.getCommunityModerators(key) + return sublinksCommunityService.getCommunityModerators(key, person) .stream() .map(communityModerator -> conversionService.convert(communityModerator, CommunityModeratorResponse.class)) .toList(); } - @Operation(summary = "Ban a user from a community") + @Operation(summary = "Ban/Unban a user from a community") @GetMapping("/ban/{personKey}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) @@ -153,38 +165,12 @@ public PersonResponse ban(@PathVariable final String key, @PathVariable final St throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_ban_user"); } - final Person bannedPerson = sublinksCommunityService.banPerson(key, personKey, person); - - // @todo: Modlog + final Person bannedPerson = sublinksCommunityService.banPerson(key, personKey, person, + communityBanPersonForm); return conversionService.convert(bannedPerson, PersonResponse.class); } - @Operation(summary = "Unban a user from a community") - @GetMapping("/unban/{personKey}") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public PersonResponse unban(@PathVariable final String key, @PathVariable final String personKey, - CommunityBanPerson communityBanPersonForm, final SublinksJwtPerson sublinksJwtPerson) - { - - final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - - final Community community = communityRepository.findCommunityByTitleSlug(key) - .orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_unban_user"); - } - - final Person unbannedPerson = sublinksCommunityService.unbanPerson(key, personKey, person); - - // @todo: Modlog - - return conversionService.convert(unbannedPerson, PersonResponse.class); - } @Operation(summary = "Get banned users from a community") @GetMapping("/banned") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java index 955fe04f..9564b0cd 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java @@ -1,5 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; -public record CommunityBanPerson(String reason) { +public record CommunityBanPerson(String reason, + Boolean ban) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityDelete.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityDelete.java new file mode 100644 index 00000000..c3335cea --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityDelete.java @@ -0,0 +1,6 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; + +public record CommunityDelete(String reason, + Boolean remove) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityRemove.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityRemove.java new file mode 100644 index 00000000..757a6e83 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityRemove.java @@ -0,0 +1,6 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; + +public record CommunityRemove(String reason, + Boolean remove) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index b0cf5c59..93f48a66 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -6,6 +6,8 @@ import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.IndexCommunity; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityBanPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityRemove; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.utils.ActorIdUtils; import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommunityTypes; @@ -41,9 +43,20 @@ public class SublinksCommunityService { private final PersonRepository personRepository; + /** + * Retrieves a list of CommunityResponse objects based on the search criteria provided. + * + * @param indexCommunityParam The search criteria for retrieving community responses. + * @param person The person requesting the community responses. + * @return The list of CommunityResponse objects that match the search criteria. + */ public List index(IndexCommunity indexCommunityParam, Person person) { + rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.READ_COMMUNITY, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_read_community")); + com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType sortType = indexCommunityParam.sortType(); if (sortType == null) { @@ -163,8 +176,10 @@ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommu } Community community = foundCommunity.get(); - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + if (!(linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) + && rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.UPDATE_COMMUNITY))) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_update_community"); } @@ -196,7 +211,12 @@ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommu * @return The list of moderators in the community. * @throws ResponseStatusException If the community is not found. */ - public List getCommunityModerators(String key) { + public List getCommunityModerators(String key, Person person) { + + rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.READ_COMMUNITY_MODERATORS, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_read_community_moderators")); final Community community = communityRepository.findCommunityByTitleSlug(key) .orElseThrow( @@ -209,16 +229,20 @@ public List getCommunityModerators(String key) { } /** - * Ban a person from a community. + * Bans or unbans a person in a community. * - * @param key the community key - * @param personKey the person key - * @param person the person making the request - * @return the banned person - * @throws ResponseStatusException if the community or person is not found, or if the person is - * not authorized to ban (details in the exception message) + * @param key The key of the community. + * @param personKey The key of the person to ban or unban. + * @param person The person performing the ban or unban. + * @param ban Determines whether to ban or unban the person. True to ban, false to unban. + * @return The banned or unbanned person. + * @throws ResponseStatusException If the community is not found, the person is not found, the + * person is not authorized to ban or unban, or the person is + * already banned and attempting to ban again. */ - public Person banPerson(String key, String personKey, Person person) { + public Person banPerson(String key, String personKey, Person person, + CommunityBanPerson communityBanPersonForm) + { final Community community = communityRepository.findCommunityByTitleSlug(key) .orElseThrow( @@ -227,56 +251,65 @@ public Person banPerson(String key, String personKey, Person person) { final Person personToBan = personRepository.findOneByNameIgnoreCase(personKey) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); - if (linkPersonCommunityService.hasAnyLinkOrAdmin(personToBan, community, + if (communityBanPersonForm.ban() && linkPersonCommunityService.hasAnyLinkOrAdmin(personToBan, + community, List.of(LinkPersonCommunityType.banned))) { return personToBan; } - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + if (!(linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) + && rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.MODERATOR_BAN_USER)) && !rolePermissionService.isPermitted( + person, RolePermissionCommunityTypes.ADMIN_BAN_USER)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_ban_person"); } - linkPersonCommunityService.removeAnyLink(personToBan, community, - List.of(LinkPersonCommunityType.owner, LinkPersonCommunityType.moderator, - LinkPersonCommunityType.follower, LinkPersonCommunityType.pending_follow)); + if (!communityBanPersonForm.ban()) { + linkPersonCommunityService.removeLink(personToBan, community, LinkPersonCommunityType.banned); + } else { + linkPersonCommunityService.removeAnyLink(personToBan, community, + List.of(LinkPersonCommunityType.owner, LinkPersonCommunityType.moderator, + LinkPersonCommunityType.follower, LinkPersonCommunityType.pending_follow)); - linkPersonCommunityService.addLink(personToBan, community, LinkPersonCommunityType.banned); + linkPersonCommunityService.addLink(personToBan, community, LinkPersonCommunityType.banned); + } + // @todo: Modlog return personToBan; } /** - * Unban a person from a community. + * Removes a community based on the provided key, remove comment, and person. * - * @param key the community key - * @param personKey the person key - * @param person the person making the request - * @return the unbanned person - * @throws ResponseStatusException if the community or person is not found, or if the person is - * not authorized to unban (details in the exception message) + * @param key The key of the community to remove. + * @param removeComment The comment specifying the reason for removal and whether to remove all + * content. + * @param person The person performing the removal. + * @return The response containing the removed community. + * @throws ResponseStatusException If the community is not found, or the person is not authorized + * to remove the community. */ - public Person unbanPerson(String key, String personKey, Person person) { + public CommunityResponse remove(String key, CommunityRemove removeComment, Person person) { final Community community = communityRepository.findCommunityByTitleSlug(key) .orElseThrow( () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - final Person personToUnban = personRepository.findOneByNameIgnoreCase(personKey) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); - - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(personToUnban, community, - List.of(LinkPersonCommunityType.banned))) { - return personToUnban; + if (!(linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.owner)) && rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.MODERATOR_REMOVE_COMMUNITY)) + && !rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.ADMIN_REMOVE_COMMUNITY)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_remove_community"); } - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_unban_person"); - } + community.setRemoved(removeComment.remove() != null ? removeComment.remove() : true); - linkPersonCommunityService.removeLink(personToUnban, community, LinkPersonCommunityType.banned); + // @todo: modlog - return personToUnban; + communityService.updateCommunity(community); + + return conversionService.convert(community, CommunityResponse.class); } } diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommunityTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommunityTypes.java index 1eaf2937..38c265b8 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommunityTypes.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommunityTypes.java @@ -21,6 +21,7 @@ public enum RolePermissionCommunityTypes implements RolePermissionInterface { MODERATOR_TRANSFER_COMMUNITY("community-moderator", AuthorizeAction.UPDATE), MODERATOR_ADD_MODERATOR("community-moderator", AuthorizeAction.CREATE), MODERATOR_REMOVE_MODERATOR("community-moderator-moderator", AuthorizeAction.DELETE), + MODERATOR_BAN_USER("community-moderator", AuthorizeAction.BAN), // Admin permissions ADMIN_SHOW_DELETED_COMMUNITY("community-admin", AuthorizeAction.READ), @@ -31,6 +32,7 @@ public enum RolePermissionCommunityTypes implements RolePermissionInterface { ADMIN_REMOVE_COMMUNITY("community-admin", AuthorizeAction.REMOVE), ADMIN_ADD_COMMUNITY_MODERATOR("community-admin-moderator", AuthorizeAction.CREATE), ADMIN_REMOVE_COMMUNITY_MODERATOR("community-admin-moderator", AuthorizeAction.REMOVE), + ADMIN_BAN_USER("community-admin", AuthorizeAction.BAN), /** * Unused diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java index c2b52d29..034e0410 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java @@ -4,6 +4,7 @@ public enum RolePermissionPersonTypes implements RolePermissionInterface { // Person permissions READ_USER("user", AuthorizeAction.READ), + READ_USERS("users", AuthorizeAction.READ), UPDATE_USER("user", AuthorizeAction.UPDATE), UPDATE_USER_SETTINGS("user-settings", AuthorizeAction.UPDATE), DELETE_USER("user", AuthorizeAction.DELETE), diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java index 4e21163d..ba92e00e 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java @@ -34,7 +34,8 @@ public class InitialRoleSetupService { */ public void generateInitialRoles() { - if (roleRepository.findAll().isEmpty()) { + if (roleRepository.findAll() + .isEmpty()) { createAdminRole(); createBannedRole(); createGuestRole(); @@ -51,12 +52,11 @@ public void generateInitialRoles() { private void savePermissions(Role role, Set rolePermissions) { role.setRolePermissions(rolePermissions.stream() - .map(rolePermission -> rolePermissionsRepository.save( - RolePermissions.builder() - .role(role) - .permission(rolePermission.toString()) - .build()) - ).collect(Collectors.toSet())); + .map(rolePermission -> rolePermissionsRepository.save(RolePermissions.builder() + .role(role) + .permission(rolePermission.toString()) + .build())) + .collect(Collectors.toSet())); } /** @@ -70,9 +70,11 @@ private void applyCommonPermissions(Set rolePermissions rolePermissions.add(RolePermissionPostTypes.READ_POST); rolePermissions.add(RolePermissionPostTypes.READ_POSTS); rolePermissions.add(RolePermissionCommentTypes.READ_COMMENT); + rolePermissions.add(RolePermissionCommentTypes.READ_COMMENTS); rolePermissions.add(RolePermissionCommunityTypes.READ_COMMUNITY); rolePermissions.add(RolePermissionCommunityTypes.READ_COMMUNITIES); rolePermissions.add(RolePermissionPersonTypes.READ_USER); + rolePermissions.add(RolePermissionPersonTypes.READ_USERS); rolePermissions.add(RolePermissionModLogTypes.READ_MODLOG); } @@ -82,13 +84,11 @@ private void applyCommonPermissions(Set rolePermissions private void createAdminRole() { Set rolePermissions = new HashSet<>(); - Role adminRole = roleRepository.save( - Role.builder() - .description("Admin role for admins") - .name(RoleTypes.ADMIN.toString()) - .isActive(true) - .build() - ); + Role adminRole = roleRepository.save(Role.builder() + .description("Admin role for admins") + .name(RoleTypes.ADMIN.toString()) + .isActive(true) + .build()); savePermissions(adminRole, rolePermissions); } @@ -105,8 +105,7 @@ private void createGuestRole() { .description("Default role for all users") .name(RoleTypes.GUEST.toString()) .isActive(true) - .build() - ); + .build()); savePermissions(defaultUserRole, rolePermissions); } @@ -123,8 +122,7 @@ private void createBannedRole() { .description("Banned role for banned users") .name(RoleTypes.BANNED.toString()) .isActive(true) - .build() - ); + .build()); savePermissions(bannedRole, rolePermissions); } @@ -153,6 +151,8 @@ private void createRegisteredRole() { rolePermissions.add(RolePermissionCommunityTypes.CREATE_COMMUNITY); rolePermissions.add(RolePermissionCommunityTypes.UPDATE_COMMUNITY); rolePermissions.add(RolePermissionCommunityTypes.DELETE_COMMUNITY); + rolePermissions.add(RolePermissionCommunityTypes.READ_COMMUNITY_AGGREGATION); + rolePermissions.add(RolePermissionCommunityTypes.READ_COMMUNITY_MODERATORS); rolePermissions.add(RolePermissionPostTypes.CREATE_POST); rolePermissions.add(RolePermissionPostTypes.UPDATE_POST); @@ -196,8 +196,7 @@ private void createRegisteredRole() { .description("Default Role for all registered users") .name(RoleTypes.REGISTERED.toString()) .isActive(true) - .build() - ); + .build()); savePermissions(registeredUserRole, rolePermissions); } From 9e01c0b9940354eb51f2ca080c2e8ea0869228b2 Mon Sep 17 00:00:00 2001 From: rooki <34943569+Pdzly@users.noreply.github.com> Date: Tue, 21 May 2024 16:22:27 +0200 Subject: [PATCH 039/115] Enhance ban logic and add community remove feature This commit significantly enhances the ban logic and introduces the community remove feature in the community moderation functionality. Specifically, the ban logic has been updated to allow both banning and unbanning of users. The commit adds a remove method that enables moderators to remove communities based on roles and permissions for both admin and moderator roles have been updated accordingly. As part of this enhancement, several record models related to community moderation have been updated or created. --- ...SublinksCommunityModerationController.java | 50 +++----- .../models/Moderation/CommunityBanPerson.java | 3 +- .../models/Moderation/CommunityDelete.java | 6 + .../models/Moderation/CommunityRemove.java | 6 + .../services/SublinksCommunityService.java | 109 ++++++++++++------ .../enums/RolePermissionCommunityTypes.java | 2 + .../enums/RolePermissionPersonTypes.java | 1 + .../services/InitialRoleSetupService.java | 39 +++---- 8 files changed, 125 insertions(+), 91 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityDelete.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityRemove.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java index de82c666..d7193b72 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -2,8 +2,10 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityBanPerson; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityModeratorResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityRemove; import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommunityTypes; @@ -42,6 +44,16 @@ public class SublinksCommunityModerationController extends AbstractSublinksApiCo private final ConversionService conversionService; private final RolePermissionService rolePermissionService; + public CommunityResponse remove(@PathVariable final String key, + CommunityRemove communityRemoveForm, SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksCommunityService.remove(key, communityRemoveForm, person); + } + + @Operation(summary = "Get moderators of the community") @GetMapping("/moderators") @ApiResponses(value = { @@ -58,7 +70,7 @@ public List show(@PathVariable final String key, "not_authorized_to_read_moderators"); }); - return sublinksCommunityService.getCommunityModerators(key) + return sublinksCommunityService.getCommunityModerators(key, person.orElse(null)) .stream() .map(communityModerator -> conversionService.convert(communityModerator, CommunityModeratorResponse.class)) @@ -98,7 +110,7 @@ public List add(@PathVariable final String key, linkPersonCommunityService.addLink(newModerator, community, LinkPersonCommunityType.moderator); - return sublinksCommunityService.getCommunityModerators(key) + return sublinksCommunityService.getCommunityModerators(key, person) .stream() .map(communityModerator -> conversionService.convert(communityModerator, CommunityModeratorResponse.class)) @@ -127,14 +139,14 @@ public List remove(@PathVariable final String key, linkPersonCommunityService.removeLink(person, community, LinkPersonCommunityType.moderator); - return sublinksCommunityService.getCommunityModerators(key) + return sublinksCommunityService.getCommunityModerators(key, person) .stream() .map(communityModerator -> conversionService.convert(communityModerator, CommunityModeratorResponse.class)) .toList(); } - @Operation(summary = "Ban a user from a community") + @Operation(summary = "Ban/Unban a user from a community") @GetMapping("/ban/{personKey}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) @@ -153,38 +165,12 @@ public PersonResponse ban(@PathVariable final String key, @PathVariable final St throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_ban_user"); } - final Person bannedPerson = sublinksCommunityService.banPerson(key, personKey, person); - - // @todo: Modlog + final Person bannedPerson = sublinksCommunityService.banPerson(key, personKey, person, + communityBanPersonForm); return conversionService.convert(bannedPerson, PersonResponse.class); } - @Operation(summary = "Unban a user from a community") - @GetMapping("/unban/{personKey}") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public PersonResponse unban(@PathVariable final String key, @PathVariable final String personKey, - CommunityBanPerson communityBanPersonForm, final SublinksJwtPerson sublinksJwtPerson) - { - - final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - - final Community community = communityRepository.findCommunityByTitleSlug(key) - .orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_unban_user"); - } - - final Person unbannedPerson = sublinksCommunityService.unbanPerson(key, personKey, person); - - // @todo: Modlog - - return conversionService.convert(unbannedPerson, PersonResponse.class); - } @Operation(summary = "Get banned users from a community") @GetMapping("/banned") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java index 955fe04f..9564b0cd 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java @@ -1,5 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; -public record CommunityBanPerson(String reason) { +public record CommunityBanPerson(String reason, + Boolean ban) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityDelete.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityDelete.java new file mode 100644 index 00000000..c3335cea --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityDelete.java @@ -0,0 +1,6 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; + +public record CommunityDelete(String reason, + Boolean remove) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityRemove.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityRemove.java new file mode 100644 index 00000000..757a6e83 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityRemove.java @@ -0,0 +1,6 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; + +public record CommunityRemove(String reason, + Boolean remove) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index b0cf5c59..93f48a66 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -6,6 +6,8 @@ import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.IndexCommunity; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityBanPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityRemove; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.utils.ActorIdUtils; import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommunityTypes; @@ -41,9 +43,20 @@ public class SublinksCommunityService { private final PersonRepository personRepository; + /** + * Retrieves a list of CommunityResponse objects based on the search criteria provided. + * + * @param indexCommunityParam The search criteria for retrieving community responses. + * @param person The person requesting the community responses. + * @return The list of CommunityResponse objects that match the search criteria. + */ public List index(IndexCommunity indexCommunityParam, Person person) { + rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.READ_COMMUNITY, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_read_community")); + com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType sortType = indexCommunityParam.sortType(); if (sortType == null) { @@ -163,8 +176,10 @@ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommu } Community community = foundCommunity.get(); - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + if (!(linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) + && rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.UPDATE_COMMUNITY))) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_update_community"); } @@ -196,7 +211,12 @@ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommu * @return The list of moderators in the community. * @throws ResponseStatusException If the community is not found. */ - public List getCommunityModerators(String key) { + public List getCommunityModerators(String key, Person person) { + + rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.READ_COMMUNITY_MODERATORS, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_read_community_moderators")); final Community community = communityRepository.findCommunityByTitleSlug(key) .orElseThrow( @@ -209,16 +229,20 @@ public List getCommunityModerators(String key) { } /** - * Ban a person from a community. + * Bans or unbans a person in a community. * - * @param key the community key - * @param personKey the person key - * @param person the person making the request - * @return the banned person - * @throws ResponseStatusException if the community or person is not found, or if the person is - * not authorized to ban (details in the exception message) + * @param key The key of the community. + * @param personKey The key of the person to ban or unban. + * @param person The person performing the ban or unban. + * @param ban Determines whether to ban or unban the person. True to ban, false to unban. + * @return The banned or unbanned person. + * @throws ResponseStatusException If the community is not found, the person is not found, the + * person is not authorized to ban or unban, or the person is + * already banned and attempting to ban again. */ - public Person banPerson(String key, String personKey, Person person) { + public Person banPerson(String key, String personKey, Person person, + CommunityBanPerson communityBanPersonForm) + { final Community community = communityRepository.findCommunityByTitleSlug(key) .orElseThrow( @@ -227,56 +251,65 @@ public Person banPerson(String key, String personKey, Person person) { final Person personToBan = personRepository.findOneByNameIgnoreCase(personKey) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); - if (linkPersonCommunityService.hasAnyLinkOrAdmin(personToBan, community, + if (communityBanPersonForm.ban() && linkPersonCommunityService.hasAnyLinkOrAdmin(personToBan, + community, List.of(LinkPersonCommunityType.banned))) { return personToBan; } - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + if (!(linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) + && rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.MODERATOR_BAN_USER)) && !rolePermissionService.isPermitted( + person, RolePermissionCommunityTypes.ADMIN_BAN_USER)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_ban_person"); } - linkPersonCommunityService.removeAnyLink(personToBan, community, - List.of(LinkPersonCommunityType.owner, LinkPersonCommunityType.moderator, - LinkPersonCommunityType.follower, LinkPersonCommunityType.pending_follow)); + if (!communityBanPersonForm.ban()) { + linkPersonCommunityService.removeLink(personToBan, community, LinkPersonCommunityType.banned); + } else { + linkPersonCommunityService.removeAnyLink(personToBan, community, + List.of(LinkPersonCommunityType.owner, LinkPersonCommunityType.moderator, + LinkPersonCommunityType.follower, LinkPersonCommunityType.pending_follow)); - linkPersonCommunityService.addLink(personToBan, community, LinkPersonCommunityType.banned); + linkPersonCommunityService.addLink(personToBan, community, LinkPersonCommunityType.banned); + } + // @todo: Modlog return personToBan; } /** - * Unban a person from a community. + * Removes a community based on the provided key, remove comment, and person. * - * @param key the community key - * @param personKey the person key - * @param person the person making the request - * @return the unbanned person - * @throws ResponseStatusException if the community or person is not found, or if the person is - * not authorized to unban (details in the exception message) + * @param key The key of the community to remove. + * @param removeComment The comment specifying the reason for removal and whether to remove all + * content. + * @param person The person performing the removal. + * @return The response containing the removed community. + * @throws ResponseStatusException If the community is not found, or the person is not authorized + * to remove the community. */ - public Person unbanPerson(String key, String personKey, Person person) { + public CommunityResponse remove(String key, CommunityRemove removeComment, Person person) { final Community community = communityRepository.findCommunityByTitleSlug(key) .orElseThrow( () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - final Person personToUnban = personRepository.findOneByNameIgnoreCase(personKey) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); - - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(personToUnban, community, - List.of(LinkPersonCommunityType.banned))) { - return personToUnban; + if (!(linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.owner)) && rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.MODERATOR_REMOVE_COMMUNITY)) + && !rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.ADMIN_REMOVE_COMMUNITY)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_remove_community"); } - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_unban_person"); - } + community.setRemoved(removeComment.remove() != null ? removeComment.remove() : true); - linkPersonCommunityService.removeLink(personToUnban, community, LinkPersonCommunityType.banned); + // @todo: modlog - return personToUnban; + communityService.updateCommunity(community); + + return conversionService.convert(community, CommunityResponse.class); } } diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommunityTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommunityTypes.java index 1eaf2937..38c265b8 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommunityTypes.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommunityTypes.java @@ -21,6 +21,7 @@ public enum RolePermissionCommunityTypes implements RolePermissionInterface { MODERATOR_TRANSFER_COMMUNITY("community-moderator", AuthorizeAction.UPDATE), MODERATOR_ADD_MODERATOR("community-moderator", AuthorizeAction.CREATE), MODERATOR_REMOVE_MODERATOR("community-moderator-moderator", AuthorizeAction.DELETE), + MODERATOR_BAN_USER("community-moderator", AuthorizeAction.BAN), // Admin permissions ADMIN_SHOW_DELETED_COMMUNITY("community-admin", AuthorizeAction.READ), @@ -31,6 +32,7 @@ public enum RolePermissionCommunityTypes implements RolePermissionInterface { ADMIN_REMOVE_COMMUNITY("community-admin", AuthorizeAction.REMOVE), ADMIN_ADD_COMMUNITY_MODERATOR("community-admin-moderator", AuthorizeAction.CREATE), ADMIN_REMOVE_COMMUNITY_MODERATOR("community-admin-moderator", AuthorizeAction.REMOVE), + ADMIN_BAN_USER("community-admin", AuthorizeAction.BAN), /** * Unused diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java index c2b52d29..034e0410 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java @@ -4,6 +4,7 @@ public enum RolePermissionPersonTypes implements RolePermissionInterface { // Person permissions READ_USER("user", AuthorizeAction.READ), + READ_USERS("users", AuthorizeAction.READ), UPDATE_USER("user", AuthorizeAction.UPDATE), UPDATE_USER_SETTINGS("user-settings", AuthorizeAction.UPDATE), DELETE_USER("user", AuthorizeAction.DELETE), diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java index 4e21163d..ba92e00e 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java @@ -34,7 +34,8 @@ public class InitialRoleSetupService { */ public void generateInitialRoles() { - if (roleRepository.findAll().isEmpty()) { + if (roleRepository.findAll() + .isEmpty()) { createAdminRole(); createBannedRole(); createGuestRole(); @@ -51,12 +52,11 @@ public void generateInitialRoles() { private void savePermissions(Role role, Set rolePermissions) { role.setRolePermissions(rolePermissions.stream() - .map(rolePermission -> rolePermissionsRepository.save( - RolePermissions.builder() - .role(role) - .permission(rolePermission.toString()) - .build()) - ).collect(Collectors.toSet())); + .map(rolePermission -> rolePermissionsRepository.save(RolePermissions.builder() + .role(role) + .permission(rolePermission.toString()) + .build())) + .collect(Collectors.toSet())); } /** @@ -70,9 +70,11 @@ private void applyCommonPermissions(Set rolePermissions rolePermissions.add(RolePermissionPostTypes.READ_POST); rolePermissions.add(RolePermissionPostTypes.READ_POSTS); rolePermissions.add(RolePermissionCommentTypes.READ_COMMENT); + rolePermissions.add(RolePermissionCommentTypes.READ_COMMENTS); rolePermissions.add(RolePermissionCommunityTypes.READ_COMMUNITY); rolePermissions.add(RolePermissionCommunityTypes.READ_COMMUNITIES); rolePermissions.add(RolePermissionPersonTypes.READ_USER); + rolePermissions.add(RolePermissionPersonTypes.READ_USERS); rolePermissions.add(RolePermissionModLogTypes.READ_MODLOG); } @@ -82,13 +84,11 @@ private void applyCommonPermissions(Set rolePermissions private void createAdminRole() { Set rolePermissions = new HashSet<>(); - Role adminRole = roleRepository.save( - Role.builder() - .description("Admin role for admins") - .name(RoleTypes.ADMIN.toString()) - .isActive(true) - .build() - ); + Role adminRole = roleRepository.save(Role.builder() + .description("Admin role for admins") + .name(RoleTypes.ADMIN.toString()) + .isActive(true) + .build()); savePermissions(adminRole, rolePermissions); } @@ -105,8 +105,7 @@ private void createGuestRole() { .description("Default role for all users") .name(RoleTypes.GUEST.toString()) .isActive(true) - .build() - ); + .build()); savePermissions(defaultUserRole, rolePermissions); } @@ -123,8 +122,7 @@ private void createBannedRole() { .description("Banned role for banned users") .name(RoleTypes.BANNED.toString()) .isActive(true) - .build() - ); + .build()); savePermissions(bannedRole, rolePermissions); } @@ -153,6 +151,8 @@ private void createRegisteredRole() { rolePermissions.add(RolePermissionCommunityTypes.CREATE_COMMUNITY); rolePermissions.add(RolePermissionCommunityTypes.UPDATE_COMMUNITY); rolePermissions.add(RolePermissionCommunityTypes.DELETE_COMMUNITY); + rolePermissions.add(RolePermissionCommunityTypes.READ_COMMUNITY_AGGREGATION); + rolePermissions.add(RolePermissionCommunityTypes.READ_COMMUNITY_MODERATORS); rolePermissions.add(RolePermissionPostTypes.CREATE_POST); rolePermissions.add(RolePermissionPostTypes.UPDATE_POST); @@ -196,8 +196,7 @@ private void createRegisteredRole() { .description("Default Role for all registered users") .name(RoleTypes.REGISTERED.toString()) .isActive(true) - .build() - ); + .build()); savePermissions(registeredUserRole, rolePermissions); } From 5bbf85abc900836215f9bd9b2df3526f39eebde0 Mon Sep 17 00:00:00 2001 From: rooki Date: Sat, 18 May 2024 12:26:48 +0200 Subject: [PATCH 040/115] Add community and comment moderation features Introduced delete and remove functionality to both community and comment sections of the platform. Changed tag names in community controllers for better clarity. Updated Community and Comment services to support new moderation features. Also, made minor adjustments to PostMapper and RolePermissionCommentTypes for better code organization. --- .../api/lemmy/v3/post/mappers/PostMapper.java | 2 + .../SublinksCommentModerationController.java | 55 ++++++++++++++ .../models/Moderation/CommentDelete.java | 6 ++ .../models/Moderation/CommentRemove.java | 6 ++ .../services/SublinksCommentService.java | 69 ++++++++++++++++- ...ublinksCommunityAggregationController.java | 5 +- .../SublinksCommunityController.java | 23 ++---- ...SublinksCommunityModerationController.java | 39 +++++++--- .../services/SublinksCommunityService.java | 75 ++++++++++++------- .../enums/RolePermissionCommentTypes.java | 4 +- 10 files changed, 225 insertions(+), 59 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/CommentDelete.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/CommentRemove.java 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 d837f9f7..7c7ca9c6 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 @@ -5,6 +5,7 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; +import org.mapstruct.Named; import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; @@ -31,4 +32,5 @@ public interface PostMapper extends Converter new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); + if (!Objects.equals(comment.getPerson() + .getId(), person.getId())) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_update_not_permitted"); + } + if (updateCommentForm.featured() != null) { comment.setFeatured(updateCommentForm.featured()); } @@ -214,7 +222,66 @@ public CommentResponse updateComment(UpdateComment updateCommentForm, Person per comment.setLanguage(language); } - commentRepository.save(comment); + commentService.updateComment(comment); + return conversionService.convert(comment, CommentResponse.class); + } + + /** + * Removes a comment based on the provided key, comment remove form, and person. + * + * @param key The key of the comment to be removed. + * @param commentRemoveForm The CommentRemove object representing the remove form data. + * @param person The Person object representing the user performing the removal. + * @return A CommentResponse object representing the removed comment. + * @throws ResponseStatusException If the user does not have permission to remove the comment or + * the comment is not found. + */ + public CommentResponse remove(String key, CommentRemove commentRemoveForm, Person person) { + + rolePermissionService.isPermitted(person, RolePermissionCommentTypes.REMOVE_COMMENT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_remove_not_permitted")); + + Comment comment = commentRepository.findByPath(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); + + comment.setRemovedState( + commentRemoveForm.remove() != null ? commentRemoveForm.remove() ? RemovedState.REMOVED + : RemovedState.NOT_REMOVED : RemovedState.REMOVED); + commentService.updateComment(comment); + // @todo: modlog + + return conversionService.convert(comment, CommentResponse.class); + } + + /** + * Deletes a comment based on the provided key, comment delete form, and person. + * + * @param key The key of the comment to be deleted. + * @param commentDeleteForm The CommentDelete object representing the delete form data. + * @param person The Person object representing the user performing the deletion. + * @return A CommentResponse object representing the deleted comment. + * @throws ResponseStatusException If the user does not have permission to delete the comment or + * the comment is not found. + */ + public CommentResponse delete(String key, CommentDelete commentDeleteForm, Person person) { + + rolePermissionService.isPermitted(person, RolePermissionCommentTypes.DELETE_COMMENT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_delete_not_permitted")); + + Comment comment = commentRepository.findByPath(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); + + if (!Objects.equals(comment.getPerson() + .getId(), person.getId())) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_delete_not_permitted"); + } + + comment.setDeleted(commentDeleteForm.remove()); + commentService.updateComment(comment); + // @todo: modlog + return conversionService.convert(comment, CommentResponse.class); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java index ca9c88ed..7873c1d8 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java @@ -25,7 +25,7 @@ @RestController @AllArgsConstructor @RequestMapping("/api/v1/community/{key}/aggregate") -@Tag(name = "Community", description = "Community Aggregation API") +@Tag(name = "Community Aggregation", description = "Community Aggregation API") public class SublinksCommunityAggregationController extends AbstractSublinksApiController { private final CommunityAggregateRepository communityAggregateRepository; @@ -37,8 +37,7 @@ public class SublinksCommunityAggregationController extends AbstractSublinksApiC @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityAggregatesResponse show(@PathVariable final String key, - final SublinksJwtPerson sublinksJwtPerson) - { + final SublinksJwtPerson sublinksJwtPerson) { final Optional person = getOptionalPerson(sublinksJwtPerson); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index bb95b335..425a234a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -47,8 +47,7 @@ public class SublinksCommunityController extends AbstractSublinksApiController { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public List index( @RequestParam(required = false) final Optional indexCommunityParam, - final SublinksJwtPerson sublinksJwtPerson) - { + final SublinksJwtPerson sublinksJwtPerson) { final Optional person = getOptionalPerson(sublinksJwtPerson); @@ -63,14 +62,10 @@ public List index( @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse show(@PathVariable final String key) { - try { - return communityRepository.findCommunityByTitleSlug(key) - .map(comm -> conversionService.convert(comm, CommunityResponse.class)) - .orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Community not found")); - } catch (Exception e) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Community not found"); - } + return communityRepository.findCommunityByTitleSlug(key) + .map(comm -> conversionService.convert(comm, CommunityResponse.class)) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Community not found")); } @Operation(summary = "Create a new community") @@ -78,8 +73,7 @@ public CommunityResponse show(@PathVariable final String key) { @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse create(@RequestBody @Valid final CreateCommunity createCommunity, - final SublinksJwtPerson sublinksJwtPerson) - { + final SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); @@ -90,9 +84,8 @@ public CommunityResponse create(@RequestBody @Valid final CreateCommunity create @PostMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommunityResponse update(@PathVariable String key, - @RequestBody @Valid UpdateCommunity updateCommunityForm, final SublinksJwtPerson principal) - { + public CommunityResponse update(@PathVariable final String key, + @RequestBody @Valid UpdateCommunity updateCommunityForm, final SublinksJwtPerson principal) { final Person person = getPersonOrThrowUnauthorized(principal); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java index d7193b72..684c8afc 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -4,6 +4,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityBanPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityDelete; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityModeratorResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityRemove; import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; @@ -20,6 +21,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import java.util.List; import java.util.Optional; import lombok.AllArgsConstructor; @@ -27,6 +29,7 @@ import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; @@ -34,7 +37,7 @@ @RestController @AllArgsConstructor @RequestMapping("/api/v1/community/{key}/moderation") -@Tag(name = "Community", description = "Community Moderation API") +@Tag(name = "Community Moderation", description = "Community Moderation API") public class SublinksCommunityModerationController extends AbstractSublinksApiController { private final LinkPersonCommunityService linkPersonCommunityService; @@ -44,9 +47,26 @@ public class SublinksCommunityModerationController extends AbstractSublinksApiCo private final ConversionService conversionService; private final RolePermissionService rolePermissionService; + @Operation(summary = "Delete a community") + @GetMapping("/delete") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public CommunityResponse delete(@PathVariable final String key, + @RequestBody @Valid CommunityDelete communityDeleteForm, + SublinksJwtPerson sublinksJwtPerson) { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksCommunityService.delete(key, communityDeleteForm, person); + } + + @Operation(summary = "Remove a community") + @GetMapping("/remove") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse remove(@PathVariable final String key, - CommunityRemove communityRemoveForm, SublinksJwtPerson sublinksJwtPerson) - { + @RequestBody @Valid CommunityRemove communityRemoveForm, + SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); @@ -59,8 +79,7 @@ public CommunityResponse remove(@PathVariable final String key, @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public List show(@PathVariable final String key, - final SublinksJwtPerson sublinksJwtPerson) - { + final SublinksJwtPerson sublinksJwtPerson) { Optional person = getOptionalPerson(sublinksJwtPerson); @@ -82,8 +101,7 @@ public List show(@PathVariable final String key, @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public List add(@PathVariable final String key, - @PathVariable final String personKey, final SublinksJwtPerson sublinksJwtPerson) - { + @PathVariable final String personKey, final SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); @@ -122,8 +140,7 @@ public List add(@PathVariable final String key, @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public List remove(@PathVariable final String key, - @PathVariable final String personKey) - { + @PathVariable final String personKey) { final Community community = communityRepository.findCommunityByTitleSlug(key) .orElseThrow( @@ -151,8 +168,7 @@ public List remove(@PathVariable final String key, @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public PersonResponse ban(@PathVariable final String key, @PathVariable final String personKey, - CommunityBanPerson communityBanPersonForm, final SublinksJwtPerson sublinksJwtPerson) - { + CommunityBanPerson communityBanPersonForm, final SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); @@ -188,4 +204,5 @@ public List banned(@PathVariable final String key) { .map(person -> conversionService.convert(person, PersonResponse.class)) .toList(); } + } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index 93f48a66..828e6037 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -7,6 +7,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.IndexCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityBanPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityDelete; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityRemove; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.utils.ActorIdUtils; @@ -50,8 +51,7 @@ public class SublinksCommunityService { * @param person The person requesting the community responses. * @return The list of CommunityResponse objects that match the search criteria. */ - public List index(IndexCommunity indexCommunityParam, Person person) - { + public List index(IndexCommunity indexCommunityParam, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.READ_COMMUNITY, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, @@ -161,21 +161,17 @@ public CommunityResponse createCommunity(CreateCommunity createCommunity, Person * to perform the update. */ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommunityForm, - Person person) - { + Person person) { String domain = ActorIdUtils.getActorDomain(key); if (domain != null && domain.equals(localInstanceContext.instance() .getDomain())) { key = ActorIdUtils.getActorId(key); } - Optional foundCommunity = communityRepository.findCommunityByTitleSlug(key); - - if (foundCommunity.isEmpty()) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found"); - } + Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - Community community = foundCommunity.get(); if (!(linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) && rolePermissionService.isPermitted(person, @@ -229,20 +225,18 @@ public List getCommunityModerators(String key, Person perso } /** - * Bans or unbans a person in a community. + * Bans a person from a community. * - * @param key The key of the community. - * @param personKey The key of the person to ban or unban. - * @param person The person performing the ban or unban. - * @param ban Determines whether to ban or unban the person. True to ban, false to unban. - * @return The banned or unbanned person. - * @throws ResponseStatusException If the community is not found, the person is not found, the - * person is not authorized to ban or unban, or the person is - * already banned and attempting to ban again. + * @param key The key of the community. + * @param personKey The key of the person to ban. + * @param person The person performing the ban action. + * @param communityBanPersonForm The form containing ban information. + * @return The banned person. + * @throws ResponseStatusException If the community or person is not found, or if the person is + * not authorized to perform the ban action. */ public Person banPerson(String key, String personKey, Person person, - CommunityBanPerson communityBanPersonForm) - { + CommunityBanPerson communityBanPersonForm) { final Community community = communityRepository.findCommunityByTitleSlug(key) .orElseThrow( @@ -252,8 +246,7 @@ public Person banPerson(String key, String personKey, Person person, .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); if (communityBanPersonForm.ban() && linkPersonCommunityService.hasAnyLinkOrAdmin(personToBan, - community, - List.of(LinkPersonCommunityType.banned))) { + community, List.of(LinkPersonCommunityType.banned))) { return personToBan; } @@ -296,19 +289,47 @@ public CommunityResponse remove(String key, CommunityRemove removeComment, Perso .orElseThrow( () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - if (!(linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, - List.of(LinkPersonCommunityType.owner)) && rolePermissionService.isPermitted(person, - RolePermissionCommunityTypes.MODERATOR_REMOVE_COMMUNITY)) - && !rolePermissionService.isPermitted(person, + if (!rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.ADMIN_REMOVE_COMMUNITY)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_remove_community"); } community.setRemoved(removeComment.remove() != null ? removeComment.remove() : true); + communityService.updateCommunity(community); // @todo: modlog + return conversionService.convert(community, CommunityResponse.class); + } + + /** + * Deletes a community based on the provided key, delete form, and person. + * + * @param key The key of the community to delete. + * @param communityDeleteForm The delete form specifying the reason for deletion and whether to + * remove the community. + * @param person The person performing the deletion. + * @return The response containing the deleted community. + * @throws ResponseStatusException If the community is not found, or the person is not authorized + * to delete the community. + */ + public CommunityResponse delete(String key, CommunityDelete communityDeleteForm, Person person) { + + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + + if (!(linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.owner)) && rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.MODERATOR_REMOVE_COMMUNITY))) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_remove_community"); + } + + community.setDeleted( + communityDeleteForm.remove() != null ? communityDeleteForm.remove() : true); + communityService.updateCommunity(community); + // @todo: modlog return conversionService.convert(community, CommunityResponse.class); } diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommentTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommentTypes.java index 65a6d51f..3c4f94f7 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommentTypes.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommentTypes.java @@ -16,6 +16,7 @@ public enum RolePermissionCommentTypes implements RolePermissionInterface { COMMENT_UPVOTE("comment-upvote", AuthorizeAction.CREATE), COMMENT_DOWNVOTE("comment-downvote", AuthorizeAction.CREATE), COMMENT_NEUTRALVOTE("comment-neutralvote", AuthorizeAction.CREATE), + REPORT_COMMENT("comment-report", AuthorizeAction.CREATE), // Moderator permissions MODERATOR_REMOVE_COMMENT("comment-moderator", AuthorizeAction.DELETE), @@ -26,8 +27,7 @@ public enum RolePermissionCommentTypes implements RolePermissionInterface { // Admin permissions ADMIN_SHOW_DELETED_COMMENT("comment-admin", AuthorizeAction.READ), - ADMIN_SPEAK("admin-speak", AuthorizeAction.CREATE), - REPORT_COMMENT("comment-report", AuthorizeAction.CREATE); + ADMIN_SPEAK("admin-speak", AuthorizeAction.CREATE); public final String Entity; public final AuthorizeAction Action; From 430baa2155fe0ec159825bcdb22d7c57309f767c Mon Sep 17 00:00:00 2001 From: rooki Date: Sat, 25 May 2024 19:43:05 +0200 Subject: [PATCH 041/115] Add comment pinning feature and refactor HTTP methods The commit includes the implementation of a new comment pinning feature in the comment service and controller, which will allow moderators to pin and unpin comments. It also includes changes in HTTP methods from 'GET' to 'POST' in several places to align with REST best practices for methods that change the state of the data. Some formatting adjustments for improve code readability are also included. Signed-off-by: rooki --- .../v3/post/controllers/PostController.java | 48 ++-- .../SublinksCommentModerationController.java | 18 +- .../comment/models/Moderation/CommentPin.java | 5 + .../services/SublinksCommentService.java | 25 ++ ...SublinksCommunityModerationController.java | 255 ++++++++---------- .../controllers/SublinksRolesController.java | 1 + 6 files changed, 186 insertions(+), 166 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/CommentPin.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostController.java index 6fec0675..d10c445a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostController.java @@ -105,9 +105,11 @@ public class PostController extends AbstractLemmyApiController { @Operation(summary = "Get / fetch a post.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = GetPostResponse.class))}), + @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = GetPostResponse.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 = ApiError.class))})}) @GetMapping GetPostResponse show(@Valid final GetPost getPostForm, final JwtPerson principal) { @@ -158,13 +160,14 @@ GetPostResponse show(@Valid final GetPost getPostForm, final JwtPerson principal @Operation(summary = "Mark a post as read.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PostResponse.class))}), + @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 = ApiError.class))})}) @PostMapping("mark_as_read") PostResponse markAsRead(@Valid @RequestBody final MarkPostAsRead markPostAsReadForm, - final JwtPerson principal) - { + final JwtPerson principal) { final Person person = getPersonOrThrowBadRequest(principal); @@ -182,7 +185,8 @@ PostResponse markAsRead(@Valid @RequestBody final MarkPostAsRead markPostAsReadF @Operation(summary = "Get / fetch posts, with various filters.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = GetPostsResponse.class))})}) + @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = GetPostsResponse.class))})}) @GetMapping("list") @Transactional(readOnly = true) public GetPostsResponse index(@Valid final GetPosts getPostsForm, final JwtPerson principal) { @@ -293,9 +297,11 @@ public GetPostsResponse index(@Valid final GetPosts getPostsForm, final JwtPerso @Operation(summary = "Like / vote on a post.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PostResponse.class))}), + @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 = ApiError.class))})}) @PostMapping("like") PostResponse like(@Valid @RequestBody CreatePostLike createPostLikeForm, JwtPerson principal) { @@ -324,9 +330,11 @@ PostResponse like(@Valid @RequestBody CreatePostLike createPostLikeForm, JwtPers @Operation(summary = "Get Votes on a post.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PostResponse.class))}), + @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 = ApiError.class))})}) @GetMapping("like/list") ListPostLikesResponse listLikes(@Valid ListPostLikes listPostLikesForm, JwtPerson principal) { @@ -354,9 +362,11 @@ ListPostLikesResponse listLikes(@Valid ListPostLikes listPostLikesForm, JwtPerso @Operation(summary = "Save a post.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PostResponse.class))}), + @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 = ApiError.class))})}) @PutMapping("save") public PostResponse saveForLater(@Valid @RequestBody SavePost savePostForm, JwtPerson principal) { @@ -379,13 +389,14 @@ public PostResponse saveForLater(@Valid @RequestBody SavePost savePostForm, JwtP @Operation(summary = "Report a post.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PostReportResponse.class))}), + @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = PostReportResponse.class))}), @ApiResponse(responseCode = "400", description = "Post Not Found", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ResponseStatusException.class))})}) + @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ResponseStatusException.class))})}) @PostMapping("report") PostReportResponse report(@Valid @RequestBody final CreatePostReport createPostReportForm, - final JwtPerson principal) - { + final JwtPerson principal) { final Person person = getPersonOrThrowUnauthorized(principal); @@ -414,7 +425,8 @@ PostReportResponse report(@Valid @RequestBody final CreatePostReport createPostR @Operation(summary = "Fetch metadata for any given site.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = GetSiteMetadataResponse.class))})}) + @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = GetSiteMetadataResponse.class))})}) @GetMapping("site_metadata") public GetSiteMetadataResponse siteMetadata(@Valid GetSiteMetadata getSiteMetadataForm) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java index 5c208f4b..fbb866a4 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java @@ -3,6 +3,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.CommentDelete; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.CommentPin; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.CommentRemove; import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; @@ -15,6 +16,7 @@ import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -28,7 +30,7 @@ public class SublinksCommentModerationController extends AbstractSublinksApiCont private final SublinksCommentService sublinksCommentService; @Operation(summary = "Remove a comment") - @GetMapping("/remove") + @PostMapping("/remove") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommentResponse remove(@PathVariable final String key, @@ -41,7 +43,7 @@ public CommentResponse remove(@PathVariable final String key, } @Operation(summary = "Delete a comment") - @GetMapping("/delete") + @PostMapping("/delete") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommentResponse delete(@PathVariable final String key, @@ -52,4 +54,16 @@ public CommentResponse delete(@PathVariable final String key, return sublinksCommentService.delete(key, commentDeleteForm, person); } + + @Operation(summary = "Pin/Unpin a comment") + @GetMapping("/pin") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public CommentResponse highlight(@PathVariable final String key, final CommentPin commentPinForm, + final SublinksJwtPerson sublinksJwtPerson) { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksCommentService.pin(key, commentPinForm, person); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/CommentPin.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/CommentPin.java new file mode 100644 index 00000000..0825cc41 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/CommentPin.java @@ -0,0 +1,5 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation; + +public record CommentPin(Boolean pin) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index be2dcfae..9bf1e51c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -4,6 +4,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CreateComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.IndexComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.CommentDelete; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.CommentPin; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.CommentRemove; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.UpdateComment; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; @@ -22,7 +23,9 @@ import com.sublinks.sublinksapi.language.repositories.LanguageRepository; import com.sublinks.sublinksapi.language.services.LanguageService; import com.sublinks.sublinksapi.person.entities.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.post.entities.Post; import com.sublinks.sublinksapi.post.repositories.PostRepository; import com.sublinks.sublinksapi.shared.RemovedState; @@ -48,6 +51,7 @@ public class SublinksCommentService { private final PostRepository postRepository; private final LanguageService languageService; private final RolePermissionService rolePermissionService; + private final LinkPersonCommunityService linkPersonCommunityService; /** * Retrieves a list of CommentResponse objects based on the provided IndexComment and Person @@ -284,4 +288,25 @@ public CommentResponse delete(String key, CommentDelete commentDeleteForm, Perso return conversionService.convert(comment, CommentResponse.class); } + + public CommentResponse pin(String key, CommentPin commentPinForm, Person person) { + + rolePermissionService.isPermitted(person, RolePermissionCommentTypes.MODERATOR_PIN_COMMENT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_highlight_not_permitted")); + + Comment comment = commentRepository.findByPath(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); + if (linkPersonCommunityService.hasAnyLinkOrAdmin(person, comment.getCommunity(), + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_highlight_not_permitted"); + } + + comment.setFeatured( + commentPinForm.pin() != null ? commentPinForm.pin() : !comment.isFeatured()); + + commentService.updateComment(comment); + + return conversionService.convert(comment, CommentResponse.class); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java index 684c8afc..f9c5a48f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -22,187 +22,150 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -import java.util.List; -import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import org.springframework.web.server.ResponseStatusException; +import java.util.List; +import java.util.Optional; + @RestController @AllArgsConstructor @RequestMapping("/api/v1/community/{key}/moderation") @Tag(name = "Community Moderation", description = "Community Moderation API") public class SublinksCommunityModerationController extends AbstractSublinksApiController { - private final LinkPersonCommunityService linkPersonCommunityService; - private final SublinksCommunityService sublinksCommunityService; - private final CommunityRepository communityRepository; - private final PersonRepository personRepository; - private final ConversionService conversionService; - private final RolePermissionService rolePermissionService; - - @Operation(summary = "Delete a community") - @GetMapping("/delete") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommunityResponse delete(@PathVariable final String key, - @RequestBody @Valid CommunityDelete communityDeleteForm, - SublinksJwtPerson sublinksJwtPerson) { - - final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - - return sublinksCommunityService.delete(key, communityDeleteForm, person); - } - - @Operation(summary = "Remove a community") - @GetMapping("/remove") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommunityResponse remove(@PathVariable final String key, - @RequestBody @Valid CommunityRemove communityRemoveForm, - SublinksJwtPerson sublinksJwtPerson) { - - final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - - return sublinksCommunityService.remove(key, communityRemoveForm, person); - } - - - @Operation(summary = "Get moderators of the community") - @GetMapping("/moderators") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List show(@PathVariable final String key, - final SublinksJwtPerson sublinksJwtPerson) { - - Optional person = getOptionalPerson(sublinksJwtPerson); - - rolePermissionService.isPermitted(person.orElse(null), - RolePermissionCommunityTypes.READ_COMMUNITY_MODERATORS, () -> { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_read_moderators"); - }); + private final LinkPersonCommunityService linkPersonCommunityService; + private final SublinksCommunityService sublinksCommunityService; + private final CommunityRepository communityRepository; + private final PersonRepository personRepository; + private final ConversionService conversionService; + private final RolePermissionService rolePermissionService; + + @Operation(summary = "Delete a community") + @PostMapping("/delete") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public CommunityResponse delete(@PathVariable final String key, @RequestBody @Valid CommunityDelete communityDeleteForm, SublinksJwtPerson sublinksJwtPerson) { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksCommunityService.delete(key, communityDeleteForm, person); + } + + @Operation(summary = "Remove a community") + @GetMapping("/remove") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public CommunityResponse remove(@PathVariable final String key, @RequestBody @Valid CommunityRemove communityRemoveForm, SublinksJwtPerson sublinksJwtPerson) { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - return sublinksCommunityService.getCommunityModerators(key, person.orElse(null)) - .stream() - .map(communityModerator -> conversionService.convert(communityModerator, - CommunityModeratorResponse.class)) - .toList(); - } - - @Operation(summary = "Add a moderator to the community") - @GetMapping("/moderator/add/{personKey}") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List add(@PathVariable final String key, - @PathVariable final String personKey, final SublinksJwtPerson sublinksJwtPerson) { - - final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - - final Community community = communityRepository.findCommunityByTitleSlug(key) - .orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - - if (!(linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) - && rolePermissionService.isPermitted(person, - RolePermissionCommunityTypes.MODERATOR_ADD_MODERATOR)) - && !rolePermissionService.isPermitted(person, - RolePermissionCommunityTypes.ADMIN_ADD_COMMUNITY_MODERATOR)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_add_moderator"); + return sublinksCommunityService.remove(key, communityRemoveForm, person); } - final Person newModerator = personRepository.findOneByNameIgnoreCase(personKey) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); - if (linkPersonCommunityService.hasAnyLinkOrAdmin(newModerator, community, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "person_already_moderator"); + @Operation(summary = "Get moderators of the community") + @GetMapping("/moderators") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List show(@PathVariable final String key, final SublinksJwtPerson sublinksJwtPerson) { + + Optional person = getOptionalPerson(sublinksJwtPerson); + + rolePermissionService.isPermitted(person.orElse(null), RolePermissionCommunityTypes.READ_COMMUNITY_MODERATORS, () -> { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_read_moderators"); + }); + + return sublinksCommunityService.getCommunityModerators(key, person.orElse(null)) + .stream() + .map(communityModerator -> conversionService.convert(communityModerator, CommunityModeratorResponse.class)) + .toList(); } - linkPersonCommunityService.addLink(newModerator, community, LinkPersonCommunityType.moderator); + @Operation(summary = "Add a moderator to the community") + @PostMapping("/moderator/add/{personKey}") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List add(@PathVariable final String key, @PathVariable final String personKey, final SublinksJwtPerson sublinksJwtPerson) { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - return sublinksCommunityService.getCommunityModerators(key, person) - .stream() - .map(communityModerator -> conversionService.convert(communityModerator, - CommunityModeratorResponse.class)) - .toList(); - } + if (!(linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) && rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.MODERATOR_ADD_MODERATOR)) && !rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.ADMIN_ADD_COMMUNITY_MODERATOR)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_add_moderator"); + } - @Operation(summary = "Remove a moderator from the community") - @GetMapping("/moderator/remove/{personKey}") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List remove(@PathVariable final String key, - @PathVariable final String personKey) { + final Person newModerator = personRepository.findOneByNameIgnoreCase(personKey) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); - final Community community = communityRepository.findCommunityByTitleSlug(key) - .orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + if (linkPersonCommunityService.hasAnyLinkOrAdmin(newModerator, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "person_already_moderator"); + } - final Person person = personRepository.findOneByNameIgnoreCase(personKey) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + linkPersonCommunityService.addLink(newModerator, community, LinkPersonCommunityType.moderator); - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_remove_moderator"); + return sublinksCommunityService.getCommunityModerators(key, person) + .stream() + .map(communityModerator -> conversionService.convert(communityModerator, CommunityModeratorResponse.class)) + .toList(); } - linkPersonCommunityService.removeLink(person, community, LinkPersonCommunityType.moderator); + @Operation(summary = "Remove a moderator from the community") + @PostMapping("/moderator/remove/{personKey}") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List remove(@PathVariable final String key, @PathVariable final String personKey) { - return sublinksCommunityService.getCommunityModerators(key, person) - .stream() - .map(communityModerator -> conversionService.convert(communityModerator, - CommunityModeratorResponse.class)) - .toList(); - } + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - @Operation(summary = "Ban/Unban a user from a community") - @GetMapping("/ban/{personKey}") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public PersonResponse ban(@PathVariable final String key, @PathVariable final String personKey, - CommunityBanPerson communityBanPersonForm, final SublinksJwtPerson sublinksJwtPerson) { + final Person person = personRepository.findOneByNameIgnoreCase(personKey) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); - final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_remove_moderator"); + } - final Community community = communityRepository.findCommunityByTitleSlug(key) - .orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + linkPersonCommunityService.removeLink(person, community, LinkPersonCommunityType.moderator); - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, - List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_ban_user"); + return sublinksCommunityService.getCommunityModerators(key, person) + .stream() + .map(communityModerator -> conversionService.convert(communityModerator, CommunityModeratorResponse.class)) + .toList(); } - final Person bannedPerson = sublinksCommunityService.banPerson(key, personKey, person, - communityBanPersonForm); + @Operation(summary = "Ban/Unban a user from a community") + @PostMapping("/ban/{personKey}") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PersonResponse ban(@PathVariable final String key, @PathVariable final String personKey, CommunityBanPerson communityBanPersonForm, final SublinksJwtPerson sublinksJwtPerson) { - return conversionService.convert(bannedPerson, PersonResponse.class); - } + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - @Operation(summary = "Get banned users from a community") - @GetMapping("/banned") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List banned(@PathVariable final String key) { + if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_ban_user"); + } - final Community community = communityRepository.findCommunityByTitleSlug(key) - .orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + final Person bannedPerson = sublinksCommunityService.banPerson(key, personKey, person, communityBanPersonForm); - return linkPersonCommunityService.getPersonsFromCommunityAndListTypes(community, - List.of(LinkPersonCommunityType.banned)) - .stream() - .map(person -> conversionService.convert(person, PersonResponse.class)) - .toList(); - } + return conversionService.convert(bannedPerson, PersonResponse.class); + } + + + @Operation(summary = "Get banned users from a community") + @GetMapping("/banned") + @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List banned(@PathVariable final String key) { + + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + + return linkPersonCommunityService.getPersonsFromCommunityAndListTypes(community, List.of(LinkPersonCommunityType.banned)) + .stream() + .map(person -> conversionService.convert(person, PersonResponse.class)) + .toList(); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java index 34d817bf..97acddd1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java @@ -2,6 +2,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.*; From 815c3f04e95853b143c16c222de6ff8a9dae9d6f Mon Sep 17 00:00:00 2001 From: rooki Date: Sun, 26 May 2024 17:56:19 +0200 Subject: [PATCH 042/115] Add comment pinning functionality and refactor HTTP methods This commit introduces the ability for moderators to pin and unpin comments through the new functionality in the comment service and controller. It also replaces several 'GET' HTTP methods with 'POST' for compliance with REST standards on state-changing operations. Additionally, some code formatting was adjusted for better readability. Signed-off-by: rooki --- .editorconfig | 3 +- .../sublinks/v1/common/enums/SortOrder.java | 6 + .../models/moderation/IndexBannedPerson.java | 13 ++ .../v1/languages/models/LanguageResponse.java | 1 + .../controllers/SublinksPersonController.java | 20 ++- .../SublinksPersonModerationController.java | 39 +++++ .../person/mappers/SublinksPersonMapper.java | 31 ++-- .../v1/person/models/CreatePerson.java | 21 ++- .../v1/person/models/LoginPerson.java | 4 +- .../v1/person/models/LoginResponse.java | 12 +- .../v1/person/models/PersonIdentity.java | 9 ++ .../v1/person/models/UpdatePerson.java | 21 ++- .../person/models/moderation/BanPerson.java | 23 +++ .../services/SublinksPersonService.java | 134 +++++++++++------- .../person/repositories/PersonRepository.java | 12 +- .../services/LinkPersonPostService.java | 21 +-- 16 files changed, 262 insertions(+), 108 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SortOrder.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/moderation/IndexBannedPerson.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonIdentity.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/moderation/BanPerson.java diff --git a/.editorconfig b/.editorconfig index 91b450b9..e3661aea 100644 --- a/.editorconfig +++ b/.editorconfig @@ -191,7 +191,6 @@ ij_java_message_eb_prefix = ij_java_message_eb_suffix = Bean ij_java_method_annotation_wrap = split_into_lines ij_java_method_brace_style = next_line_if_wrapped -ij_java_method_brace_style = end_of_line ij_java_method_call_chain_wrap = split_into_lines ij_java_method_parameters_new_line_after_left_paren = false ij_java_method_parameters_right_paren_on_new_line = false @@ -201,7 +200,7 @@ ij_java_multi_catch_types_wrap = normal ij_java_names_count_to_use_import_on_demand = 999 ij_java_new_line_after_lparen_in_annotation = false ij_java_new_line_after_lparen_in_deconstruction_pattern = true -ij_java_new_line_after_lparen_in_record_header = false +ij_java_new_line_after_lparen_in_record_header = true ij_java_packages_to_use_import_on_demand = ij_java_parameter_annotation_wrap = on_every_item ij_java_parameter_name_prefix = diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SortOrder.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SortOrder.java new file mode 100644 index 00000000..ff1bb00a --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/enums/SortOrder.java @@ -0,0 +1,6 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.common.enums; + +public enum SortOrder { + Asc, + Desc, +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/moderation/IndexBannedPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/moderation/IndexBannedPerson.java new file mode 100644 index 00000000..61158a75 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/moderation/IndexBannedPerson.java @@ -0,0 +1,13 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.instance.models.moderation; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortOrder; +import lombok.Builder; + +@Builder +public record IndexBannedPerson(String search, + Boolean local, + SortOrder sortOrder, + Integer limit, + Integer page) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/models/LanguageResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/models/LanguageResponse.java index 26d0400b..3b00278f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/models/LanguageResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/models/LanguageResponse.java @@ -1,5 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.languages.models; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; @Builder diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java index 1e44315b..4a14f0bd 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java @@ -6,6 +6,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.person.models.IndexPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonIdentity; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.UpdatePerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; @@ -18,7 +19,6 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import java.util.List; -import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.PageRequest; @@ -61,10 +61,11 @@ public List index(final IndexPerson indexPerson) { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public PersonResponse show(@PathVariable String key) { - Optional personResponse = personRepository.findOneByNameIgnoreCase(key) - .map(person -> conversionService.convert(person, PersonResponse.class)); + final PersonIdentity ids = sublinksPersonService.getPersonIdentifiersFromKey(key); - return personResponse.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + return personRepository.findOneByNameAndInstance_Domain(ids.name(), ids.domain()) + .map(person -> conversionService.convert(person, PersonResponse.class)) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); } @Operation(summary = "Register a new person") @@ -72,20 +73,18 @@ public PersonResponse show(@PathVariable String key) { @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public LoginResponse create(final HttpServletRequest request, - @RequestBody @Valid final CreatePerson createPerson) - { + @RequestBody @Valid final CreatePerson createPerson) { return sublinksPersonService.registerPerson(createPerson, request.getRemoteAddr(), request.getHeader("User-Agent")); } @Operation(summary = "Log into a user") - @PostMapping("/{key}/login") + @PostMapping("/login") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public LoginResponse login(final HttpServletRequest request, - @RequestBody @Valid final LoginPerson loginPerson) - { + @RequestBody @Valid final LoginPerson loginPerson) { return sublinksPersonService.login(loginPerson, request.getRemoteAddr(), request.getHeader("User-Agent")); @@ -96,8 +95,7 @@ public LoginResponse login(final HttpServletRequest request, @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public PersonResponse update(@PathVariable String key, - @RequestBody @Valid final UpdatePerson updatePersonForm, final SublinksJwtPerson principal) - { + @RequestBody @Valid final UpdatePerson updatePersonForm, final SublinksJwtPerson principal) { final Person person = getPersonOrThrowUnauthorized(principal); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java new file mode 100644 index 00000000..7ec29d08 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java @@ -0,0 +1,39 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.moderation.BanPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; +import com.sublinks.sublinksapi.person.repositories.PersonRepository; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.ConversionService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("api/v1/person/{key}/moderation") +@Tag(name = "Person Moderation", description = "Person Moderation API") +@AllArgsConstructor +public class SublinksPersonModerationController extends AbstractSublinksApiController { + + private final PersonRepository personRepository; + private final SublinksPersonService sublinksPersonService; + private final ConversionService conversionService; + + @Operation(summary = "Ban a person") + @GetMapping("/ban") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) + }) + public PersonResponse ban(@RequestBody @Valid BanPerson banPersonForm) { + + return null; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java index f38887e4..408769d9 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java @@ -20,7 +20,7 @@ public abstract class SublinksPersonMapper implements Converter mapBanExpiresAt(Person person) { simpleDateFormat.applyPattern(DateUtils.FRONT_END_DATE_FORMAT); - return Optional.ofNullable(person.getRole().getExpiresAt() != null ? simpleDateFormat.format( - person.getRole().getExpiresAt()) : null); + return Optional.ofNullable(person.getRole() + .getExpiresAt() != null ? simpleDateFormat.format(person.getRole() + .getExpiresAt()) : null); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/CreatePerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/CreatePerson.java index 62e0b031..af302cd3 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/CreatePerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/CreatePerson.java @@ -2,22 +2,21 @@ import com.sublinks.sublinksapi.api.sublinks.v1.languages.models.LanguageResponse; import java.util.List; -import java.util.Optional; import lombok.Builder; @Builder public record CreatePerson(String name, String displayName, - Optional email, + String email, List languages, - Optional avatarImageUrl, - Optional bannerImageUrl, - Optional bio, - Optional matrixUserId, - Optional password, - Optional passwordConfirmation, - Optional answer, - Optional captcha_token, - Optional captcha_answer) { + String avatarImageUrl, + String bannerImageUrl, + String bio, + String matrixUserId, + String password, + String passwordConfirmation, + String answer, + String captcha_token, + String captcha_answer) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginPerson.java index 67a1770e..e970526e 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginPerson.java @@ -6,7 +6,7 @@ @Builder public record LoginPerson(String username, String password, - Optional captcha_token, - Optional captcha_answer) { + String captcha_token, + String captcha_answer) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginResponse.java index 01d731a5..17074aa1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginResponse.java @@ -1,11 +1,15 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.models; -import java.util.Optional; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import lombok.Builder; @Builder -public record LoginResponse(Optional token, - RegistrationState status, - Optional error) { +public record LoginResponse( + @Schema(description = "The token to be used in the Authorization header for future requests", + requiredMode = RequiredMode.NOT_REQUIRED) String token, + RegistrationState status, + @Schema(description = "The error message if the login failed", + requiredMode = RequiredMode.NOT_REQUIRED) String error) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonIdentity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonIdentity.java new file mode 100644 index 00000000..d1a82cdd --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonIdentity.java @@ -0,0 +1,9 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.models; + +import lombok.Builder; + +@Builder +public record PersonIdentity(String name, + String domain) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/UpdatePerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/UpdatePerson.java index 61e68ac6..9bb107ce 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/UpdatePerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/UpdatePerson.java @@ -1,19 +1,18 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.models; import java.util.List; -import java.util.Optional; import lombok.Builder; @Builder -public record UpdatePerson(Optional displayName, - Optional email, - Optional> languagesKeys, - Optional avatarImageUrl, - Optional bannerImageUrl, - Optional bio, - Optional matrixUserId, - Optional oldPassword, - Optional password, - Optional passwordConfirmation) { +public record UpdatePerson(String displayName, + String email, + List languagesKeys, + String avatarImageUrl, + String bannerImageUrl, + String bio, + String matrixUserId, + String oldPassword, + String password, + String passwordConfirmation) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/moderation/BanPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/moderation/BanPerson.java new file mode 100644 index 00000000..9388d7ce --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/moderation/BanPerson.java @@ -0,0 +1,23 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.models.moderation; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import lombok.Builder; + +@Builder +public record BanPerson( + String key, + String reason, + Boolean ban, + @Schema(description = "Remove all data associated with the user", + requiredMode = RequiredMode.NOT_REQUIRED, + defaultValue = "false") Boolean removeData, + @Schema(description = "Unix timestamp when the ban should expire", + requiredMode = RequiredMode.NOT_REQUIRED) Long expirationTimestamp) { + + @Override + public Boolean removeData() { + + return removeData != null && removeData; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index 83bdbf02..1642adc1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -2,12 +2,17 @@ import com.sublinks.sublinksapi.api.lemmy.v3.enums.RegistrationMode; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtUtil; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortOrder; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.CreatePerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonIdentity; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.RegistrationState; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.UpdatePerson; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.moderation.IndexBannedPerson; +import com.sublinks.sublinksapi.authorization.entities.Role; +import com.sublinks.sublinksapi.authorization.services.RoleService; import com.sublinks.sublinksapi.email.entities.Email; import com.sublinks.sublinksapi.email.enums.EmailTemplatesEnum; import com.sublinks.sublinksapi.email.services.EmailService; @@ -30,6 +35,8 @@ import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import org.springframework.web.server.ResponseStatusException; @@ -50,15 +57,44 @@ public class SublinksPersonService { private final UserDataService userDataService; private final ConversionService conversionService; private final LanguageRepository languageRepository; + private final RoleService roleService; /** - * Registers a person. + * Retrieves the name and domain identifiers of a person from the given key. * - * @param createPersonForm The details of the person to be registered. + * @param key The key containing the person's information. If the key contains "@", it is split + * into name and domain using "@" as the separator. Otherwise, the name is set as the + * key and the domain is obtained from the local instance context. + * @return The PersonIdentity object containing the name and domain identifiers of the person. + */ + public PersonIdentity getPersonIdentifiersFromKey(String key) { + + String name, domain; + if (key.contains("@")) { + name = key.substring(0, key.indexOf('@')); + domain = key.substring(key.indexOf('@') + 1); + } else { + name = key; + domain = localInstanceContext.instance() + .getDomain(); + } + + return PersonIdentity.builder() + .name(name) + .domain(domain) + .build(); + } + + /** + * Registers a person with the given details. + * + * @param createPersonForm The form containing the details of the person to be registered. * @param ip The IP address of the client making the registration request. * @param userAgent The user agent string of the client making the registration request. - * @return The login response containing the token, registration status, and error (if any). - * @throws ResponseStatusException if email is required but not provided. + * @return The registration response containing the token, registration status, and error (if + * any). + * @throws ResponseStatusException if the email is required but not provided, or if the email send + * failed. */ public LoginResponse registerPerson(final CreatePerson createPersonForm, final String ip, final String userAgent) @@ -77,14 +113,10 @@ public LoginResponse registerPerson(final CreatePerson createPersonForm, final S final Person.PersonBuilder personBuilder = Person.builder() .name(createPersonForm.name()) .displayName(createPersonForm.displayName()) - .avatarImageUrl(createPersonForm.avatarImageUrl() - .orElse(null)) - .bannerImageUrl(createPersonForm.bannerImageUrl() - .orElse(null)) - .biography(createPersonForm.bio() - .orElse(null)) - .matrixUserId(createPersonForm.matrixUserId() - .orElse(null)); + .avatarImageUrl(createPersonForm.avatarImageUrl()) + .bannerImageUrl(createPersonForm.bannerImageUrl()) + .biography(createPersonForm.bio()) + .matrixUserId(createPersonForm.matrixUserId()); final Person person = personBuilder.build(); @@ -120,9 +152,8 @@ public LoginResponse registerPerson(final CreatePerson createPersonForm, final S } catch (Exception e) { personRepository.delete(person); return LoginResponse.builder() - .token(Optional.empty()) .status(RegistrationState.NOT_CREATED) - .error(Optional.of("email_send_failed")) + .error("email_send_failed") .build(); } } @@ -137,8 +168,7 @@ public LoginResponse registerPerson(final CreatePerson createPersonForm, final S : PersonRegistrationApplicationStatus.pending) .person(person) .question(instanceConfig.getRegistrationQuestion()) - .answer(createPersonForm.answer() - .orElse(null)) + .answer(createPersonForm.answer()) .build()); if (!instanceConfig.isRequireEmailVerification()) { status = RegistrationState.APPLICATION_CREATED; @@ -150,7 +180,7 @@ public LoginResponse registerPerson(final CreatePerson createPersonForm, final S } return LoginResponse.builder() - .token(Optional.ofNullable(token)) + .token(token) .status(status) .build(); } @@ -182,17 +212,15 @@ public LoginResponse login(final LoginPerson loginPersonForm, final String ip, if (person.isDeleted()) { return LoginResponse.builder() - .token(Optional.empty()) .status(RegistrationState.UNCHANGED) - .error(Optional.of("person_deleted")) + .error("person_deleted") .build(); } if (!person.isEmailVerified()) { return LoginResponse.builder() - .token(Optional.empty()) .status(RegistrationState.UNCHANGED) - .error(Optional.of("email_not_verified")) + .error("email_not_verified") .build(); } @@ -202,17 +230,15 @@ public LoginResponse login(final LoginPerson loginPersonForm, final String ip, if (application.isPresent() && application.get() .getApplicationStatus() != PersonRegistrationApplicationStatus.approved) { return LoginResponse.builder() - .token(Optional.empty()) .status(RegistrationState.UNCHANGED) - .error(Optional.of("application_not_approved")) + .error("application_not_approved") .build(); } if (!personService.isValidPersonPassword(person, loginPersonForm.password())) { return LoginResponse.builder() - .token(Optional.empty()) .status(RegistrationState.UNCHANGED) - .error(Optional.of("password_incorrect")) + .error("password_incorrect") .build(); } @@ -221,7 +247,7 @@ public LoginResponse login(final LoginPerson loginPersonForm, final String ip, userDataService.checkAndAddIpRelation(person, ip, token, userAgent); return LoginResponse.builder() - .token(Optional.of(token)) + .token(token) .status(RegistrationState.UNCHANGED) .build(); } @@ -235,45 +261,57 @@ public LoginResponse login(final LoginPerson loginPersonForm, final String ip, */ public PersonResponse updatePerson(Person person, UpdatePerson updatePersonForm) { - if (updatePersonForm.languagesKeys() + if (Optional.ofNullable(updatePersonForm.languagesKeys()) .isPresent()) { - person.setLanguages(languageRepository.findAllByCodeIsIn(updatePersonForm.languagesKeys() - .get())); + person.setLanguages(languageRepository.findAllByCodeIsIn(updatePersonForm.languagesKeys())); } - updatePersonForm.displayName() + Optional.ofNullable(updatePersonForm.displayName()) .ifPresent(person::setDisplayName); - updatePersonForm.email() + Optional.ofNullable(updatePersonForm.email()) .ifPresent(person::setEmail); - updatePersonForm.avatarImageUrl() + Optional.ofNullable(updatePersonForm.avatarImageUrl()) .ifPresent(person::setAvatarImageUrl); - updatePersonForm.bannerImageUrl() + Optional.ofNullable(updatePersonForm.bannerImageUrl()) .ifPresent(person::setBannerImageUrl); - updatePersonForm.bio() + Optional.ofNullable(updatePersonForm.bio()) .ifPresent(person::setBiography); - updatePersonForm.matrixUserId() + Optional.ofNullable(updatePersonForm.matrixUserId()) .ifPresent(person::setMatrixUserId); - if (updatePersonForm.oldPassword() - .isPresent() && updatePersonForm.password() - .isPresent() && updatePersonForm.passwordConfirmation() + if (Optional.ofNullable(updatePersonForm.password()) .isPresent()) { - if (!personService.isValidPersonPassword(person, updatePersonForm.oldPassword() - .get())) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "password_incorrect"); - } + Optional.ofNullable(updatePersonForm.oldPassword()) + .ifPresent(oldPassword -> { + if (!personService.isValidPersonPassword(person, oldPassword)) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "password_incorrect"); + } + }); if (!updatePersonForm.password() - .get() - .equals(updatePersonForm.passwordConfirmation() - .get())) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "password_mismatch"); + .equals(updatePersonForm.passwordConfirmation())) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "passwords_do_not_match"); } - personService.updatePassword(person, updatePersonForm.password() - .get()); + personService.updatePassword(person, updatePersonForm.password()); } personRepository.save(person); return conversionService.convert(person, PersonResponse.class); } + + public List indexBannedPersons(final IndexBannedPerson indexBannedPersonForm) { + + Optional bannedRole = roleService.getBannedRole(); + + if (bannedRole.isEmpty()) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "banned_role_not_found"); + } + + return personRepository.findAllByNameAndBiographyAndRole(indexBannedPersonForm.search(), + bannedRole.get() + .getId(), PageRequest.of(indexBannedPersonForm.limit(), indexBannedPersonForm.page(), + indexBannedPersonForm.sortOrder() == SortOrder.Asc ? Sort.by("name") + .ascending() : Sort.by("name") + .descending())); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java b/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java index 3172305c..f8568afe 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java @@ -19,12 +19,22 @@ public interface PersonRepository extends JpaRepository { Optional findOneByNameIgnoreCase(String name); + Optional findOneByNameAndInstance_Domain(String name, String instance_domain); + Optional findOneByEmail(String email); HashSet findAllByRole(Role role); - @Query(value = "SELECT p FROM people p WHERE p.search_vector @@ to_tsquery('keyword', :keyword)", countQuery = "SELECT COUNT(p.id) FROM people p WHERE p.search_vector @@ to_tsquery('english', :keyword)", nativeQuery = true) + @Query(value = "SELECT p FROM people p WHERE p.search_vector @@ to_tsquery('keyword', :keyword)", + countQuery = "SELECT COUNT(p.id) FROM people p WHERE p.search_vector @@ to_tsquery('english', :keyword)", + nativeQuery = true) List findAllByNameAndBiography(@Param("keyword") String keyword, Pageable pageable); + @Query(value = "SELECT p FROM people p WHERE p.search_vector @@ to_tsquery('keyword', :keyword) AND p.role_id = :role", + countQuery = "SELECT COUNT(p.id) FROM people p WHERE p.search_vector @@ to_tsquery('english', :keyword) AND p.role_id = :role", + nativeQuery = true) + List findAllByNameAndBiographyAndRole(@Param("keyword") String keyword, + @Param("role") long roleId, Pageable pageable); + List findAllByRoleExpireAtBefore(Date expireAt); } diff --git a/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonPostService.java b/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonPostService.java index c01d8617..6ad212eb 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonPostService.java @@ -39,8 +39,10 @@ public void createLink(Person person, Post post, LinkPersonPostType type) { if (person.getLinkPersonPost() == null) { person.setLinkPersonPost(new LinkedHashSet<>()); } - post.getLinkPersonPost().add(newLink); - person.getLinkPersonPost().add(newLink); + post.getLinkPersonPost() + .add(newLink); + person.getLinkPersonPost() + .add(newLink); linkPersonPostRepository.save(newLink); postLikeService.updateOrCreatePostLikeLike(post, person); linkPersonPostCreatedPublisher.publish(newLink); @@ -49,18 +51,17 @@ public void createLink(Person person, Post post, LinkPersonPostType type) { @Transactional public void removeLink(Person person, Post post, LinkPersonPostType type) { - Optional linkPersonPost - = linkPersonPostRepository.getLinkPersonPostByPostAndPersonAndLinkType( - post, - person, - type - ); + Optional linkPersonPost = linkPersonPostRepository.getLinkPersonPostByPostAndPersonAndLinkType( + post, person, type); if (linkPersonPost.isEmpty()) { return; } person.getLinkPersonPost() - .removeIf(l -> Objects.equals(l.getId(), linkPersonPost.get().getId())); - post.getLinkPersonPost().removeIf(l -> Objects.equals(l.getId(), linkPersonPost.get().getId())); + .removeIf(l -> Objects.equals(l.getId(), linkPersonPost.get() + .getId())); + post.getLinkPersonPost() + .removeIf(l -> Objects.equals(l.getId(), linkPersonPost.get() + .getId())); linkPersonPostRepository.delete(linkPersonPost.get()); linkPersonPostDeletedPublisher.publish(linkPersonPost.get()); } From 290cb35592e388654b545a29faae8920a49f9983 Mon Sep 17 00:00:00 2001 From: rooki Date: Tue, 28 May 2024 10:41:04 +0200 Subject: [PATCH 043/115] Refactor application and extend functionality This commit updates and enhances the application in multiple ways. It includes refactoring classes that have been moved to more appropriate packages and renaming some of them for better understanding. Furthermore, new functionalities have been introduced such as implementing a service to ban a user and a service to retrieve a list of banned users. The code also contains improved error handling and minor modifications for improving code quality. Signed-off-by: rooki --- ...nksCommunityAggregatesResponseMapper.java} | 7 ++-- .../models/CommunityAggregatesResponse.java | 16 ++++---- .../SublinksPersonAggregationController.java | 39 +++++++++++++++++++ .../controllers/SublinksPersonController.java | 8 ---- .../SublinksPersonModerationController.java | 26 ++++++++++--- .../{models => enums}/RegistrationState.java | 2 +- .../SublinksPersonAggregationMapper.java | 32 +++++++++++++++ .../v1/person/models/LoginPerson.java | 22 ++++++++--- .../v1/person/models/LoginResponse.java | 1 + .../models/PersonAggregateResponse.java | 13 +++++++ .../person/models/moderation/BanPerson.java | 24 +++++++++++- .../services/SublinksPersonService.java | 39 ++++++++++++++++++- .../PersonAggregateRepository.java | 4 +- 13 files changed, 197 insertions(+), 36 deletions(-) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/{SublinksCommunityAggregatesResponse.java => SublinksCommunityAggregatesResponseMapper.java} (81%) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/{models => enums}/RegistrationState.java (65%) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonAggregationMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonAggregateResponse.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityAggregatesResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityAggregatesResponseMapper.java similarity index 81% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityAggregatesResponse.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityAggregatesResponseMapper.java index 84ee012e..76f0d106 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityAggregatesResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityAggregatesResponseMapper.java @@ -9,18 +9,19 @@ import org.springframework.lang.Nullable; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING) -public abstract class SublinksCommunityAggregatesResponse implements +public abstract class SublinksCommunityAggregatesResponseMapper implements Converter { @Override - @Mapping(target = "key", source = "communityAggregate.id") + @Mapping(target = "communityKey", source = "communityAggregate.community.titleSlug") @Mapping(target = "subscriberCount", source = "communityAggregate.subscriberCount") @Mapping(target = "postCount", source = "communityAggregate.postCount") @Mapping(target = "commentCount", source = "communityAggregate.commentCount") @Mapping(target = "activeDailyUserCount", source = "communityAggregate.activeDailyUserCount") @Mapping(target = "activeWeeklyUserCount", source = "communityAggregate.activeWeeklyUserCount") @Mapping(target = "activeMonthlyUserCount", source = "communityAggregate.activeMonthlyUserCount") - @Mapping(target = "activeHalfYearUserCount", source = "communityAggregate.activeHalfYearUserCount") + @Mapping(target = "activeHalfYearUserCount", + source = "communityAggregate.activeHalfYearUserCount") public abstract CommunityAggregatesResponse convert( @Nullable CommunityAggregate communityAggregate); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java index 8da6a326..d594845a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java @@ -1,12 +1,12 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models; -public record CommunityAggregatesResponse(String key, - String subscriberCount, - String postCount, - String commentCount, - String activeDailyUserCount, - String activeWeeklyUserCount, - String activeMonthlyUserCount, - String activeHalfYearUserCount) { +public record CommunityAggregatesResponse(String communityKey, + Integer subscriberCount, + Integer postCount, + Integer commentCount, + Integer activeDailyUserCount, + Integer activeWeeklyUserCount, + Integer activeMonthlyUserCount, + Integer activeHalfYearUserCount) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java new file mode 100644 index 00000000..473d255b --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java @@ -0,0 +1,39 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.moderation.BanPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; +import com.sublinks.sublinksapi.person.entities.Person; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.ConversionService; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("api/v1/person/{key}/aggregation") +@Tag(name = "Person Aggegation", description = "Person Aggregation API") +@AllArgsConstructor +public class SublinksPersonAggregationController extends AbstractSublinksApiController { + + private final SublinksPersonService sublinksPersonService; + private final ConversionService conversionService; + + @Operation(summary = "Get a person's aggregation") + @GetMapping + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PersonResponse show(@RequestBody @Valid BanPerson banPersonForm, + final SublinksJwtPerson jwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(jwtPerson); + + return sublinksPersonService.banPerson(banPersonForm, person); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java index 4a14f0bd..d607b8a7 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java @@ -101,12 +101,4 @@ public PersonResponse update(@PathVariable String key, return sublinksPersonService.updatePerson(person, updatePersonForm); } - - @Operation(summary = "Delete/Purge an person ( as an admin )") - @DeleteMapping("/{key}") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void delete(@PathVariable String key) { - // TODO: implement - } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java index 7ec29d08..7ccb77f1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java @@ -1,10 +1,11 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.controllers; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.moderation.BanPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; -import com.sublinks.sublinksapi.person.repositories.PersonRepository; +import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -12,7 +13,9 @@ import jakarta.validation.Valid; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -23,17 +26,28 @@ @AllArgsConstructor public class SublinksPersonModerationController extends AbstractSublinksApiController { - private final PersonRepository personRepository; private final SublinksPersonService sublinksPersonService; private final ConversionService conversionService; @Operation(summary = "Ban a person") @GetMapping("/ban") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public PersonResponse ban(@RequestBody @Valid BanPerson banPersonForm) { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PersonResponse ban(@RequestBody @Valid BanPerson banPersonForm, + final SublinksJwtPerson jwtPerson) + { - return null; + final Person person = getPersonOrThrowUnauthorized(jwtPerson); + + return sublinksPersonService.banPerson(banPersonForm, person); + } + + + @Operation(summary = "Delete/Purge an person ( as an admin )") + @DeleteMapping("/purge") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public void delete(@PathVariable String key) { + // TODO: implement } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/RegistrationState.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/enums/RegistrationState.java similarity index 65% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/RegistrationState.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/enums/RegistrationState.java index 5ea72a57..a41186f2 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/RegistrationState.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/enums/RegistrationState.java @@ -1,4 +1,4 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.person.models; +package com.sublinks.sublinksapi.api.sublinks.v1.person.enums; public enum RegistrationState { UNCHANGED, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonAggregationMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonAggregationMapper.java new file mode 100644 index 00000000..3098bddc --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonAggregationMapper.java @@ -0,0 +1,32 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.mappers; + +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonAggregateResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksRoleMapper; +import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.person.entities.PersonAggregate; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.Named; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksRoleMapper.class}) +public abstract class SublinksPersonAggregationMapper implements + Converter { + + @Override + @Mapping(target = "personKey", source = "person", qualifiedByName = "personKey") + @Mapping(target = "postCount", source = "postCount") + @Mapping(target = "commentCount", source = "commentCount") + @Mapping(target = "postScore", source = "postScore") + @Mapping(target = "commentScore", source = "commentScore") + public abstract PersonAggregateResponse convert(@Nullable PersonAggregate personAggregate); + + @Named("personKey") + String mapPersonKey(Person person) { + + return person.getName() + "@" + person.getInstance() + .getDomain(); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginPerson.java index e970526e..1a6829c4 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginPerson.java @@ -1,12 +1,24 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.models; -import java.util.Optional; import lombok.Builder; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; @Builder -public record LoginPerson(String username, - String password, - String captcha_token, - String captcha_answer) { +public record LoginPerson( + String username, + String password, + String captcha_token, + String captcha_answer) { + + public LoginPerson { + + if (username == null) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "username_required"); + } + if (password == null) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "password_required"); + } + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginResponse.java index 17074aa1..9f51238a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginResponse.java @@ -1,5 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.models; +import com.sublinks.sublinksapi.api.sublinks.v1.person.enums.RegistrationState; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import lombok.Builder; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonAggregateResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonAggregateResponse.java new file mode 100644 index 00000000..48fcc8db --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonAggregateResponse.java @@ -0,0 +1,13 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.models; + +import lombok.Builder; + +@Builder +public record PersonAggregateResponse( + String personKey, + Integer postCount, + Integer commentCount, + Integer postScore, + Integer commentScore) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/moderation/BanPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/moderation/BanPerson.java index 9388d7ce..da8e62f8 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/moderation/BanPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/moderation/BanPerson.java @@ -3,21 +3,41 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import lombok.Builder; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; @Builder public record BanPerson( String key, - String reason, - Boolean ban, + @Schema(description = "The reason for the ban", + requiredMode = RequiredMode.NOT_REQUIRED) String reason, + @Schema(description = "Ban the user", + requiredMode = RequiredMode.NOT_REQUIRED, + defaultValue = "true") Boolean ban, @Schema(description = "Remove all data associated with the user", requiredMode = RequiredMode.NOT_REQUIRED, defaultValue = "false") Boolean removeData, @Schema(description = "Unix timestamp when the ban should expire", requiredMode = RequiredMode.NOT_REQUIRED) Long expirationTimestamp) { + public BanPerson { + + if (expirationTimestamp != null && expirationTimestamp < System.currentTimeMillis() / 1000) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "expiration_timestamp_must_be_in_the_future"); + } + } + + @Override + public Boolean ban() { + + return ban == null || ban; + } + @Override public Boolean removeData() { return removeData != null && removeData; } + } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index 1642adc1..7bcb777d 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -3,14 +3,15 @@ import com.sublinks.sublinksapi.api.lemmy.v3.enums.RegistrationMode; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtUtil; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortOrder; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.moderation.IndexBannedPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.person.enums.RegistrationState; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.CreatePerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonIdentity; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.person.models.RegistrationState; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.UpdatePerson; -import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.moderation.IndexBannedPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.moderation.BanPerson; import com.sublinks.sublinksapi.authorization.entities.Role; import com.sublinks.sublinksapi.authorization.services.RoleService; import com.sublinks.sublinksapi.email.entities.Email; @@ -299,6 +300,13 @@ public PersonResponse updatePerson(Person person, UpdatePerson updatePersonForm) return conversionService.convert(person, PersonResponse.class); } + /** + * Retrieves a list of banned persons based on the search criteria. + * + * @param indexBannedPersonForm The form containing the search criteria. + * @return A list of Person objects representing the banned persons. + * @throws ResponseStatusException if the banned role is not found. + */ public List indexBannedPersons(final IndexBannedPerson indexBannedPersonForm) { Optional bannedRole = roleService.getBannedRole(); @@ -314,4 +322,31 @@ public List indexBannedPersons(final IndexBannedPerson indexBannedPerson .ascending() : Sort.by("name") .descending())); } + + /** + * Bans a person based on the provided ban form and person details. + * + * @param banPersonForm The form containing the ban details. + * @param person The person to be banned. + * @return The PersonResponse object representing the updated person information. + */ + public PersonResponse banPerson(final BanPerson banPersonForm, final Person person) { + + PersonIdentity ids = getPersonIdentifiersFromKey(banPersonForm.key()); + + Person bannedPerson = personRepository.findOneByNameAndInstance_Domain(ids.name(), ids.domain()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + + bannedPerson.setRole(banPersonForm.ban() ? roleService.getBannedRole( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "banned_role_not_found")) + : roleService.getDefaultRegisteredRole( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, + "default_registered_role_not_found"))); + personService.updatePerson(person); + // @todo: modlog + + return conversionService.convert(person, PersonResponse.class); + } + + public } diff --git a/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonAggregateRepository.java b/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonAggregateRepository.java index df680446..c9b90a77 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonAggregateRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonAggregateRepository.java @@ -1,9 +1,11 @@ package com.sublinks.sublinksapi.person.repositories; +import com.sublinks.sublinksapi.person.entities.Person; import com.sublinks.sublinksapi.person.entities.PersonAggregate; import org.springframework.data.jpa.repository.JpaRepository; public interface PersonAggregateRepository extends JpaRepository { - PersonAggregate findFirstByPersonId(Long personId); + + PersonAggregate findByPerson(Person person); } From 529971e9d530e04ea5bb91aef9c7663b284b4a6d Mon Sep 17 00:00:00 2001 From: rooki Date: Tue, 28 May 2024 13:45:59 +0200 Subject: [PATCH 044/115] Refactor PersonAggregationController and add Auth checks Changes have been made to refactor the `SublinksPersonAggregationController`. The controller now makes use of the `PersonAggregateResponse` and `PersonIdentity` models. In addition, authentication checks have been added to ensure a user has the correct permissions before accessing certain data. Signed-off-by: rooki --- .../SublinksPersonAggregationController.java | 42 +++++++++++++++---- .../services/SublinksPersonService.java | 2 - .../enums/RolePermissionPersonTypes.java | 1 + .../services/InitialRoleSetupService.java | 2 + 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java index 473d255b..5b1af996 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java @@ -2,18 +2,28 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; -import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.person.models.moderation.BanPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonAggregateResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonIdentity; import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionPersonTypes; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.person.entities.PersonAggregate; +import com.sublinks.sublinksapi.person.repositories.PersonAggregateRepository; +import com.sublinks.sublinksapi.person.repositories.PersonRepository; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; +import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; -import org.springframework.web.bind.annotation.*; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; @RestController @RequestMapping("api/v1/person/{key}/aggregation") @@ -23,17 +33,33 @@ public class SublinksPersonAggregationController extends AbstractSublinksApiCont private final SublinksPersonService sublinksPersonService; private final ConversionService conversionService; + private final RolePermissionService rolePermissionService; + private final PersonAggregateRepository personAggregateRepository; + private final PersonRepository personRepository; @Operation(summary = "Get a person's aggregation") @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public PersonResponse show(@RequestBody @Valid BanPerson banPersonForm, - final SublinksJwtPerson jwtPerson) + public PersonAggregateResponse show(@RequestParam final String key, + final SublinksJwtPerson sublinksJwtPerson) { - final Person person = getPersonOrThrowUnauthorized(jwtPerson); + final Optional person = getOptionalPerson(sublinksJwtPerson); - return sublinksPersonService.banPerson(banPersonForm, person); + rolePermissionService.isPermitted(person.orElse(null), + RolePermissionPersonTypes.READ_PERSON_AGGREGATION, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_read_community_aggregation")); + + final PersonIdentity personIdentity = sublinksPersonService.getPersonIdentifiersFromKey(key); + + final Person foundPerson = personRepository.findOneByNameAndInstance_Domain( + personIdentity.name(), personIdentity.domain()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + + final PersonAggregate personAggregate = personAggregateRepository.findByPerson(foundPerson); + + return conversionService.convert(personAggregate, PersonAggregateResponse.class); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index 7bcb777d..d94f7cca 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -347,6 +347,4 @@ public PersonResponse banPerson(final BanPerson banPersonForm, final Person pers return conversionService.convert(person, PersonResponse.class); } - - public } diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java index 034e0410..a2e4c54b 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java @@ -10,6 +10,7 @@ public enum RolePermissionPersonTypes implements RolePermissionInterface { DELETE_USER("user", AuthorizeAction.DELETE), PURGE_USER("user", AuthorizeAction.PURGE), READ_MENTION_USER("user-mention", AuthorizeAction.READ), + READ_PERSON_AGGREGATION("user-aggregation", AuthorizeAction.READ), MARK_MENTION_AS_READ("user-mention-read", AuthorizeAction.UPDATE), READ_REPLIES("user-reply-read", AuthorizeAction.READ), MARK_REPLIES_AS_READ("user-reply-read", AuthorizeAction.UPDATE), diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java index ba92e00e..9c708a5a 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java @@ -76,6 +76,8 @@ private void applyCommonPermissions(Set rolePermissions rolePermissions.add(RolePermissionPersonTypes.READ_USER); rolePermissions.add(RolePermissionPersonTypes.READ_USERS); rolePermissions.add(RolePermissionModLogTypes.READ_MODLOG); + rolePermissions.add(RolePermissionPersonTypes.READ_PERSON_AGGREGATION); + rolePermissions.add(RolePermissionCommunityTypes.READ_COMMUNITY_AGGREGATION); } /** From c6f3615124c1353a6164fa1d9c52f373c7a50a59 Mon Sep 17 00:00:00 2001 From: rooki Date: Tue, 28 May 2024 20:18:43 +0200 Subject: [PATCH 045/115] Enhanced authorization checks and response models The commit introduced more detailed authorization checks in various parts of the application. It further enriched the response models for better client-side readability. Moreover, annotation responses have been updated in multiple controllers to improve the OpenAPI documentation. Signed-off-by: rooki --- .../CommunityModActionsController.java | 98 ++++++++++++------- .../v3/post/controllers/PostController.java | 88 +++++++---------- .../v3/user/controllers/UserController.java | 85 +++++++++++----- .../SublinksAnnouncementController.java | 1 + .../SublinksInstanceController.java | 1 + .../services/SublinksPersonService.java | 48 +++++++-- .../controllers/SublinksPostController.java | 1 + .../SublinksPrivatemessageController.java | 1 + .../controllers/SublinksSearchController.java | 1 + .../enums/RolePermissionPersonTypes.java | 5 +- .../services/InitialRoleSetupService.java | 10 +- 11 files changed, 210 insertions(+), 129 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java index bc6ed4e7..702f4985 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java @@ -18,7 +18,6 @@ import com.sublinks.sublinksapi.api.lemmy.v3.modlog.services.ModerationLogService; import com.sublinks.sublinksapi.api.lemmy.v3.user.services.LemmyPersonService; import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommunityTypes; -import com.sublinks.sublinksapi.authorization.enums.RolePermissionPersonTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.comment.services.CommentReportService; import com.sublinks.sublinksapi.comment.services.CommentService; @@ -40,12 +39,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; - import java.util.Collection; import java.util.Date; import java.util.List; import java.util.Set; - import lombok.RequiredArgsConstructor; import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpStatus; @@ -81,7 +78,8 @@ public class CommunityModActionsController extends AbstractLemmyApiController { @Operation(summary = "Hide a community from public / \"All\" view. Admins only.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = CommunityResponse.class))})}) + @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = CommunityResponse.class))})}) @PutMapping("hide") CommunityResponse hide(@Valid @RequestBody final HideCommunity hideCommunityForm, JwtPerson principal) @@ -110,13 +108,16 @@ CommunityResponse hide(@Valid @RequestBody final HideCommunity hideCommunityForm .build(); moderationLogService.createModerationLog(moderationLog); - return CommunityResponse.builder().community_view( - lemmyCommunityService.communityViewFromCommunity(community)).build(); + return CommunityResponse.builder() + .community_view( + lemmyCommunityService.communityViewFromCommunity(community)) + .build(); } @Operation(summary = "Delete a community.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = CommunityResponse.class))})}) + @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = CommunityResponse.class))})}) @PostMapping("delete") CommunityResponse delete(@Valid final DeleteCommunity deleteCommunityForm, JwtPerson principal) { @@ -128,10 +129,10 @@ CommunityResponse delete(@Valid final DeleteCommunity deleteCommunityForm, JwtPe RolePermissionService.isAdminElseThrow(person, () -> new ResponseStatusException(HttpStatus.FORBIDDEN)); - final Community community = communityRepository.findById( - (long) deleteCommunityForm.community_id()).orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + (long) deleteCommunityForm.community_id()) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); community.setDeleted(deleteCommunityForm.deleted()); communityRepository.save(community); @@ -147,13 +148,16 @@ CommunityResponse delete(@Valid final DeleteCommunity deleteCommunityForm, JwtPe .build(); moderationLogService.createModerationLog(moderationLog); - return CommunityResponse.builder().community_view( - lemmyCommunityService.communityViewFromCommunity(community)).build(); + return CommunityResponse.builder() + .community_view( + lemmyCommunityService.communityViewFromCommunity(community)) + .build(); } @Operation(summary = "A moderator remove for a community.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = CommunityResponse.class))})}) + @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = CommunityResponse.class))})}) @PostMapping("remove") CommunityResponse remove(@Valid @RequestBody final RemoveCommunity removeCommunityForm, JwtPerson principal) @@ -166,8 +170,9 @@ CommunityResponse remove(@Valid @RequestBody final RemoveCommunity removeCommuni () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); final Community community = communityRepository.findById( - (long) removeCommunityForm.community_id()).orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + (long) removeCommunityForm.community_id()) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); if (!linkPersonCommunityService.hasAnyLink(person, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { @@ -189,13 +194,16 @@ CommunityResponse remove(@Valid @RequestBody final RemoveCommunity removeCommuni .build(); moderationLogService.createModerationLog(moderationLog); - return CommunityResponse.builder().community_view( - lemmyCommunityService.communityViewFromCommunity(community)).build(); + return CommunityResponse.builder() + .community_view( + lemmyCommunityService.communityViewFromCommunity(community)) + .build(); } @Operation(summary = "Transfer your community to an existing moderator.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = GetCommunityResponse.class))})}) + @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = GetCommunityResponse.class))})}) @PostMapping("transfer") GetCommunityResponse transfer(@Valid @RequestBody final TransferCommunity transferCommunityForm, JwtPerson principal) @@ -208,8 +216,9 @@ GetCommunityResponse transfer(@Valid @RequestBody final TransferCommunity transf () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); final Community community = communityRepository.findById( - (long) transferCommunityForm.community_id()).orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + (long) transferCommunityForm.community_id()) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); if (!linkPersonCommunityService.hasLinkOrAdmin(person, community, LinkPersonCommunityType.owner)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_allowed"); @@ -224,8 +233,11 @@ GetCommunityResponse transfer(@Valid @RequestBody final TransferCommunity transf } final Person oldOwner = linkPersonCommunityService.getPersonsFromCommunityAndListTypes( - community, List.of(LinkPersonCommunityType.owner)).stream().findFirst().orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "owner_not_found")); + community, List.of(LinkPersonCommunityType.owner)) + .stream() + .findFirst() + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "owner_not_found")); linkPersonCommunityService.addLink(oldOwner, community, LinkPersonCommunityType.moderator); linkPersonCommunityService.removeLink(oldOwner, community, LinkPersonCommunityType.owner); @@ -243,13 +255,16 @@ GetCommunityResponse transfer(@Valid @RequestBody final TransferCommunity transf .build(); moderationLogService.createModerationLog(moderationLog); - return GetCommunityResponse.builder().community_view( - lemmyCommunityService.communityViewFromCommunity(community)).build(); + return GetCommunityResponse.builder() + .community_view( + lemmyCommunityService.communityViewFromCommunity(community)) + .build(); } @Operation(summary = "Ban a user from a community.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = BanFromCommunityResponse.class))})}) + @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = BanFromCommunityResponse.class))})}) @PostMapping("ban_user") BanFromCommunityResponse banUser(@Valid @RequestBody final BanFromCommunity banPersonForm, JwtPerson principal) @@ -257,7 +272,7 @@ BanFromCommunityResponse banUser(@Valid @RequestBody final BanFromCommunity banP final Person person = getPersonOrThrowUnauthorized(principal); - rolePermissionService.isPermitted(person, RolePermissionPersonTypes.MODERATOR_BAN_USER, + rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.MODERATOR_BAN_USER, () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "not_allowed")); final Community community = communityRepository.findById((long) banPersonForm.community_id()) @@ -312,13 +327,17 @@ BanFromCommunityResponse banUser(@Valid @RequestBody final BanFromCommunity banP .build(); moderationLogService.createModerationLog(moderationLog); - return BanFromCommunityResponse.builder().banned(banPersonForm.ban()).person_view( - lemmyPersonService.getPersonView(personToBan)).build(); + return BanFromCommunityResponse.builder() + .banned(banPersonForm.ban()) + .person_view( + lemmyPersonService.getPersonView(personToBan)) + .build(); } @Operation(summary = "Add a moderator to your community.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = AddModToCommunityResponse.class))})}) + @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = AddModToCommunityResponse.class))})}) @PostMapping("mod") AddModToCommunityResponse addMod(@Valid @RequestBody AddModToCommunity addModToCommunityForm, JwtPerson principal) @@ -332,8 +351,9 @@ AddModToCommunityResponse addMod(@Valid @RequestBody AddModToCommunity addModToC () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); final Community community = communityRepository.findById( - (long) addModToCommunityForm.community_id()).orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + (long) addModToCommunityForm.community_id()) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); final boolean isAllowed = linkPersonCommunityService.hasLink(person, community, LinkPersonCommunityType.moderator) || linkPersonCommunityService.hasLink(person, community, @@ -363,13 +383,15 @@ AddModToCommunityResponse addMod(@Valid @RequestBody AddModToCommunity addModToC Collection moderators = linkPersonCommunityService.getPersonsFromCommunityAndListTypes( community, List.of(LinkPersonCommunityType.moderator)); - List moderatorsView = moderators.stream().map( - moderator -> CommunityModeratorView.builder() - .moderator(conversionService.convert(moderator, - com.sublinks.sublinksapi.api.lemmy.v3.user.models.Person.class)) - .community(conversionService.convert(community, - com.sublinks.sublinksapi.api.lemmy.v3.community.models.Community.class)) - .build()).toList(); + List moderatorsView = moderators.stream() + .map( + moderator -> CommunityModeratorView.builder() + .moderator(conversionService.convert(moderator, + com.sublinks.sublinksapi.api.lemmy.v3.user.models.Person.class)) + .community(conversionService.convert(community, + com.sublinks.sublinksapi.api.lemmy.v3.community.models.Community.class)) + .build()) + .toList(); // Create Moderation Log ModerationLog moderationLog = ModerationLog.builder() diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostController.java index cc6a82ff..15e6a738 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/post/controllers/PostController.java @@ -104,11 +104,13 @@ public class PostController extends AbstractLemmyApiController { private final SortFactory sortFactory; @Operation(summary = "Get / fetch a post.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = GetPostResponse.class))}), - @ApiResponse(responseCode = "400", description = "Post Not Found", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + @ApiResponse(responseCode = "400", + description = "Post Not Found", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ApiError.class))})}) @GetMapping GetPostResponse show(@Valid final GetPost getPostForm, final JwtPerson principal) { @@ -157,34 +159,11 @@ GetPostResponse show(@Valid final GetPost getPostForm, final JwtPerson principal .cross_posts(crossPosts) .build(); } - @Operation(summary = "Mark a post as read.") - @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))})}) - @PostMapping("mark_as_read") - PostResponse markAsRead(@Valid @RequestBody final MarkPostAsRead markPostAsReadForm, - final JwtPerson principal) { - - final Person person = getPersonOrThrowBadRequest(principal); - - rolePermissionService.isPermitted(person, RolePermissionPostTypes.MARK_POST_AS_READ, - () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); - - // @todo support multiple posts - final Post post = postRepository.findById((long) markPostAsReadForm.post_id()) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); - postReadService.markPostReadByPerson(post, person); - return PostResponse.builder() - .post_view(lemmyPostService.postViewFromPost(post, person)) - .build(); - } @Operation(summary = "Get / fetch posts, with various filters.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = GetPostsResponse.class))})}) @GetMapping("list") @Transactional(readOnly = true) @@ -313,7 +292,8 @@ public GetPostsResponse index(@Valid final GetPosts getPostsForm, final JwtPerso schema = @Schema(implementation = ApiError.class))})}) @PostMapping("mark_as_read") PostResponse markAsRead(@Valid @RequestBody final MarkPostAsRead markPostAsReadForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person person = getPersonOrThrowBadRequest(principal); @@ -330,11 +310,6 @@ PostResponse markAsRead(@Valid @RequestBody final MarkPostAsRead markPostAsReadF } @Operation(summary = "Like / vote on 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, @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, @@ -344,7 +319,9 @@ PostResponse markAsRead(@Valid @RequestBody final MarkPostAsRead markPostAsReadF content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ApiError.class))})}) @PostMapping("like") - PostResponse like(@Valid @RequestBody CreatePostLike createPostLikeForm, JwtPerson principal) { + public PostResponse like(@Valid @RequestBody CreatePostLike createPostLikeForm, + JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -370,11 +347,6 @@ PostResponse like(@Valid @RequestBody CreatePostLike createPostLikeForm, JwtPers } @Operation(summary = "Get Votes on 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, @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, @@ -384,7 +356,9 @@ PostResponse like(@Valid @RequestBody CreatePostLike createPostLikeForm, JwtPers content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ApiError.class))})}) @GetMapping("like/list") - ListPostLikesResponse listLikes(@Valid ListPostLikes listPostLikesForm, JwtPerson principal) { + public ListPostLikesResponse listLikes(@Valid ListPostLikes listPostLikesForm, + JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -409,11 +383,13 @@ ListPostLikesResponse listLikes(@Valid ListPostLikes listPostLikesForm, JwtPerso } @Operation(summary = "Save a post.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + @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, + @ApiResponse(responseCode = "400", + description = "Post Not Found", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ApiError.class))})}) @PutMapping("save") public PostResponse saveForLater(@Valid @RequestBody SavePost savePostForm, JwtPerson principal) { @@ -436,15 +412,18 @@ public PostResponse saveForLater(@Valid @RequestBody SavePost savePostForm, JwtP } @Operation(summary = "Report a post.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PostReportResponse.class))}), - @ApiResponse(responseCode = "400", description = "Post Not Found", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + @ApiResponse(responseCode = "400", + description = "Post Not Found", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ResponseStatusException.class))})}) @PostMapping("report") PostReportResponse report(@Valid @RequestBody final CreatePostReport createPostReportForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -472,8 +451,9 @@ PostReportResponse report(@Valid @RequestBody final CreatePostReport createPostR } @Operation(summary = "Fetch metadata for any given site.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = GetSiteMetadataResponse.class))})}) @GetMapping("site_metadata") public GetSiteMetadataResponse siteMetadata(@Valid GetSiteMetadata getSiteMetadataForm) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/user/controllers/UserController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/user/controllers/UserController.java index 2caa1073..513d9917 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/user/controllers/UserController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/user/controllers/UserController.java @@ -101,8 +101,10 @@ public class UserController extends AbstractLemmyApiController { private final RoleService roleService; @Operation(summary = "Get the details for a person.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = GetPersonDetailsResponse.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = GetPersonDetailsResponse.class))})}) @GetMapping() GetPersonDetailsResponse show(@Valid final GetPersonDetails getPersonDetailsForm) { @@ -133,11 +135,14 @@ GetPersonDetailsResponse show(@Valid final GetPersonDetails getPersonDetailsForm } @Operation(summary = "Get mentions for your user.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = GetPersonMentionsResponse.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = GetPersonMentionsResponse.class))})}) @GetMapping("mention") GetPersonMentionsResponse mention(@Valid final GetPersonMentions getPersonMentionsForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -171,11 +176,14 @@ GetPersonMentionsResponse mention(@Valid final GetPersonMentions getPersonMentio } @Operation(summary = "Mark a person mention as read.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PersonMentionResponse.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = PersonMentionResponse.class))})}) @PostMapping("mention/mark_as_read") PersonMentionResponse mentionMarkAsRead( - @Valid final MarkPersonMentionAsRead markPersonMentionAsReadForm, final JwtPerson principal) { + @Valid final MarkPersonMentionAsRead markPersonMentionAsReadForm, final JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -200,8 +208,10 @@ PersonMentionResponse mentionMarkAsRead( } @Operation(summary = "Get comment replies.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = GetRepliesResponse.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = GetRepliesResponse.class))})}) @GetMapping("replies") GetRepliesResponse replies(@Valid final GetReplies getReplies, JwtPerson principal) { @@ -233,8 +243,10 @@ GetRepliesResponse replies(@Valid final GetReplies getReplies, JwtPerson princip } @Operation(summary = "Get a list of banned users.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = BannedPersonsResponse.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = BannedPersonsResponse.class))})}) @GetMapping("banned") BannedPersonsResponse bannedList(final JwtPerson principal) { @@ -254,8 +266,10 @@ BannedPersonsResponse bannedList(final JwtPerson principal) { } @Operation(summary = "Mark all replies as read.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = GetRepliesResponse.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = GetRepliesResponse.class))})}) @PostMapping("mark_all_as_read") GetRepliesResponse markAllAsRead(final JwtPerson principal) { // @todo: Check if he has permission to read/update replies ( if not ignore @@ -279,17 +293,21 @@ GetRepliesResponse markAllAsRead(final JwtPerson principal) { } @Operation(summary = "Save your user settings.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = LoginResponse.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = LoginResponse.class))})}) @Transactional @PutMapping("save_user_settings") public LoginResponse saveUserSettings(@Valid @RequestBody SaveUserSettings saveUserSettingsForm, - JwtPerson principal) { + JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); rolePermissionService.isPermitted(person, RolePermissionPersonTypes.UPDATE_USER_SETTINGS, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "no_permission")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "no_permission_to_update_user_settings")); // @todo expand form validation to check for email formatting, etc. if (saveUserSettingsForm.show_nsfw() != null) { @@ -402,11 +420,14 @@ public LoginResponse saveUserSettings(@Valid @RequestBody SaveUserSettings saveU } @Operation(summary = "Get your unread counts.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = GetUnreadCountResponse.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = GetUnreadCountResponse.class))})}) @GetMapping("unread_count") GetUnreadCountResponse unreadCount(@Valid final GetUnreadCount getUnreadCountForm, - JwtPerson principal) { + JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -438,14 +459,21 @@ GetUnreadCountResponse unreadCount(@Valid final GetUnreadCount getUnreadCountFor } @Operation(summary = "Import your User Settings.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = SuccessResponse.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = SuccessResponse.class))})}) @PostMapping("import_settings") SuccessResponse import_settings(@Valid final ImportSettings importSettingsForm, - JwtPerson principal) { + JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); + rolePermissionService.isPermitted(person, RolePermissionPersonTypes.UPDATE_USER_SETTINGS, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "no_permission_to_update_user_settings")); + final SuccessResponse.SuccessResponseBuilder builder = SuccessResponse.builder(); UserExportSettings settings = importSettingsForm.settings(); @@ -517,13 +545,18 @@ SuccessResponse import_settings(@Valid final ImportSettings importSettingsForm, } @Operation(summary = "Get your user settings.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = SaveUserSettings.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = SaveUserSettings.class))})}) @GetMapping("export_settings") ExportSettingsResponse export_settings(final JwtPerson principal) { final Person person = getPersonOrThrowUnauthorized(principal); + rolePermissionService.isPermitted(person, RolePermissionPersonTypes.USER_EXPORT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "no_permission")); + ExportSettingsResponse.ExportSettingsResponseBuilder builder = ExportSettingsResponse.builder(); UserExportSettings.UserExportSettingsBuilder settings_builder = UserExportSettings.builder(); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java index bbb50880..f6f0c7eb 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java @@ -2,6 +2,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.DeleteMapping; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java index c437d0db..466d034b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java @@ -2,6 +2,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.DeleteMapping; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index d94f7cca..7ad6f4a7 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -8,11 +8,15 @@ import com.sublinks.sublinksapi.api.sublinks.v1.person.models.CreatePerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonAggregateResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonIdentity; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.UpdatePerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.moderation.BanPerson; import com.sublinks.sublinksapi.authorization.entities.Role; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionInstanceTypes; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionPersonTypes; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.authorization.services.RoleService; import com.sublinks.sublinksapi.email.entities.Email; import com.sublinks.sublinksapi.email.enums.EmailTemplatesEnum; @@ -21,9 +25,11 @@ import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; import com.sublinks.sublinksapi.language.repositories.LanguageRepository; import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.person.entities.PersonAggregate; import com.sublinks.sublinksapi.person.entities.PersonEmailVerification; import com.sublinks.sublinksapi.person.entities.PersonRegistrationApplication; import com.sublinks.sublinksapi.person.enums.PersonRegistrationApplicationStatus; +import com.sublinks.sublinksapi.person.repositories.PersonAggregateRepository; import com.sublinks.sublinksapi.person.repositories.PersonRegistrationApplicationRepository; import com.sublinks.sublinksapi.person.repositories.PersonRepository; import com.sublinks.sublinksapi.person.services.PersonEmailVerificationService; @@ -59,6 +65,8 @@ public class SublinksPersonService { private final ConversionService conversionService; private final LanguageRepository languageRepository; private final RoleService roleService; + private final RolePermissionService rolePermissionService; + private final PersonAggregateRepository personAggregateRepository; /** * Retrieves the name and domain identifiers of a person from the given key. @@ -202,15 +210,13 @@ public LoginResponse login(final LoginPerson loginPersonForm, final String ip, final String userAgent) { - final Optional foundPerson = personRepository.findOneByNameIgnoreCase( - loginPersonForm.username()); + final Person person = personRepository.findOneByNameIgnoreCase(loginPersonForm.username()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); - if (foundPerson.isEmpty()) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found"); + if (!rolePermissionService.isPermitted(person, RolePermissionPersonTypes.USER_LOGIN)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_login"); } - final Person person = foundPerson.get(); - if (person.isDeleted()) { return LoginResponse.builder() .status(RegistrationState.UNCHANGED) @@ -262,6 +268,9 @@ public LoginResponse login(final LoginPerson loginPersonForm, final String ip, */ public PersonResponse updatePerson(Person person, UpdatePerson updatePersonForm) { + rolePermissionService.isPermitted(person, RolePermissionPersonTypes.UPDATE_USER_SETTINGS, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_update_person")); + if (Optional.ofNullable(updatePersonForm.languagesKeys()) .isPresent()) { person.setLanguages(languageRepository.findAllByCodeIsIn(updatePersonForm.languagesKeys())); @@ -307,8 +316,12 @@ public PersonResponse updatePerson(Person person, UpdatePerson updatePersonForm) * @return A list of Person objects representing the banned persons. * @throws ResponseStatusException if the banned role is not found. */ - public List indexBannedPersons(final IndexBannedPerson indexBannedPersonForm) { + public List indexBannedPersons(final IndexBannedPerson indexBannedPersonForm, + final Person person) + { + rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_BAN_READ, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_ban_person")); Optional bannedRole = roleService.getBannedRole(); if (bannedRole.isEmpty()) { @@ -334,6 +347,9 @@ public PersonResponse banPerson(final BanPerson banPersonForm, final Person pers PersonIdentity ids = getPersonIdentifiersFromKey(banPersonForm.key()); + rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_BAN_USER, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_ban_person")); + Person bannedPerson = personRepository.findOneByNameAndInstance_Domain(ids.name(), ids.domain()) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); @@ -347,4 +363,22 @@ public PersonResponse banPerson(final BanPerson banPersonForm, final Person pers return conversionService.convert(person, PersonResponse.class); } + + public PersonAggregateResponse showAggregate(final String key, final Optional person) { + + rolePermissionService.isPermitted(person.orElse(null), + RolePermissionPersonTypes.READ_PERSON_AGGREGATION, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_read_community_aggregation")); + + final PersonIdentity personIdentity = getPersonIdentifiersFromKey(key); + + final Person foundPerson = personRepository.findOneByNameAndInstance_Domain( + personIdentity.name(), personIdentity.domain()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + + final PersonAggregate personAggregate = personAggregateRepository.findByPerson(foundPerson); + + return conversionService.convert(personAggregate, PersonAggregateResponse.class); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java index 8dae8099..540487d2 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java @@ -2,6 +2,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.DeleteMapping; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java index 1587058f..01ab2dca 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java @@ -2,6 +2,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.DeleteMapping; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java index 13d529c4..cfb317ff 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java @@ -2,6 +2,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java index a2e4c54b..462b676a 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java @@ -16,9 +16,8 @@ public enum RolePermissionPersonTypes implements RolePermissionInterface { MARK_REPLIES_AS_READ("user-reply-read", AuthorizeAction.UPDATE), RESET_PASSWORD("user-reset-password", AuthorizeAction.DELETE), USER_BLOCK("user", AuthorizeAction.BLOCK), - - // Moderator permissions - MODERATOR_BAN_USER("user-moderator", AuthorizeAction.BAN), + USER_LOGIN("user-login", AuthorizeAction.READ), + USER_EXPORT("user-export", AuthorizeAction.READ), /** * Unused diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java index 9c708a5a..09cc2558 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java @@ -78,6 +78,8 @@ private void applyCommonPermissions(Set rolePermissions rolePermissions.add(RolePermissionModLogTypes.READ_MODLOG); rolePermissions.add(RolePermissionPersonTypes.READ_PERSON_AGGREGATION); rolePermissions.add(RolePermissionCommunityTypes.READ_COMMUNITY_AGGREGATION); + rolePermissions.add(RolePermissionCommunityTypes.READ_COMMUNITY_MODERATORS); + rolePermissions.add(RolePermissionPersonTypes.USER_LOGIN); } /** @@ -166,11 +168,17 @@ private void createRegisteredRole() { rolePermissions.add(RolePermissionPersonTypes.UPDATE_USER_SETTINGS); rolePermissions.add(RolePermissionPersonTypes.RESET_PASSWORD); + rolePermissions.add(RolePermissionPersonTypes.USER_EXPORT); + rolePermissions.add(RolePermissionPersonTypes.MARK_MENTION_AS_READ); + rolePermissions.add(RolePermissionPersonTypes.MARK_REPLIES_AS_READ); + rolePermissions.add(RolePermissionPersonTypes.READ_MENTION_USER); + rolePermissions.add(RolePermissionPersonTypes.READ_REPLIES); rolePermissions.add(RolePermissionPostTypes.MODERATOR_REMOVE_POST); rolePermissions.add(RolePermissionCommentTypes.MODERATOR_REMOVE_COMMENT); rolePermissions.add(RolePermissionCommunityTypes.MODERATOR_REMOVE_COMMUNITY); - rolePermissions.add(RolePermissionPersonTypes.MODERATOR_BAN_USER); + rolePermissions.add(RolePermissionCommunityTypes.MODERATOR_BAN_USER); + rolePermissions.add(RolePermissionCommentTypes.MODERATOR_SPEAK); rolePermissions.add(RolePermissionCommentTypes.MODERATOR_SHOW_DELETED_COMMENT); rolePermissions.add(RolePermissionPostTypes.MODERATOR_SHOW_DELETED_POST); From b4fe7c4bdb916d50b75337ebe71269e4151c92ed Mon Sep 17 00:00:00 2001 From: rooki Date: Thu, 30 May 2024 22:40:22 +0200 Subject: [PATCH 046/115] Add search functionality for instances and enhance APIs Introduced search functionality in the Instance Repository to retrieve instances based on domain or description. Enhanced various controllers for better data handling and error management. Updated the InstanceConfig and Person Repositories for increased efficiency. Enhanced the application's mapping capabilities by introducing a new SublinksInstanceMapper. Added RegistrationMode and ListingType enums for better data management in instances. Signed-off-by: rooki --- .../SublinksCommunityController.java | 9 ++- .../SublinksInstanceAggregateController.java | 32 ++++++++ .../SublinksInstanceConfigController.java | 57 ++++++++++++++ .../SublinksInstanceController.java | 58 +++++--------- .../v1/instance/enums/ListingType.java | 8 ++ .../v1/instance/enums/RegistrationMode.java | 7 ++ .../SublinksInstanceAggregateMapper.java | 27 +++++++ .../mappers/SublinksInstanceConfigMapper.java | 43 +++++++++++ .../mappers/SublinksInstanceMapper.java | 33 ++++++++ .../v1/instance/models/IndexInstance.java | 11 +++ .../models/InstanceAggregateResponse.java | 14 ++++ .../models/InstanceConfigResponse.java | 28 +++++++ .../v1/instance/models/InstanceResponse.java | 17 ++++ .../instance/models/UpdateInstanceConfig.java | 27 +++++++ .../service/SublinksInstanceService.java | 77 +++++++++++++++++++ .../controllers/SublinksPersonController.java | 37 +++++---- .../v1/person/models/IndexPerson.java | 2 +- .../v1/person/models/PersonResponse.java | 37 +++++---- .../services/SublinksPersonService.java | 47 +++++++++++ .../instance/entities/InstanceConfig.java | 2 - .../InstanceAggregateRepository.java | 2 + .../InstanceConfigRepository.java | 2 + .../repositories/InstanceRepository.java | 9 +++ .../sublinksapi/utils/PaginationUtils.java | 8 +- ...20231003__Create_initial_entity_tables.sql | 4 + 25 files changed, 518 insertions(+), 80 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceAggregateController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceConfigController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/enums/ListingType.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/enums/RegistrationMode.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceAggregateMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceConfigMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/IndexInstance.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/InstanceAggregateResponse.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/InstanceConfigResponse.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/InstanceResponse.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/UpdateInstanceConfig.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/service/SublinksInstanceService.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index 425a234a..0dd83a3c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -47,7 +47,8 @@ public class SublinksCommunityController extends AbstractSublinksApiController { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public List index( @RequestParam(required = false) final Optional indexCommunityParam, - final SublinksJwtPerson sublinksJwtPerson) { + final SublinksJwtPerson sublinksJwtPerson) + { final Optional person = getOptionalPerson(sublinksJwtPerson); @@ -73,7 +74,8 @@ public CommunityResponse show(@PathVariable final String key) { @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse create(@RequestBody @Valid final CreateCommunity createCommunity, - final SublinksJwtPerson sublinksJwtPerson) { + final SublinksJwtPerson sublinksJwtPerson) + { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); @@ -85,7 +87,8 @@ public CommunityResponse create(@RequestBody @Valid final CreateCommunity create @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse update(@PathVariable final String key, - @RequestBody @Valid UpdateCommunity updateCommunityForm, final SublinksJwtPerson principal) { + @RequestBody @Valid UpdateCommunity updateCommunityForm, final SublinksJwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceAggregateController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceAggregateController.java new file mode 100644 index 00000000..974696e1 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceAggregateController.java @@ -0,0 +1,32 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.instance.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.InstanceAggregateResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.service.SublinksInstanceService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@AllArgsConstructor +@RestController +@RequestMapping("api/v1/instance/{key}/aggregate") +@Tag(name = "Instance Aggregate", description = "Instance Aggretate API") +public class SublinksInstanceAggregateController extends AbstractSublinksApiController { + + private final SublinksInstanceService sublinksInstanceService; + + @Operation(summary = "Get a specific instance Aggregate") + @GetMapping + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public InstanceAggregateResponse show(@PathVariable String key) { + + return sublinksInstanceService.showAggregate(key); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceConfigController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceConfigController.java new file mode 100644 index 00000000..26976c29 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceConfigController.java @@ -0,0 +1,57 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.instance.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.InstanceConfigResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.UpdateInstanceConfig; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.service.SublinksInstanceService; +import com.sublinks.sublinksapi.instance.repositories.InstanceRepository; +import com.sublinks.sublinksapi.instance.services.InstanceConfigService; +import com.sublinks.sublinksapi.instance.services.InstanceService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.ConversionService; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@AllArgsConstructor +@RestController +@RequestMapping("api/v1/instance/{key}/config") +@Tag(name = "Instance Config", description = "Instance Config API") +public class SublinksInstanceConfigController extends AbstractSublinksApiController { + + private final InstanceConfigService instanceConfigService; + private final InstanceService instanceService; + private final InstanceRepository instanceRepository; + private final SublinksInstanceService sublinksInstanceService; + private final ConversionService conversionService; + + @Operation(summary = "Get a specific instance config") + @GetMapping + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public InstanceConfigResponse show(@PathVariable String key) + { + + return conversionService.convert(instanceRepository.findInstanceByDomain(key), + InstanceConfigResponse.class); + } + + @Operation(summary = "Update an instance config") + @PostMapping + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public InstanceConfigResponse update(@PathVariable String key, + @RequestBody @Valid UpdateInstanceConfig updateInstanceConfigForm) + { + + return sublinksInstanceService.updateConfig(key, updateInstanceConfigForm); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java index 466d034b..5703441e 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java @@ -1,63 +1,47 @@ package com.sublinks.sublinksapi.api.sublinks.v1.instance.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.IndexInstance; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.InstanceResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.service.SublinksInstanceService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.web.bind.annotation.DeleteMapping; +import java.util.List; +import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +@AllArgsConstructor @RestController @RequestMapping("api/v1/instance") @Tag(name = "Instance", description = "Instance API") public class SublinksInstanceController extends AbstractSublinksApiController { - @Operation(summary = "Get a list of instances") + + private final SublinksInstanceService sublinksInstanceService; + + @Operation(summary = "Get a list of instances") @GetMapping @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void index() { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List index( + @RequestParam(required = false) final IndexInstance indexInstance) + { + + return sublinksInstanceService.index(indexInstance == null ? IndexInstance.builder() + .build() : indexInstance); } @Operation(summary = "Get a specific instance") @GetMapping("/{id}") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void show(@PathVariable String id) { - // TODO: implement - } + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public InstanceResponse show(@PathVariable String id) { - @Operation(summary = "Create a new instance") - @PostMapping - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void create() { - // TODO: implement - } - - @Operation(summary = "Update an instance") - @PostMapping("/{id}") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void update(@PathVariable String id) { - // TODO: implement - } - - @Operation(summary = "Delete an instance") - @DeleteMapping("/{id}") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void delete(@PathVariable String id) { - // TODO: implement + return sublinksInstanceService.show(id); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/enums/ListingType.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/enums/ListingType.java new file mode 100644 index 00000000..827073cb --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/enums/ListingType.java @@ -0,0 +1,8 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.instance.enums; + +public enum ListingType { + All, + Local, + Subscribed, + ModeratorView, +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/enums/RegistrationMode.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/enums/RegistrationMode.java new file mode 100644 index 00000000..20ab3f89 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/enums/RegistrationMode.java @@ -0,0 +1,7 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.instance.enums; + +public enum RegistrationMode { + Closed, + RequireApplication, + Open, +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceAggregateMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceAggregateMapper.java new file mode 100644 index 00000000..c7b35045 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceAggregateMapper.java @@ -0,0 +1,27 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.instance.mappers; + +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.InstanceAggregateResponse; +import com.sublinks.sublinksapi.instance.entities.InstanceAggregate; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +public abstract class SublinksInstanceAggregateMapper implements + Converter { + + @Override + @Mapping(target = "instanceKey", source = "instanceAggregate.instance.domain") + @Mapping(target = "userCount", source = "instanceAggregate.userCount") + @Mapping(target = "postCount", source = "instanceAggregate.postCount") + @Mapping(target = "commentCount", source = "instanceAggregate.commentCount") + @Mapping(target = "communityCount", source = "instanceAggregate.communityCount") + @Mapping(target = "activeDailyUserCount", source = "instanceAggregate.activeDailyUserCount") + @Mapping(target = "activeWeeklyUserCount", source = "instanceAggregate.activeWeeklyUserCount") + @Mapping(target = "activeMonthlyUserCount", source = "instanceAggregate.activeMonthlyUserCount") + @Mapping(target = "activeHalfYearlyUserCount", + source = "instanceAggregate.activeHalfYearlyUserCount") + public abstract InstanceAggregateResponse convert(@Nullable InstanceAggregate instanceAggregate); +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceConfigMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceConfigMapper.java new file mode 100644 index 00000000..f832d85c --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceConfigMapper.java @@ -0,0 +1,43 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.instance.mappers; + +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.InstanceConfigResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; +import com.sublinks.sublinksapi.instance.entities.InstanceConfig; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +public abstract class SublinksInstanceConfigMapper implements + Converter { + + @Override + @Mapping(target = "instanceKey", source = "instanceConfig.instance.domain") + @Mapping(target = "registrationMode", source = "instanceConfig.registrationMode") + @Mapping(target = "registrationQuestion", source = "instanceConfig.registrationQuestion") + @Mapping(target = "privateInstance", source = "instanceConfig.privateInstance") + @Mapping(target = "requireEmailVerification", source = "instanceConfig.requireEmailVerification") + @Mapping(target = "enableDownvotes", source = "instanceConfig.enableDownvotes") + @Mapping(target = "enableNsfw", source = "instanceConfig.enableNsfw") + @Mapping(target = "communityCreationAdminOnly", + source = "instanceConfig.communityCreationAdminOnly") + @Mapping(target = "applicationEmailAdmins", source = "instanceConfig.applicationEmailAdmins") + @Mapping(target = "reportEmailAdmins", source = "instanceConfig.reportEmailAdmins") + @Mapping(target = "hideModlogModNames", source = "instanceConfig.hideModlogModNames") + @Mapping(target = "federationEnabled", source = "instanceConfig.federationEnabled") + @Mapping(target = "captchaEnabled", source = "instanceConfig.captchaEnabled") + @Mapping(target = "captchaDifficulty", source = "instanceConfig.captchaDifficulty") + @Mapping(target = "nameMaxLength", source = "instanceConfig.nameMaxLength") + @Mapping(target = "defaultTheme", source = "instanceConfig.defaultTheme") + @Mapping(target = "defaultPostListingType", source = "instanceConfig.defaultPostListingType") + @Mapping(target = "legalInformation", source = "instanceConfig.legalInformation") + @Mapping(target = "createdAt", + source = "instanceConfig.createdAt", + dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "updatedAt", + source = "instanceConfig.updatedAt", + dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + public abstract InstanceConfigResponse convert(@Nullable InstanceConfig instanceConfig); +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceMapper.java new file mode 100644 index 00000000..18347afb --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceMapper.java @@ -0,0 +1,33 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.instance.mappers; + +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.InstanceResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; +import com.sublinks.sublinksapi.instance.entities.Instance; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) +public abstract class SublinksInstanceMapper implements Converter { + + @Override + @Mapping(target = "key", source = "instance.domain") + @Mapping(target = "name", source = "instance.name") + @Mapping(target = "description", source = "instance.description") + @Mapping(target = "domain", source = "instance.domain") + @Mapping(target = "software", source = "instance.software") + @Mapping(target = "version", source = "instance.version") + @Mapping(target = "sidebar", source = "instance.sidebar") + @Mapping(target = "iconUrl", source = "instance.iconUrl") + @Mapping(target = "bannerUrl", source = "instance.bannerUrl") + @Mapping(target = "publicKey", source = "instance.publicKey") + @Mapping(target = "createdAt", + source = "instance.createdAt", + dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "updatedAt", + source = "instance.updatedAt", + dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + public abstract InstanceResponse convert(@Nullable Instance instance); +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/IndexInstance.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/IndexInstance.java new file mode 100644 index 00000000..a86011c8 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/IndexInstance.java @@ -0,0 +1,11 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.instance.models; + +import lombok.Builder; + +@Builder +public record IndexInstance( + String search, + Integer page, + Integer perPage) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/InstanceAggregateResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/InstanceAggregateResponse.java new file mode 100644 index 00000000..a644ee76 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/InstanceAggregateResponse.java @@ -0,0 +1,14 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.instance.models; + +public record InstanceAggregateResponse( + String instanceKey, + Integer userCount, + Integer postCount, + Integer commentCount, + Integer communityCount, + Integer activeDailyUserCount, + Integer activeWeeklyUserCount, + Integer activeMonthlyUserCount, + Integer activeHalfYearlyUserCount) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/InstanceConfigResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/InstanceConfigResponse.java new file mode 100644 index 00000000..92422351 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/InstanceConfigResponse.java @@ -0,0 +1,28 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.instance.models; + +import com.sublinks.sublinksapi.api.sublinks.v1.instance.enums.ListingType; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.enums.RegistrationMode; + +public record InstanceConfigResponse( + String instanceKey, + RegistrationMode registrationMode, + String registrationQuestion, + Boolean privateInstance, + Boolean requireEmailVerification, + Boolean enableDownvotes, + Boolean enableNsfw, + Boolean communityCreationAdminOnly, + Boolean applicationEmailAdmins, + Boolean reportEmailAdmins, + Boolean hideModlogModNames, + Boolean federationEnabled, + Boolean captchaEnabled, + String captchaDifficulty, + Integer nameMaxLength, + String defaultTheme, + ListingType defaultPostListingType, + String legalInformation, + String createdAt, + String updatedAt) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/InstanceResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/InstanceResponse.java new file mode 100644 index 00000000..ec340660 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/InstanceResponse.java @@ -0,0 +1,17 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.instance.models; + +public record InstanceResponse( + String key, + String name, + String description, + String domain, + String software, + String version, + String sidebar, + String iconUrl, + String bannerUrl, + String publicKey, + String createdAt, + String updatedAt) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/UpdateInstanceConfig.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/UpdateInstanceConfig.java new file mode 100644 index 00000000..414d66e9 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/UpdateInstanceConfig.java @@ -0,0 +1,27 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.instance.models; + +import com.sublinks.sublinksapi.api.sublinks.v1.instance.enums.ListingType; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.enums.RegistrationMode; + +public record UpdateInstanceConfig( + RegistrationMode registrationMode, + String registrationQuestion, + Boolean privateInstance, + Boolean requireEmailVerification, + Boolean enableDownvotes, + Boolean enableNsfw, + Boolean communityCreationAdminOnly, + Boolean applicationEmailAdmins, + Boolean reportEmailAdmins, + Boolean hideModlogModNames, + Boolean federationEnabled, + Boolean captchaEnabled, + String captchaDifficulty, + Integer nameMaxLength, + String defaultTheme, + ListingType defaultPostListingType, + String legalInformation, + String createdAt, + String updatedAt) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/service/SublinksInstanceService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/service/SublinksInstanceService.java new file mode 100644 index 00000000..41e80e2d --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/service/SublinksInstanceService.java @@ -0,0 +1,77 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.instance.service; + +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.IndexInstance; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.InstanceAggregateResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.InstanceConfigResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.InstanceResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.UpdateInstanceConfig; +import com.sublinks.sublinksapi.instance.entities.InstanceConfig; +import com.sublinks.sublinksapi.instance.repositories.InstanceAggregateRepository; +import com.sublinks.sublinksapi.instance.repositories.InstanceConfigRepository; +import com.sublinks.sublinksapi.instance.repositories.InstanceRepository; +import com.sublinks.sublinksapi.instance.services.InstanceConfigService; +import com.sublinks.sublinksapi.instance.services.InstanceService; +import java.util.List; +import java.util.stream.Collectors; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.ConversionService; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +@Service +@AllArgsConstructor +public class SublinksInstanceService { + + private final InstanceRepository instanceRepository; + private final InstanceConfigRepository instanceConfigRepository; + private final InstanceAggregateRepository instanceAggregateRepository; + private final InstanceService instanceService; + private final InstanceConfigService instanceConfigService; + private final ConversionService conversionService; + + public List index(final IndexInstance indexInstance) { + + if (indexInstance.search() == null) { + return instanceRepository.findAll( + PageRequest.of(indexInstance.page(), indexInstance.perPage())) + .stream() + .map(instance -> conversionService.convert(instance, InstanceResponse.class)) + .collect(Collectors.toList()); + } + + return instanceRepository.findInstancesByDomainOrDescriptionOrSidebar(indexInstance.search(), + PageRequest.of(indexInstance.page(), indexInstance.perPage())) + .stream() + .map(instance -> conversionService.convert(instance, InstanceResponse.class)) + .collect(Collectors.toList()); + } + + public InstanceResponse show(final String key) { + + return conversionService.convert(instanceRepository.findInstanceByDomain(key), + InstanceResponse.class); + } + + public InstanceAggregateResponse showAggregate(final String key) { + + return conversionService.convert(instanceAggregateRepository.findByInstance_Domain(key), + InstanceAggregateResponse.class); + } + + public InstanceConfigResponse updateConfig(final String key, + final UpdateInstanceConfig updateInstanceConfigForm) + { + + final InstanceConfig config = instanceConfigRepository.findByInstance_Domain(key) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "instance_not_found")); + + if (updateInstanceConfigForm.nameMaxLength() != null) { + config.setActorNameMaxLength(Math.max(updateInstanceConfigForm.nameMaxLength(), 0)); + } + + instanceConfigService.updateInstanceConfig(config); + return conversionService.convert(config, InstanceConfigResponse.class); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java index d607b8a7..fb40d150 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java @@ -6,7 +6,6 @@ import com.sublinks.sublinksapi.api.sublinks.v1.person.models.IndexPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonIdentity; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.UpdatePerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; @@ -19,18 +18,16 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import java.util.List; +import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; -import org.springframework.data.domain.PageRequest; -import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ResponseStatusException; @RestController @RequestMapping("api/v1/person") @@ -46,26 +43,25 @@ public class SublinksPersonController extends AbstractSublinksApiController { @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List index(final IndexPerson indexPerson) { + public List index(@RequestParam(required = false) final IndexPerson indexPerson, + final SublinksJwtPerson principal) + { - return personRepository.findAllByNameAndBiography(indexPerson.search(), - PageRequest.of(indexPerson.page(), indexPerson.limit())) - .stream() - .map(person -> conversionService.convert(person, PersonResponse.class)) - .toList(); + final Optional person = getOptionalPerson(principal); + + return sublinksPersonService.index(indexPerson == null ? IndexPerson.builder() + .build() : indexPerson); } @Operation(summary = "Get a specific person") @GetMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public PersonResponse show(@PathVariable String key) { + public PersonResponse show(@PathVariable String key, final SublinksJwtPerson principal) { - final PersonIdentity ids = sublinksPersonService.getPersonIdentifiersFromKey(key); + final Optional person = getOptionalPerson(principal); - return personRepository.findOneByNameAndInstance_Domain(ids.name(), ids.domain()) - .map(person -> conversionService.convert(person, PersonResponse.class)) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + return sublinksPersonService.show(key); } @Operation(summary = "Register a new person") @@ -73,7 +69,8 @@ public PersonResponse show(@PathVariable String key) { @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public LoginResponse create(final HttpServletRequest request, - @RequestBody @Valid final CreatePerson createPerson) { + @RequestBody @Valid final CreatePerson createPerson) + { return sublinksPersonService.registerPerson(createPerson, request.getRemoteAddr(), request.getHeader("User-Agent")); @@ -84,7 +81,8 @@ public LoginResponse create(final HttpServletRequest request, @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public LoginResponse login(final HttpServletRequest request, - @RequestBody @Valid final LoginPerson loginPerson) { + @RequestBody @Valid final LoginPerson loginPerson) + { return sublinksPersonService.login(loginPerson, request.getRemoteAddr(), request.getHeader("User-Agent")); @@ -95,7 +93,8 @@ public LoginResponse login(final HttpServletRequest request, @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public PersonResponse update(@PathVariable String key, - @RequestBody @Valid final UpdatePerson updatePersonForm, final SublinksJwtPerson principal) { + @RequestBody @Valid final UpdatePerson updatePersonForm, final SublinksJwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java index 171b5b12..7d8a9b5b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java @@ -8,7 +8,7 @@ public record IndexPerson(String search, SublinksListingType sublinksListingType, SortType sortType, - Integer limit, + Integer perPage, Integer page) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java index d1999274..90fcea05 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java @@ -1,25 +1,28 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.models; import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.Role; -import java.util.Optional; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import lombok.Builder; @Builder -public record PersonResponse(String key, - String name, - String displayName, - String avatarImageUrl, - String bannerImageUrl, - String bio, - String matrixUserId, - String actorId, - Boolean isLocal, - Boolean isBanned, - Optional banExpiresAt, - Boolean isDeleted, - Boolean isBotAccount, - Role role, - String createdAt, - String updatedAt) { +public record PersonResponse( + String key, + String name, + String displayName, + String avatarImageUrl, + String bannerImageUrl, + String bio, + String matrixUserId, + String actorId, + Boolean isLocal, + Boolean isBanned, + @Schema(description = "The date and time the users ban expires at", + requiredMode = RequiredMode.NOT_REQUIRED) String banExpiresAt, + Boolean isDeleted, + Boolean isBotAccount, + Role role, + String createdAt, + String updatedAt) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index 7ad6f4a7..7fed98a1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -6,6 +6,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.moderation.IndexBannedPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.enums.RegistrationState; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.CreatePerson; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.IndexPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonAggregateResponse; @@ -36,6 +37,7 @@ import com.sublinks.sublinksapi.person.services.PersonRegistrationApplicationService; import com.sublinks.sublinksapi.person.services.PersonService; import com.sublinks.sublinksapi.person.services.UserDataService; +import com.sublinks.sublinksapi.utils.PaginationUtils; import java.util.List; import java.util.Locale; import java.util.Map; @@ -94,6 +96,51 @@ public PersonIdentity getPersonIdentifiersFromKey(String key) { .build(); } + /** + * Retrieves a list of PersonResponse objects based on the search criteria. + * + * @param indexPerson The object containing the search criteria for the persons. + * @return A list of PersonResponse objects representing the persons that match the search + * criteria. + */ + public List index(final IndexPerson indexPerson) { + + if (indexPerson.search() == null) { + return personRepository.findAll( + PageRequest.of(Math.max(indexPerson.page() != null ? indexPerson.page() : 1, 1), + PaginationUtils.Clamp(indexPerson.perPage() != null ? indexPerson.perPage() : 20, 1, + 20))) + .stream() + .map(person -> conversionService.convert(person, PersonResponse.class)) + .toList(); + } + return personRepository.findAllByNameAndBiography(indexPerson.search(), + PageRequest.of(Math.max(indexPerson.page() != null ? indexPerson.page() : 1, 1), + PaginationUtils.Clamp(indexPerson.perPage() != null ? indexPerson.perPage() : 20, 1, + 20))) + .stream() + .map(person -> conversionService.convert(person, PersonResponse.class)) + .toList(); + } + + /** + * Retrieves a PersonResponse object based on the provided key. + * + * @param key The key containing the person's information. If the key contains "@", it is split + * into name and domain using "@" as the separator. Otherwise, the name is set as the + * key and the domain is obtained from the local instance context. + * @return The PersonResponse object representing the person information. + * @throws ResponseStatusException if the person is not found. + */ + public PersonResponse show(final String key) { + + final PersonIdentity ids = getPersonIdentifiersFromKey(key); + + return personRepository.findOneByNameAndInstance_Domain(ids.name(), ids.domain()) + .map(person -> conversionService.convert(person, PersonResponse.class)) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + } + /** * Registers a person with the given details. * diff --git a/src/main/java/com/sublinks/sublinksapi/instance/entities/InstanceConfig.java b/src/main/java/com/sublinks/sublinksapi/instance/entities/InstanceConfig.java index 9a7e352f..654ff3d3 100644 --- a/src/main/java/com/sublinks/sublinksapi/instance/entities/InstanceConfig.java +++ b/src/main/java/com/sublinks/sublinksapi/instance/entities/InstanceConfig.java @@ -47,7 +47,6 @@ public class InstanceConfig { @Column(name = "registration_mode") @Enumerated(EnumType.STRING) - private RegistrationMode registrationMode; @Column(name = "registration_question") @@ -94,7 +93,6 @@ public class InstanceConfig { @Column(name = "default_post_listing_type") @Enumerated(EnumType.STRING) - private ListingType defaultPostListingType; @Column(name = "legal_information") diff --git a/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceAggregateRepository.java b/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceAggregateRepository.java index c28743d2..bc16e0f1 100644 --- a/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceAggregateRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceAggregateRepository.java @@ -5,4 +5,6 @@ public interface InstanceAggregateRepository extends JpaRepository { + InstanceAggregate findByInstance_Domain(String domain); + } diff --git a/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceConfigRepository.java b/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceConfigRepository.java index fd2fc2f1..836e689a 100644 --- a/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceConfigRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceConfigRepository.java @@ -8,4 +8,6 @@ public interface InstanceConfigRepository extends JpaRepository { Optional findByInstance(Instance instance); + + Optional findByInstance_Domain(String instance_domain); } diff --git a/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceRepository.java b/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceRepository.java index 663ce9e5..e33871c7 100644 --- a/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceRepository.java @@ -1,10 +1,19 @@ package com.sublinks.sublinksapi.instance.repositories; import com.sublinks.sublinksapi.instance.entities.Instance; +import java.util.List; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface InstanceRepository extends JpaRepository { Instance findInstanceByDomain(String domain); + @Query(value = "SELECT s FROM instances s WHERE s.search_vector @@ to_tsquery('keyword', :keyword)", + countQuery = "SELECT COUNT(s.id) FROM instances s WHERE s.search_vector @@ to_tsquery('english', :keyword)", + nativeQuery = true) + List findInstancesByDomainOrDescriptionOrSidebar(@Param("keyword") String keyword, Pageable pageable); + } diff --git a/src/main/java/com/sublinks/sublinksapi/utils/PaginationUtils.java b/src/main/java/com/sublinks/sublinksapi/utils/PaginationUtils.java index c44a1ca7..dfbca098 100644 --- a/src/main/java/com/sublinks/sublinksapi/utils/PaginationUtils.java +++ b/src/main/java/com/sublinks/sublinksapi/utils/PaginationUtils.java @@ -26,11 +26,17 @@ public static int getOffset(int page, int size) { * @param size The number of records per page. */ public static void applyPagination(TypedQuery query, @Nullable Integer page, - Integer size) { + Integer size) + { if (page != null) { query.setFirstResult(getOffset(page, size)); } query.setMaxResults(Math.abs(size)); } + + public static int Clamp(T value, T min, T max) { + + return Math.min(Math.max(value, min), max); + } } 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 11721e7b..04a6c68f 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 @@ -140,6 +140,10 @@ CREATE TABLE instances name VARCHAR(255) NULL, description TEXT NULL, sidebar TEXT NULL, + search_vector TSVECTOR GENERATED ALWAYS AS (to_tsvector('english', + coalesce(name, '') || + ' ' || + coalesce(description, ''))) STORED, icon_url TEXT NULL, banner_url TEXT NULL, public_key TEXT NOT NULL, From 6fdc7b38f01ae7a9c335bfba215772fd6c546f3c Mon Sep 17 00:00:00 2001 From: rooki Date: Thu, 30 May 2024 22:41:18 +0200 Subject: [PATCH 047/115] LINT Signed-off-by: rooki --- .../v1/instance/controllers/SublinksInstanceController.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java index 5703441e..ceb85eef 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java @@ -37,11 +37,11 @@ public List index( } @Operation(summary = "Get a specific instance") - @GetMapping("/{id}") + @GetMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public InstanceResponse show(@PathVariable String id) { + public InstanceResponse show(@PathVariable String key) { - return sublinksInstanceService.show(id); + return sublinksInstanceService.show(key); } } From 1885ed33fc124ea6801be9e90eea0643bc422247 Mon Sep 17 00:00:00 2001 From: rooki Date: Fri, 31 May 2024 20:32:33 +0200 Subject: [PATCH 048/115] Extend Search API to support communities, posts, and comments In this update, the Search API has been expanded to include not only person-related responses but also community, post, and comment responses. A new service, SublinksSearchService, has been added, along with several model updates to support these features. Additionally, code adjustments have been made to adapt the parameter renaming of 'limit' to 'perPage'. Signed-off-by: rooki --- .../controllers/ModerationLogController.java | 2 +- .../modlog/services/ModerationLogService.java | 2 +- .../v1/comment/models/IndexComment.java | 19 ++-- .../services/SublinksCommentService.java | 4 +- .../v1/community/models/IndexCommunity.java | 19 ++-- .../controllers/SublinksSearchController.java | 29 +++++-- .../api/sublinks/v1/search/models/Search.java | 17 ++++ .../v1/search/models/SearchResponse.java | 12 ++- .../services/SublinksSearchService.java | 86 +++++++++++++++++++ 9 files changed, 162 insertions(+), 28 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/Search.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/modlog/controllers/ModerationLogController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/modlog/controllers/ModerationLogController.java index 15ce8d74..8ee2190d 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/modlog/controllers/ModerationLogController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/modlog/controllers/ModerationLogController.java @@ -65,7 +65,7 @@ GetModlogResponse index(@Valid final GetModLog getModLogForm, final JwtPerson pr RolePermissionModLogTypes.READ_MODLOG, () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); - // Lemmy limit is 20 per ModLog table and there are 15 tables + // Lemmy perPage is 20 per ModLog table and there are 15 tables final int limit = 300; final List removed_posts = new ArrayList<>(); diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/modlog/services/ModerationLogService.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/modlog/services/ModerationLogService.java index 0522cd9d..ce65808c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/modlog/services/ModerationLogService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/modlog/services/ModerationLogService.java @@ -73,7 +73,7 @@ public class ModerationLogService { * @param moderationPersonId Moderation Person Id * @param otherPersonId Other Person Id * @param page the page number - * @param pageSize the size limit of a page + * @param pageSize the size perPage of a page * @param sort the sort option * @return a Page of moderation logs */ diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java index bfb52aac..d9a19e46 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java @@ -5,14 +5,15 @@ import lombok.Builder; @Builder -public record IndexComment(String search, - SortType sortType, - SublinksListingType sublinksListingType, - String communityKey, - String postKey, - Boolean showNsfw, - Boolean savedOnly, - Integer limit, - Integer page) { +public record IndexComment( + String search, + SortType sortType, + SublinksListingType sublinksListingType, + String communityKey, + String postKey, + Boolean showNsfw, + Boolean savedOnly, + Integer perPage, + Integer page) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index 9bf1e51c..0a1a4807 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -63,7 +63,7 @@ public class SublinksCommentService { * @return A list of CommentResponse objects representing the retrieved comments. * @throws ResponseStatusException If the user does not have permission to read comments. */ - public List index(IndexComment indexCommentForm, Person person) { + public List index(final IndexComment indexCommentForm, final Person person) { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.READ_COMMENTS, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_update_not_permitted")); @@ -108,7 +108,7 @@ public List index(IndexComment indexCommentForm, Person person) CommentSearchCriteria.CommentSearchCriteriaBuilder commentSearchCriteria = CommentSearchCriteria.builder() .page(indexCommentForm.page()) .commentSortType(conversionService.convert(sortType, CommentSortType.class)) - .perPage(indexCommentForm.limit()) + .perPage(indexCommentForm.perPage()) .savedOnly(indexCommentForm.savedOnly()) .listingType(conversionService.convert(sublinksListingType, ListingType.class)) .community(community.orElse(null)) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java index 5d0a6e1b..b7089e7f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java @@ -7,11 +7,18 @@ import lombok.Builder; @Builder -public record IndexCommunity(String search, - @Schema(description = "Sort type", example = "Hot", requiredMode = RequiredMode.NOT_REQUIRED) SortType sortType, - @Schema(description = "Sublinks listing type", example = "All", requiredMode = RequiredMode.NOT_REQUIRED) SublinksListingType sublinksListingType, - @Schema(description = "Show NSFW", example = "false", requiredMode = RequiredMode.NOT_REQUIRED) Boolean showNsfw, - Integer limit, - Integer page) { +public record IndexCommunity( + String search, + @Schema(description = "Sort type", + example = "Hot", + requiredMode = RequiredMode.NOT_REQUIRED) SortType sortType, + @Schema(description = "Sublinks listing type", + example = "All", + requiredMode = RequiredMode.NOT_REQUIRED) SublinksListingType sublinksListingType, + @Schema(description = "Show NSFW", + example = "false", + requiredMode = RequiredMode.NOT_REQUIRED) Boolean showNsfw, + Integer perPage, + Integer page) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java index cfb317ff..ca5e5b02 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java @@ -1,22 +1,39 @@ package com.sublinks.sublinksapi.api.sublinks.v1.search.controllers; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.search.models.Search; +import com.sublinks.sublinksapi.api.sublinks.v1.search.models.SearchResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.search.services.SublinksSearchService; +import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.web.bind.annotation.*; +import java.util.Optional; +import lombok.AllArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +@AllArgsConstructor @RestController @RequestMapping("api/v1/search") @Tag(name = "Search", description = "Search API") public class SublinksSearchController extends AbstractSublinksApiController { - @Operation(summary = "Get a list of privatemessages") + + private final SublinksSearchService sublinksSearchService; + + @Operation(summary = "Get a list of privatemessages") @GetMapping @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void index() { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public void index(@RequestBody Search search, final SublinksJwtPerson principal) { + + final Optional person = getOptionalPerson(principal); + + final SearchResponse.SearchResponseBuilder searchResponseBuilder = SearchResponse.builder(); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/Search.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/Search.java new file mode 100644 index 00000000..9fe872b9 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/Search.java @@ -0,0 +1,17 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.search.models; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; +import lombok.Builder; + +// @todo: Add Communities, Posts, Comments, and Messages +@Builder +public record Search( + String search, + SortType type, + SublinksListingType sublinksListingType, + Boolean showNsfw, + Integer perPage, + Integer page) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/SearchResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/SearchResponse.java index 536911d0..3c9ecfab 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/SearchResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/SearchResponse.java @@ -1,12 +1,18 @@ package com.sublinks.sublinksapi.api.sublinks.v1.search.models; -import com.sublinks.sublinksapi.api.lemmy.v3.user.models.Person; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import java.util.List; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; import lombok.Builder; // @todo: Add Communities, Posts, Comments, and Messages @Builder -public record SearchResponse(String key, - List persons) { +public record SearchResponse( + List persons, + List communities, + List posts, + List comments) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java new file mode 100644 index 00000000..6a3dfc02 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java @@ -0,0 +1,86 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.search.services; + +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.IndexComment; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.IndexCommunity; +import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.IndexPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; +import com.sublinks.sublinksapi.api.sublinks.v1.search.models.Search; +import com.sublinks.sublinksapi.api.sublinks.v1.search.models.SearchResponse; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommentTypes; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommunityTypes; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionInstanceTypes; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionPostTypes; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; +import com.sublinks.sublinksapi.person.entities.Person; +import java.util.Optional; +import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +@AllArgsConstructor +@Service +public class SublinksSearchService { + + + private final RolePermissionService rolePermissionService; + private final SublinksPersonService sublinksPersonService; + private final SublinksCommentService sublinksCommentService; + private final SublinksCommunityService sublinksCommunityService; + + public SearchResponse list(final Search searchForm, final Optional person) { + + rolePermissionService.isPermitted(person.orElse(null), + RolePermissionInstanceTypes.INSTANCE_SEARCH, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "search_not_permitted")); + + final String search = searchForm.search(); + + final Integer page = searchForm.page() == null ? 1 : searchForm.page(); + final Integer perPage = searchForm.perPage() == null ? 20 : searchForm.perPage(); + + final SearchResponse.SearchResponseBuilder searchResponseBuilder = SearchResponse.builder(); + + if (rolePermissionService.isPermitted(person.orElse(null), + RolePermissionPostTypes.READ_POSTS)) { + + searchResponseBuilder.persons(sublinksPersonService.index(IndexPerson.builder() + .search(search) + .page(page) + .perPage(perPage) + .sublinksListingType(searchForm.sublinksListingType()) + .sortType(searchForm.type()) + .build())); + } + + // @todo: posts + if (rolePermissionService.isPermitted(person.orElse(null), + RolePermissionCommentTypes.READ_COMMENTS)) { + + searchResponseBuilder.comments(sublinksCommentService.index(IndexComment.builder() + .search(search) + .sublinksListingType(searchForm.sublinksListingType()) + .showNsfw(searchForm.showNsfw()) + .page(searchForm.page()) + .perPage(searchForm.perPage()) + .build(), person.orElse(null))); + } + + if (rolePermissionService.isPermitted(person.orElse(null), + RolePermissionCommunityTypes.READ_COMMUNITIES)) { + + searchResponseBuilder.communities(sublinksCommunityService.index(IndexCommunity.builder() + .search(search) + .page(page) + .perPage(perPage) + .sublinksListingType(searchForm.sublinksListingType()) + .showNsfw(searchForm.showNsfw()) + .sortType(searchForm.type()) + .build(), person.orElse(null))); + } + + return searchResponseBuilder.build(); + } +} From b67c2d8d487a1a4dab6643250b10cec92cdc082a Mon Sep 17 00:00:00 2001 From: rooki Date: Sat, 1 Jun 2024 18:06:38 +0200 Subject: [PATCH 049/115] Refactor code structure and add search functionality Code has been refactored for better readability and new methods have been added to the Builder class in PostSearchQueryService.java for filtering NSFW posts and to perform a search. Improvements to code structuring have been made across multiple files to enhance readability. Additionally, search functionality parameters have been added to the IndexCommunity.java file. Signed-off-by: rooki --- .../v1/authentication/SublinksJwtPerson.java | 4 +- .../v1/authentication/SublinksJwtUtil.java | 6 +- .../config/SublinksSecurityConfig.java | 1 - .../SublinksCommentModerationController.java | 9 +- .../comment/mappers/LemmyCommentMapper.java | 11 +- .../v1/comment/models/CommentResponse.java | 23 +- .../v1/comment/models/CreateComment.java | 31 +- .../v1/comment/models/IndexComment.java | 22 +- .../models/Moderation/CommentDelete.java | 5 +- .../models/Moderation/CommentRemove.java | 5 +- .../v1/comment/models/UpdateComment.java | 15 +- .../services/SublinksCommentService.java | 3 +- .../AbstractSublinksApiController.java | 32 ++- .../mappers/LemmyListingTypeMapper.java | 3 +- .../mappers/SublinksSortTypeMapper.java | 3 +- ...ublinksCommunityAggregationController.java | 3 +- ...SublinksCommunityModerationController.java | 270 ++++++++++-------- .../mappers/SublinksCommunityMapper.java | 8 +- .../models/CommunityAggregatesResponse.java | 17 +- .../community/models/CommunityResponse.java | 33 +-- .../v1/community/models/CreateCommunity.java | 15 +- .../v1/community/models/IndexCommunity.java | 10 +- .../models/Moderation/CommunityBanPerson.java | 5 +- .../models/Moderation/CommunityDelete.java | 5 +- .../CommunityModeratorResponse.java | 5 +- .../models/Moderation/CommunityRemove.java | 5 +- .../v1/community/models/UpdateCommunity.java | 11 +- .../services/SublinksCommunityService.java | 12 +- .../models/moderation/IndexBannedPerson.java | 11 +- .../SublinksLanguageController.java | 4 +- .../v1/languages/models/LanguageResponse.java | 8 +- .../v1/person/models/CreatePerson.java | 27 +- .../v1/person/models/IndexPerson.java | 13 +- .../v1/person/models/PersonIdentity.java | 5 +- .../v1/person/models/UpdatePerson.java | 21 +- .../sublinks/v1/post/models/CreatePost.java | 36 +++ .../sublinks/v1/post/models/IndexPost.java | 21 ++ .../sublinks/v1/post/models/LinkMetaData.java | 11 +- .../v1/post/models/PostAggregateResponse.java | 15 +- .../sublinks/v1/post/models/PostResponse.java | 41 +-- .../v1/post/services/SublinksPostService.java | 60 ++++ .../SublinksPrivatemessageController.java | 3 +- .../mappers/SublinksPrivateMessageMapper.java | 11 +- .../models/PrivateMessageResponse.java | 21 +- .../controllers/SublinksRolesController.java | 10 +- .../v1/roles/mappers/SublinksRoleMapper.java | 12 +- .../api/sublinks/v1/roles/models/Role.java | 17 +- .../v1/search/models/SearchResponse.java | 2 +- .../v1/utils/PaginationControllerUtils.java | 3 +- .../utils/mappers/OptionalStringMapper.java | 3 +- .../comment/models/CommentSearchCriteria.java | 20 +- .../repositories/CommentRepositoryImpl.java | 23 +- .../repositories/CommunityRepository.java | 2 + .../post/models/PostSearchCriteria.java | 9 +- .../post/repositories/PostRepositoryImpl.java | 38 ++- .../post/services/PostSearchQueryService.java | 44 ++- 56 files changed, 697 insertions(+), 366 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/CreatePost.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/IndexPost.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtPerson.java index d38d9fa9..8fef3ab7 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtPerson.java @@ -9,7 +9,9 @@ public class SublinksJwtPerson extends AbstractAuthenticationToken { private final Person person; - public SublinksJwtPerson(final Person person, final Collection authorities) { + public SublinksJwtPerson(final Person person, + final Collection authorities) + { super(authorities); this.person = person; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtUtil.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtUtil.java index 8e9aac68..854c8f98 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtUtil.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtUtil.java @@ -60,7 +60,11 @@ public T extractClaim(final String token, final Function claimsRe private Claims extractAllClaims(final String token) { final SecretKey key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret)); - return Jwts.parser().verifyWith(key).build().parseSignedClaims(token).getPayload(); + return Jwts.parser() + .verifyWith(key) + .build() + .parseSignedClaims(token) + .getPayload(); } private Boolean isTokenExpired(final String token) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SublinksSecurityConfig.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SublinksSecurityConfig.java index 61f67e8d..7c7fe42c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SublinksSecurityConfig.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SublinksSecurityConfig.java @@ -42,7 +42,6 @@ public SecurityFilterChain sublinksFilterChain(final HttpSecurity http) throws E .sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy( SessionCreationPolicy.STATELESS)); - return http.build(); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java index fbb866a4..dbdaca71 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java @@ -35,7 +35,8 @@ public class SublinksCommentModerationController extends AbstractSublinksApiCont @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommentResponse remove(@PathVariable final String key, @RequestBody @Valid final CommentRemove commentRemove, - final SublinksJwtPerson sublinksJwtPerson) { + final SublinksJwtPerson sublinksJwtPerson) + { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); @@ -48,7 +49,8 @@ public CommentResponse remove(@PathVariable final String key, @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommentResponse delete(@PathVariable final String key, @RequestBody @Valid final CommentDelete commentDeleteForm, - final SublinksJwtPerson sublinksJwtPerson) { + final SublinksJwtPerson sublinksJwtPerson) + { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); @@ -60,7 +62,8 @@ public CommentResponse delete(@PathVariable final String key, @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommentResponse highlight(@PathVariable final String key, final CommentPin commentPinForm, - final SublinksJwtPerson sublinksJwtPerson) { + final SublinksJwtPerson sublinksJwtPerson) + { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/LemmyCommentMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/LemmyCommentMapper.java index 3b1daf70..37311816 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/LemmyCommentMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/LemmyCommentMapper.java @@ -10,7 +10,8 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksPersonMapper.class}) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, + uses = {SublinksPersonMapper.class}) public abstract class LemmyCommentMapper implements Converter { SublinksPersonMapper personMapper; @@ -26,8 +27,12 @@ public abstract class LemmyCommentMapper implements Converter index(final IndexComment indexCommentForm, final Pe } } - SublinksListingType sublinksListingType = indexCommentForm.sublinksListingType(); + SublinksListingType sublinksListingType = indexCommentForm.listingType(); if (sublinksListingType == null) { if (person.getDefaultListingType() != null) { @@ -106,6 +106,7 @@ public List index(final IndexComment indexCommentForm, final Pe } CommentSearchCriteria.CommentSearchCriteriaBuilder commentSearchCriteria = CommentSearchCriteria.builder() + .search(indexCommentForm.search()) .page(indexCommentForm.page()) .commentSortType(conversionService.convert(sortType, CommentSortType.class)) .perPage(indexCommentForm.perPage()) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/controllers/AbstractSublinksApiController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/controllers/AbstractSublinksApiController.java index 44831e41..b68955fa 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/controllers/AbstractSublinksApiController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/controllers/AbstractSublinksApiController.java @@ -16,10 +16,14 @@ public abstract class AbstractSublinksApiController { * @return Person * @throws ResponseStatusException Exception thrown when Person not present */ - public Person getPersonOrThrowBadRequest(SublinksJwtPerson principal) throws ResponseStatusException { + public Person getPersonOrThrowBadRequest(SublinksJwtPerson principal) + throws ResponseStatusException + { - return Optional.ofNullable(principal).map(p -> (Person) p.getPrincipal()).orElseThrow( - () -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); + return Optional.ofNullable(principal) + .map(p -> (Person) p.getPrincipal()) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); } /** @@ -30,10 +34,13 @@ public Person getPersonOrThrowBadRequest(SublinksJwtPerson principal) throws Res * @throws ResponseStatusException Exception thrown when Person not present */ public Person getPersonOrThrow(SublinksJwtPerson principal, - Supplier exceptionSupplier) throws X { + Supplier exceptionSupplier) throws X + { - return Optional.ofNullable(principal).map(p -> (Person) p.getPrincipal()).orElseThrow( - exceptionSupplier); + return Optional.ofNullable(principal) + .map(p -> (Person) p.getPrincipal()) + .orElseThrow( + exceptionSupplier); } @@ -44,10 +51,14 @@ public Person getPersonOrThrow(SublinksJwtPerson principal * @return Person * @throws ResponseStatusException Exception thrown when Person not present */ - public Person getPersonOrThrowUnauthorized(SublinksJwtPerson principal) throws ResponseStatusException { + public Person getPersonOrThrowUnauthorized(SublinksJwtPerson principal) + throws ResponseStatusException + { - return Optional.ofNullable(principal).map(p -> (Person) p.getPrincipal()).orElseThrow( - () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED)); + return Optional.ofNullable(principal) + .map(p -> (Person) p.getPrincipal()) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED)); } public Person getPerson(SublinksJwtPerson principal) { @@ -57,6 +68,7 @@ public Person getPerson(SublinksJwtPerson principal) { public Optional getOptionalPerson(SublinksJwtPerson principal) { - return Optional.ofNullable(principal).map(p -> (Person) p.getPrincipal()); + return Optional.ofNullable(principal) + .map(p -> (Person) p.getPrincipal()); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmyListingTypeMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmyListingTypeMapper.java index 94c9efda..9a1d2e9a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmyListingTypeMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/LemmyListingTypeMapper.java @@ -14,7 +14,8 @@ public class LemmyListingTypeMapper implements @Nullable @Override public ListingType convert( - SublinksListingType sublinksListingType) { + SublinksListingType sublinksListingType) + { return ListingType.valueOf(sublinksListingType.name()); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/SublinksSortTypeMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/SublinksSortTypeMapper.java index aee77a82..a52c60f4 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/SublinksSortTypeMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/mappers/SublinksSortTypeMapper.java @@ -13,7 +13,8 @@ public class SublinksSortTypeMapper implements @Nullable @Override public SortType convert( - com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType listingType) { + com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType listingType) + { return SortType.valueOf(listingType.name()); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java index 7873c1d8..e48aa54c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java @@ -37,7 +37,8 @@ public class SublinksCommunityAggregationController extends AbstractSublinksApiC @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityAggregatesResponse show(@PathVariable final String key, - final SublinksJwtPerson sublinksJwtPerson) { + final SublinksJwtPerson sublinksJwtPerson) + { final Optional person = getOptionalPerson(sublinksJwtPerson); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java index f9c5a48f..18d613a3 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -22,150 +22,192 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; +import java.util.List; +import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; -import java.util.List; -import java.util.Optional; - @RestController @AllArgsConstructor @RequestMapping("/api/v1/community/{key}/moderation") @Tag(name = "Community Moderation", description = "Community Moderation API") public class SublinksCommunityModerationController extends AbstractSublinksApiController { - private final LinkPersonCommunityService linkPersonCommunityService; - private final SublinksCommunityService sublinksCommunityService; - private final CommunityRepository communityRepository; - private final PersonRepository personRepository; - private final ConversionService conversionService; - private final RolePermissionService rolePermissionService; - - @Operation(summary = "Delete a community") - @PostMapping("/delete") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommunityResponse delete(@PathVariable final String key, @RequestBody @Valid CommunityDelete communityDeleteForm, SublinksJwtPerson sublinksJwtPerson) { - - final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - - return sublinksCommunityService.delete(key, communityDeleteForm, person); - } - - @Operation(summary = "Remove a community") - @GetMapping("/remove") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommunityResponse remove(@PathVariable final String key, @RequestBody @Valid CommunityRemove communityRemoveForm, SublinksJwtPerson sublinksJwtPerson) { - - final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - - return sublinksCommunityService.remove(key, communityRemoveForm, person); - } - - - @Operation(summary = "Get moderators of the community") - @GetMapping("/moderators") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List show(@PathVariable final String key, final SublinksJwtPerson sublinksJwtPerson) { - - Optional person = getOptionalPerson(sublinksJwtPerson); - - rolePermissionService.isPermitted(person.orElse(null), RolePermissionCommunityTypes.READ_COMMUNITY_MODERATORS, () -> { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_read_moderators"); + private final LinkPersonCommunityService linkPersonCommunityService; + private final SublinksCommunityService sublinksCommunityService; + private final CommunityRepository communityRepository; + private final PersonRepository personRepository; + private final ConversionService conversionService; + private final RolePermissionService rolePermissionService; + + @Operation(summary = "Delete a community") + @PostMapping("/delete") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public CommunityResponse delete(@PathVariable final String key, + @RequestBody @Valid CommunityDelete communityDeleteForm, SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksCommunityService.delete(key, communityDeleteForm, person); + } + + @Operation(summary = "Remove a community") + @GetMapping("/remove") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public CommunityResponse remove(@PathVariable final String key, + @RequestBody @Valid CommunityRemove communityRemoveForm, SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksCommunityService.remove(key, communityRemoveForm, person); + } + + + @Operation(summary = "Get moderators of the community") + @GetMapping("/moderators") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List show(@PathVariable final String key, + final SublinksJwtPerson sublinksJwtPerson) + { + + Optional person = getOptionalPerson(sublinksJwtPerson); + + rolePermissionService.isPermitted(person.orElse(null), + RolePermissionCommunityTypes.READ_COMMUNITY_MODERATORS, () -> { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_read_moderators"); }); - return sublinksCommunityService.getCommunityModerators(key, person.orElse(null)) - .stream() - .map(communityModerator -> conversionService.convert(communityModerator, CommunityModeratorResponse.class)) - .toList(); + return sublinksCommunityService.getCommunityModerators(key, person.orElse(null)) + .stream() + .map(communityModerator -> conversionService.convert(communityModerator, + CommunityModeratorResponse.class)) + .toList(); + } + + @Operation(summary = "Add a moderator to the community") + @PostMapping("/moderator/add/{personKey}") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List add(@PathVariable final String key, + @PathVariable final String personKey, final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + + if (!(linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) + && rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.MODERATOR_ADD_MODERATOR)) + && !rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.ADMIN_ADD_COMMUNITY_MODERATOR)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_add_moderator"); } - @Operation(summary = "Add a moderator to the community") - @PostMapping("/moderator/add/{personKey}") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List add(@PathVariable final String key, @PathVariable final String personKey, final SublinksJwtPerson sublinksJwtPerson) { - - final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - - final Community community = communityRepository.findCommunityByTitleSlug(key) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - - if (!(linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) && rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.MODERATOR_ADD_MODERATOR)) && !rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.ADMIN_ADD_COMMUNITY_MODERATOR)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_add_moderator"); - } - - final Person newModerator = personRepository.findOneByNameIgnoreCase(personKey) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); - - if (linkPersonCommunityService.hasAnyLinkOrAdmin(newModerator, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "person_already_moderator"); - } + final Person newModerator = personRepository.findOneByNameIgnoreCase(personKey) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); - linkPersonCommunityService.addLink(newModerator, community, LinkPersonCommunityType.moderator); + if (linkPersonCommunityService.hasAnyLinkOrAdmin(newModerator, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "person_already_moderator"); + } - return sublinksCommunityService.getCommunityModerators(key, person) - .stream() - .map(communityModerator -> conversionService.convert(communityModerator, CommunityModeratorResponse.class)) - .toList(); + linkPersonCommunityService.addLink(newModerator, community, LinkPersonCommunityType.moderator); + + return sublinksCommunityService.getCommunityModerators(key, person) + .stream() + .map(communityModerator -> conversionService.convert(communityModerator, + CommunityModeratorResponse.class)) + .toList(); + } + + @Operation(summary = "Remove a moderator from the community") + @PostMapping("/moderator/remove/{personKey}") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List remove(@PathVariable final String key, + @PathVariable final String personKey) + { + + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + + final Person person = personRepository.findOneByNameIgnoreCase(personKey) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + + if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_remove_moderator"); } - @Operation(summary = "Remove a moderator from the community") - @PostMapping("/moderator/remove/{personKey}") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List remove(@PathVariable final String key, @PathVariable final String personKey) { + linkPersonCommunityService.removeLink(person, community, LinkPersonCommunityType.moderator); - final Community community = communityRepository.findCommunityByTitleSlug(key) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + return sublinksCommunityService.getCommunityModerators(key, person) + .stream() + .map(communityModerator -> conversionService.convert(communityModerator, + CommunityModeratorResponse.class)) + .toList(); + } - final Person person = personRepository.findOneByNameIgnoreCase(personKey) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + @Operation(summary = "Ban/Unban a user from a community") + @PostMapping("/ban/{personKey}") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PersonResponse ban(@PathVariable final String key, @PathVariable final String personKey, + CommunityBanPerson communityBanPersonForm, final SublinksJwtPerson sublinksJwtPerson) + { - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_remove_moderator"); - } + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - linkPersonCommunityService.removeLink(person, community, LinkPersonCommunityType.moderator); + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - return sublinksCommunityService.getCommunityModerators(key, person) - .stream() - .map(communityModerator -> conversionService.convert(communityModerator, CommunityModeratorResponse.class)) - .toList(); + if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_ban_user"); } - @Operation(summary = "Ban/Unban a user from a community") - @PostMapping("/ban/{personKey}") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public PersonResponse ban(@PathVariable final String key, @PathVariable final String personKey, CommunityBanPerson communityBanPersonForm, final SublinksJwtPerson sublinksJwtPerson) { - - final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + final Person bannedPerson = sublinksCommunityService.banPerson(key, personKey, person, + communityBanPersonForm); - final Community community = communityRepository.findCommunityByTitleSlug(key) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + return conversionService.convert(bannedPerson, PersonResponse.class); + } - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_ban_user"); - } - final Person bannedPerson = sublinksCommunityService.banPerson(key, personKey, person, communityBanPersonForm); - - return conversionService.convert(bannedPerson, PersonResponse.class); - } + @Operation(summary = "Get banned users from a community") + @GetMapping("/banned") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List banned(@PathVariable final String key) { + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - @Operation(summary = "Get banned users from a community") - @GetMapping("/banned") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List banned(@PathVariable final String key) { - - final Community community = communityRepository.findCommunityByTitleSlug(key) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - - return linkPersonCommunityService.getPersonsFromCommunityAndListTypes(community, List.of(LinkPersonCommunityType.banned)) - .stream() - .map(person -> conversionService.convert(person, PersonResponse.class)) - .toList(); - } + return linkPersonCommunityService.getPersonsFromCommunityAndListTypes(community, + List.of(LinkPersonCommunityType.banned)) + .stream() + .map(person -> conversionService.convert(person, PersonResponse.class)) + .toList(); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityMapper.java index 7da47b64..aa8a1ff2 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityMapper.java @@ -30,7 +30,11 @@ public abstract class SublinksCommunityMapper implements Converter bannerImageUrl, - String activityPubId, - List languages, - Boolean isLocal, - Boolean isDeleted, - Boolean isRemoved, - Boolean isNsfw, - Boolean restrictedToModerators, - String publicKey, - String createdAt, - String updatedAt +public record CommunityResponse( + String key, + String title, + String titleSlug, + String description, + String iconImageUrl, + Optional bannerImageUrl, + String activityPubId, + List languages, + Boolean isLocal, + Boolean isDeleted, + Boolean isRemoved, + Boolean isNsfw, + Boolean restrictedToModerators, + String publicKey, + String createdAt, + String updatedAt ) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CreateCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CreateCommunity.java index 645a55d7..478d7fc3 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CreateCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CreateCommunity.java @@ -1,11 +1,12 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models; -public record CreateCommunity(String title, - String titleSlug, - String description, - String iconImageUrl, - String bannerImageUrl, - Boolean isNsfw, - Boolean isPostingRestrictedToMods) { +public record CreateCommunity( + String title, + String titleSlug, + String description, + String iconImageUrl, + String bannerImageUrl, + Boolean isNsfw, + Boolean isPostingRestrictedToMods) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java index b7089e7f..f4469a85 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java @@ -8,17 +8,21 @@ @Builder public record IndexCommunity( - String search, + @Schema(description = "Search query", + example = "Search query", + requiredMode = RequiredMode.NOT_REQUIRED) String search, @Schema(description = "Sort type", example = "Hot", requiredMode = RequiredMode.NOT_REQUIRED) SortType sortType, @Schema(description = "Sublinks listing type", example = "All", - requiredMode = RequiredMode.NOT_REQUIRED) SublinksListingType sublinksListingType, + requiredMode = RequiredMode.NOT_REQUIRED) SublinksListingType listingType, @Schema(description = "Show NSFW", example = "false", requiredMode = RequiredMode.NOT_REQUIRED) Boolean showNsfw, Integer perPage, - Integer page) { + @Schema(description = "Page", + example = "1", + requiredMode = RequiredMode.NOT_REQUIRED) Integer page) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java index 9564b0cd..ea95bb4b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java @@ -1,6 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; -public record CommunityBanPerson(String reason, - Boolean ban) { +public record CommunityBanPerson( + String reason, + Boolean ban) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityDelete.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityDelete.java index c3335cea..d020adc9 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityDelete.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityDelete.java @@ -1,6 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; -public record CommunityDelete(String reason, - Boolean remove) { +public record CommunityDelete( + String reason, + Boolean remove) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityModeratorResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityModeratorResponse.java index cf61de78..c2cdab0f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityModeratorResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityModeratorResponse.java @@ -5,7 +5,8 @@ import lombok.Builder; @Builder -public record CommunityModeratorResponse(PersonResponse person, - SublinksPersonCommunityType linkType) { +public record CommunityModeratorResponse( + PersonResponse person, + SublinksPersonCommunityType linkType) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityRemove.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityRemove.java index 757a6e83..e770dc43 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityRemove.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityRemove.java @@ -1,6 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; -public record CommunityRemove(String reason, - Boolean remove) { +public record CommunityRemove( + String reason, + Boolean remove) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java index 14cd56b2..81055b2d 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java @@ -1,9 +1,10 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models; -public record UpdateCommunity(String description, - String iconImageUrl, - String bannerImageUrl, - Boolean isNsfw, - Boolean isPostingRestrictedToMods) { +public record UpdateCommunity( + String description, + String iconImageUrl, + String bannerImageUrl, + Boolean isNsfw, + Boolean isPostingRestrictedToMods) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index 828e6037..f22c4f9a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -68,7 +68,7 @@ public List index(IndexCommunity indexCommunityParam, Person } } - SublinksListingType sublinksListingType = indexCommunityParam.sublinksListingType(); + SublinksListingType sublinksListingType = indexCommunityParam.listingType(); if (sublinksListingType == null) { if (person != null && person.getDefaultListingType() != null) { @@ -90,14 +90,14 @@ public List index(IndexCommunity indexCommunityParam, Person person != null && person.isShowNsfw()); final CommunitySearchCriteria.CommunitySearchCriteriaBuilder criteria = CommunitySearchCriteria.builder() - .perPage(indexCommunityParam.limit()) + .perPage(indexCommunityParam.perPage()) .page(indexCommunityParam.page()) .sortType(conversionService.convert(sortType, SortType.class)) .listingType(conversionService.convert(sublinksListingType, ListingType.class)) .showNsfw(showNsfw) .person(person); - if (indexCommunityParam.limit() == 0) { + if (indexCommunityParam.perPage() == 0) { criteria.perPage(20); } if (indexCommunityParam.page() == 0) { @@ -161,7 +161,8 @@ public CommunityResponse createCommunity(CreateCommunity createCommunity, Person * to perform the update. */ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommunityForm, - Person person) { + Person person) + { String domain = ActorIdUtils.getActorDomain(key); if (domain != null && domain.equals(localInstanceContext.instance() @@ -236,7 +237,8 @@ public List getCommunityModerators(String key, Person perso * not authorized to perform the ban action. */ public Person banPerson(String key, String personKey, Person person, - CommunityBanPerson communityBanPersonForm) { + CommunityBanPerson communityBanPersonForm) + { final Community community = communityRepository.findCommunityByTitleSlug(key) .orElseThrow( diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/moderation/IndexBannedPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/moderation/IndexBannedPerson.java index 61158a75..010e04ca 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/moderation/IndexBannedPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/models/moderation/IndexBannedPerson.java @@ -4,10 +4,11 @@ import lombok.Builder; @Builder -public record IndexBannedPerson(String search, - Boolean local, - SortOrder sortOrder, - Integer limit, - Integer page) { +public record IndexBannedPerson( + String search, + Boolean local, + SortOrder sortOrder, + Integer limit, + Integer page) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java index 3bae62c5..65b2894b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java @@ -32,7 +32,9 @@ public class SublinksLanguageController extends AbstractSublinksApiController { @Operation(summary = "Get a list of languagesKeys") @GetMapping @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "List of languagesKeys", useReturnTypeSchema = true)}) + @ApiResponse(responseCode = "200", + description = "List of languagesKeys", + useReturnTypeSchema = true)}) public List index() { List languages = localInstanceContext.instance() diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/models/LanguageResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/models/LanguageResponse.java index 3b00278f..1a5cd149 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/models/LanguageResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/models/LanguageResponse.java @@ -1,11 +1,11 @@ package com.sublinks.sublinksapi.api.sublinks.v1.languages.models; -import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; @Builder -public record LanguageResponse(String key, - String code, - String name) { +public record LanguageResponse( + String key, + String code, + String name) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/CreatePerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/CreatePerson.java index af302cd3..a9dd8d5c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/CreatePerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/CreatePerson.java @@ -5,18 +5,19 @@ import lombok.Builder; @Builder -public record CreatePerson(String name, - String displayName, - String email, - List languages, - String avatarImageUrl, - String bannerImageUrl, - String bio, - String matrixUserId, - String password, - String passwordConfirmation, - String answer, - String captcha_token, - String captcha_answer) { +public record CreatePerson( + String name, + String displayName, + String email, + List languages, + String avatarImageUrl, + String bannerImageUrl, + String bio, + String matrixUserId, + String password, + String passwordConfirmation, + String answer, + String captcha_token, + String captcha_answer) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java index 7d8a9b5b..fcb01edf 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java @@ -1,14 +1,15 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.models; -import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import lombok.Builder; @Builder -public record IndexPerson(String search, - SublinksListingType sublinksListingType, - SortType sortType, - Integer perPage, - Integer page) { +public record IndexPerson( + String search, + SublinksListingType sublinksListingType, + SortType sortType, + Integer perPage, + Integer page) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonIdentity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonIdentity.java index d1a82cdd..561b0207 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonIdentity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonIdentity.java @@ -3,7 +3,8 @@ import lombok.Builder; @Builder -public record PersonIdentity(String name, - String domain) { +public record PersonIdentity( + String name, + String domain) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/UpdatePerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/UpdatePerson.java index 9bb107ce..5a21da4a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/UpdatePerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/UpdatePerson.java @@ -4,15 +4,16 @@ import lombok.Builder; @Builder -public record UpdatePerson(String displayName, - String email, - List languagesKeys, - String avatarImageUrl, - String bannerImageUrl, - String bio, - String matrixUserId, - String oldPassword, - String password, - String passwordConfirmation) { +public record UpdatePerson( + String displayName, + String email, + List languagesKeys, + String avatarImageUrl, + String bannerImageUrl, + String bio, + String matrixUserId, + String oldPassword, + String password, + String passwordConfirmation) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/CreatePost.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/CreatePost.java new file mode 100644 index 00000000..c097e0bb --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/CreatePost.java @@ -0,0 +1,36 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.models; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import lombok.Builder; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +@Builder +public record CreatePost( + String title, + String body, + @Schema(description = "The language key of the comment", + defaultValue = "und", + example = "und", + requiredMode = RequiredMode.NOT_REQUIRED) String languageKey, + @Schema(description = "Whether the comment is featured ( Requires permission to do so. )", + defaultValue = "false", + example = "false", + requiredMode = RequiredMode.NOT_REQUIRED) Boolean featured) { + + public CreatePost { + + if (title.isBlank()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "title_can_not_be_blank"); + } + if (body.isBlank()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "body_can_not_be_blank"); + } + } + + public String languageKey() { + + return languageKey == null ? "und" : languageKey; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/IndexPost.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/IndexPost.java new file mode 100644 index 00000000..e1c109d7 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/IndexPost.java @@ -0,0 +1,21 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.models; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import java.util.List; + +public record IndexPost( + @Schema(description = "Search query", requiredMode = RequiredMode.NOT_REQUIRED) String search, + @Schema(description = "Sort type", requiredMode = RequiredMode.NOT_REQUIRED) SortType sortType, + @Schema(description = "Listing type", + requiredMode = RequiredMode.NOT_REQUIRED) SublinksListingType listingType, + @Schema(description = "Community keys", + requiredMode = RequiredMode.NOT_REQUIRED) List communityKeys, + @Schema(description = "Show NSFW", requiredMode = RequiredMode.NOT_REQUIRED) Boolean showNsfw, + @Schema(description = "Saved only", requiredMode = RequiredMode.NOT_REQUIRED) Boolean savedOnly, + @Schema(description = "Per page", requiredMode = RequiredMode.NOT_REQUIRED) Integer perPage, + @Schema(description = "Page", requiredMode = RequiredMode.NOT_REQUIRED) Integer page) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/LinkMetaData.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/LinkMetaData.java index 4f33b40b..1d95436b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/LinkMetaData.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/LinkMetaData.java @@ -3,10 +3,11 @@ import lombok.Builder; @Builder -public record LinkMetaData(String linkUrl, - String linkTitle, - String linkDescription, - String linkThumbnailUrl, - String LinkVideoUrl) { +public record LinkMetaData( + String linkUrl, + String linkTitle, + String linkDescription, + String linkThumbnailUrl, + String LinkVideoUrl) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostAggregateResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostAggregateResponse.java index f0dfadfb..1d44b383 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostAggregateResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostAggregateResponse.java @@ -1,11 +1,12 @@ package com.sublinks.sublinksapi.api.sublinks.v1.post.models; -public record PostAggregateResponse(String key, - String commentCount, - String downvoteCount, - String upvoteCount, - String score, - String hotScore, - String controversyScore) { +public record PostAggregateResponse( + String key, + String commentCount, + String downvoteCount, + String upvoteCount, + String score, + String hotScore, + String controversyScore) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostResponse.java index 5cd8d46e..99406553 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostResponse.java @@ -3,25 +3,26 @@ import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; -public record PostResponse(String key, - String title, - String titleSlug, - String body, - LinkMetaData linkMetaData, - String type, - Boolean isLocal, - Boolean isDeleted, - Boolean isRemoved, - Boolean isNsfw, - Boolean isLocked, - Boolean isFeatured, - Boolean isFeaturedInCommunity, - CommunityResponse community, - PersonResponse creator, - PostAggregateResponse postAggregate, - String activityPubId, - String publicKey, - String createdAt, - String updatedAt) { +public record PostResponse( + String key, + String title, + String titleSlug, + String body, + LinkMetaData linkMetaData, + String type, + Boolean isLocal, + Boolean isDeleted, + Boolean isRemoved, + Boolean isNsfw, + Boolean isLocked, + Boolean isFeatured, + Boolean isFeaturedInCommunity, + CommunityResponse community, + PersonResponse creator, + PostAggregateResponse postAggregate, + String activityPubId, + String publicKey, + String createdAt, + String updatedAt) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java new file mode 100644 index 00000000..da49d8e8 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -0,0 +1,60 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.services; + +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.IndexPost; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; +import com.sublinks.sublinksapi.community.entities.Community; +import com.sublinks.sublinksapi.community.repositories.CommunityRepository; +import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.post.entities.Post; +import com.sublinks.sublinksapi.post.models.PostSearchCriteria; +import com.sublinks.sublinksapi.post.repositories.PostRepository; +import com.sublinks.sublinksapi.post.services.PostService; +import java.util.List; +import java.util.Optional; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.ConversionService; +import org.springframework.stereotype.Service; + +@AllArgsConstructor +@Service +public class SublinksPostService { + + private final PostService postService; + private final PostRepository postRepository; + private final ConversionService conversionService; + private final CommunityRepository communityRepository; + + /** + * Retrieves a list of PostResponse objects based on the search criteria provided. + * + * @param indexPostForm The search criteria for retrieving posts. Must not be null. + * @param person An optional Person object representing the requesting user. May be empty. + * @return A list of PostResponse objects matching the search criteria. + */ + public List index(final IndexPost indexPostForm, final Optional person) { + + final List communities = indexPostForm.communityKeys() == null ? null + : communityRepository.findCommunityByTitleSlugIn(indexPostForm.communityKeys()); + + List posts = postRepository.allPostsBySearchCriteria(PostSearchCriteria.builder() + .search(indexPostForm.search()) + .sortType(conversionService.convert(indexPostForm.sortType(), + com.sublinks.sublinksapi.person.enums.SortType.class)) + .listingType(conversionService.convert(indexPostForm.listingType(), + com.sublinks.sublinksapi.person.enums.ListingType.class)) + .communityIds(communities == null ? null : communities.stream() + .map(Community::getId) + .toList()) + .isShowNsfw(indexPostForm.showNsfw()) + .isSavedOnly(indexPostForm.savedOnly()) + .perPage(indexPostForm.perPage()) + .page(indexPostForm.page()) + .person(person.orElse(null)) + .build()); + + return posts.stream() + .map(post -> conversionService.convert(post, PostResponse.class)) + .toList(); + } + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java index 01ab2dca..e4932aec 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java @@ -16,7 +16,8 @@ @RequestMapping("api/v1/privatemessage") @Tag(name = "Privatemessage", description = "Privatemessage API") public class SublinksPrivatemessageController extends AbstractSublinksApiController { - @Operation(summary = "Get a list of privatemessages") + + @Operation(summary = "Get a list of privatemessages") @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/mappers/SublinksPrivateMessageMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/mappers/SublinksPrivateMessageMapper.java index 3f5ca299..f20944bc 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/mappers/SublinksPrivateMessageMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/mappers/SublinksPrivateMessageMapper.java @@ -10,7 +10,8 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksPersonMapper.class}) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, + uses = {SublinksPersonMapper.class}) public abstract class SublinksPrivateMessageMapper implements Converter { @@ -26,7 +27,11 @@ public abstract class SublinksPrivateMessageMapper implements //@Mapping(target = "recipient", expression = "java(personMapper.convert(privateMessage.getRecipient()))") @Mapping(target = "sender", source = "privateMessage.sender") @Mapping(target = "recipient", source = "privateMessage.recipient") - @Mapping(target = "createdAt", source = "privateMessage.createdAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) - @Mapping(target = "updatedAt", source = "privateMessage.updatedAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "createdAt", + source = "privateMessage.createdAt", + dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "updatedAt", + source = "privateMessage.updatedAt", + dateFormat = DateUtils.FRONT_END_DATE_FORMAT) public abstract PrivateMessageResponse convert(@Nullable PrivateMessage privateMessage); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/PrivateMessageResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/PrivateMessageResponse.java index 3f55e280..a0e8c98a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/PrivateMessageResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/PrivateMessageResponse.java @@ -4,15 +4,16 @@ import lombok.Builder; @Builder -public record PrivateMessageResponse(String key, - PersonResponse sender, - PersonResponse recipient, - String content, - Boolean isLocal, - Boolean isDeleted, - Boolean isRead, - String activityPubId, - String createdAt, - String updatedAt) { +public record PrivateMessageResponse( + String key, + PersonResponse sender, + PersonResponse recipient, + String content, + Boolean isLocal, + Boolean isDeleted, + Boolean isRead, + String activityPubId, + String createdAt, + String updatedAt) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java index 97acddd1..5a785333 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java @@ -5,13 +5,19 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import org.springframework.web.bind.annotation.*; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("api/v1/roles") @Tag(name = "Roles", description = "Roles API") public class SublinksRolesController extends AbstractSublinksApiController { - @Operation(summary = "Get a list of roles") + + @Operation(summary = "Get a list of roles") @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java index 6e5f2c8e..09c99244 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java @@ -22,9 +22,15 @@ public abstract class SublinksRoleMapper implements @Mapping(target = "description", source = "role.description") @Mapping(target = "isActive", source = "role.active") @Mapping(target = "isExpired", source = "role", qualifiedByName = "is_expired") - @Mapping(target = "expiresAt", source = "role.expiresAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) - @Mapping(target = "createdAt", source = "role.createdAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) - @Mapping(target = "updatedAt", source = "role.updatedAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "expiresAt", + source = "role.expiresAt", + dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "createdAt", + source = "role.createdAt", + dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "updatedAt", + source = "role.updatedAt", + dateFormat = DateUtils.FRONT_END_DATE_FORMAT) public abstract com.sublinks.sublinksapi.api.sublinks.v1.roles.models.Role convert( @Nullable Role role); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/Role.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/Role.java index 79e25ccf..5d3db75c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/Role.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/Role.java @@ -3,13 +3,14 @@ import lombok.Builder; @Builder -public record Role(String key, - String name, - String description, - Boolean isActive, - Boolean isExpired, - String expiresAt, - String createdAt, - String updatedAt) { +public record Role( + String key, + String name, + String description, + Boolean isActive, + Boolean isExpired, + String expiresAt, + String createdAt, + String updatedAt) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/SearchResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/SearchResponse.java index 3c9ecfab..573bbf37 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/SearchResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/SearchResponse.java @@ -3,8 +3,8 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; -import java.util.List; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; +import java.util.List; import lombok.Builder; // @todo: Add Communities, Posts, Comments, and Messages diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/PaginationControllerUtils.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/PaginationControllerUtils.java index 09b8eede..32e4b169 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/PaginationControllerUtils.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/PaginationControllerUtils.java @@ -5,7 +5,8 @@ public class PaginationControllerUtils { public static int getAbsoluteMinNumber(@Nullable Integer number, - @Nullable Integer defaultNumber) { + @Nullable Integer defaultNumber) + { if (number == null && defaultNumber == null) { return 1; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/mappers/OptionalStringMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/mappers/OptionalStringMapper.java index 9f3192c9..46e5b3ac 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/mappers/OptionalStringMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/mappers/OptionalStringMapper.java @@ -7,7 +7,8 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksLanguageMapper.class}) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, + uses = {SublinksLanguageMapper.class}) public class OptionalStringMapper implements Converter> { @Nullable diff --git a/src/main/java/com/sublinks/sublinksapi/comment/models/CommentSearchCriteria.java b/src/main/java/com/sublinks/sublinksapi/comment/models/CommentSearchCriteria.java index e8dca405..3ea43307 100644 --- a/src/main/java/com/sublinks/sublinksapi/comment/models/CommentSearchCriteria.java +++ b/src/main/java/com/sublinks/sublinksapi/comment/models/CommentSearchCriteria.java @@ -9,14 +9,16 @@ import lombok.Builder; @Builder -public record CommentSearchCriteria(ListingType listingType, - CommentSortType commentSortType, - Integer perPage, - Integer page, - Community community, - Post post, - Comment parent, - Boolean savedOnly, - Person person) { +public record CommentSearchCriteria( + String search, + ListingType listingType, + CommentSortType commentSortType, + Integer perPage, + Integer page, + Community community, + Post post, + Comment parent, + Boolean savedOnly, + Person person) { } diff --git a/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentRepositoryImpl.java b/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentRepositoryImpl.java index fa50a263..b0964ad3 100644 --- a/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentRepositoryImpl.java +++ b/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentRepositoryImpl.java @@ -51,6 +51,15 @@ public List allCommentsBySearchCriteria(CommentSearchCriteria commentSe predicates.add(cb.equal(commentTable.get("community"), commentSearchCriteria.community())); } + if (commentSearchCriteria.search() != null && !commentSearchCriteria.search() + .isEmpty()) { + Expression searchVector = commentTable.get("search_vector"); + Predicate searchPredicate = cb.equal(cb.function("@@", Boolean.class, searchVector, + cb.function("to_tsquery", String.class, cb.literal(commentSearchCriteria.search()))), + true); + predicates.add(searchPredicate); + } + cq.where(predicates.toArray(new Predicate[0])); // @todo determine hot / top / (controversial) @@ -72,14 +81,15 @@ public List allCommentsBySearchCriteria(CommentSearchCriteria commentSe final TypedQuery query = em.createQuery(cq); - applyPagination(query, commentSearchCriteria.page(), perPage); + applyPagination(query, page, perPage); return query.getResultList(); } @Override public List allCommentsByCommunityAndPersonAndRemoved(Community community, Person person, - @Nullable List removedStates) { + @Nullable List removedStates) + { if (community == null || person == null) { throw new IllegalArgumentException("Community and person must be provided"); @@ -103,12 +113,14 @@ public List allCommentsByCommunityAndPersonAndRemoved(Community communi cq.where(predicates.toArray(new Predicate[0])); - return em.createQuery(cq).getResultList(); + return em.createQuery(cq) + .getResultList(); } @Override public List allCommentsByPersonAndRemoved(Person person, - @Nullable List removedStates) { + @Nullable List removedStates) + { if (person == null) { throw new IllegalArgumentException("Person must be provided"); @@ -130,6 +142,7 @@ public List allCommentsByPersonAndRemoved(Person person, cq.where(predicates.toArray(new Predicate[0])); - return em.createQuery(cq).getResultList(); + return em.createQuery(cq) + .getResultList(); } } diff --git a/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepository.java b/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepository.java index d5819e20..9f023b57 100644 --- a/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepository.java @@ -15,6 +15,8 @@ public interface CommunityRepository extends JpaRepository, Optional findCommunityByTitleSlug(String titleSlug); + List findCommunityByTitleSlugIn(List titleSlug); + boolean existsByTitleSlug(String titleSlug); List findCommunitiesByInstance(Instance instance); diff --git a/src/main/java/com/sublinks/sublinksapi/post/models/PostSearchCriteria.java b/src/main/java/com/sublinks/sublinksapi/post/models/PostSearchCriteria.java index a3f382c3..b9eeffa2 100644 --- a/src/main/java/com/sublinks/sublinksapi/post/models/PostSearchCriteria.java +++ b/src/main/java/com/sublinks/sublinksapi/post/models/PostSearchCriteria.java @@ -8,6 +8,7 @@ @Builder public record PostSearchCriteria( + String search, SortType sortType, ListingType listingType, int perPage, @@ -15,9 +16,9 @@ public record PostSearchCriteria( String cursorBasedPageable, List communityIds, Person person, - boolean isSavedOnly, - boolean isLikedOnly, - boolean isDislikedOnly -) { + Boolean isShowNsfw, + Boolean isSavedOnly, + Boolean isLikedOnly, + Boolean isDislikedOnly) { } diff --git a/src/main/java/com/sublinks/sublinksapi/post/repositories/PostRepositoryImpl.java b/src/main/java/com/sublinks/sublinksapi/post/repositories/PostRepositoryImpl.java index 1e2cd92c..22aa560d 100644 --- a/src/main/java/com/sublinks/sublinksapi/post/repositories/PostRepositoryImpl.java +++ b/src/main/java/com/sublinks/sublinksapi/post/repositories/PostRepositoryImpl.java @@ -1,6 +1,7 @@ package com.sublinks.sublinksapi.post.repositories; import com.sublinks.sublinksapi.community.entities.Community; +import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; import com.sublinks.sublinksapi.person.entities.LinkPersonPost; import com.sublinks.sublinksapi.person.entities.Person; import com.sublinks.sublinksapi.person.enums.LinkPersonPostType; @@ -29,24 +30,22 @@ public class PostRepositoryImpl implements PostRepositorySearch { private final EntityManager em; private final PostSearchQueryService postSearchQueryService; + private final LocalInstanceContext localInstanceContext; @Override public List allPostsBySearchCriteria(final PostSearchCriteria postSearchCriteria) { final Builder searchBuilder = postSearchQueryService.builder(); if (postSearchCriteria.person() != null) { - searchBuilder - .setPerson(postSearchCriteria.person()) + searchBuilder.setPerson(postSearchCriteria.person()) .addPersonLikesToPost(); } if (postSearchCriteria.communityIds() != null) { - searchBuilder - .filterByCommunities(postSearchCriteria.communityIds()); + searchBuilder.filterByCommunities(postSearchCriteria.communityIds()); } if (postSearchCriteria.person() != null && postSearchCriteria.listingType() == ListingType.Subscribed) { - searchBuilder - .filterByListingType(postSearchCriteria.listingType()); + searchBuilder.filterByListingType(postSearchCriteria.listingType()); } if (postSearchCriteria.cursorBasedPageable() != null) { searchBuilder.setCursor(postSearchCriteria.cursorBasedPageable()); @@ -55,6 +54,21 @@ public List allPostsBySearchCriteria(final PostSearchCriteria postSearchCr searchBuilder.setSortType(postSearchCriteria.sortType()); } + if (localInstanceContext.instance() != null && !localInstanceContext.instance() + .getInstanceConfig() + .isEnableNsfw()) { + searchBuilder.setShowNsfw(false); + } else if (postSearchCriteria.isShowNsfw() != null) { + searchBuilder.setShowNsfw(postSearchCriteria.isShowNsfw()); + } else if (postSearchCriteria.person() != null) { + searchBuilder.setShowNsfw(postSearchCriteria.person() + .isShowNsfw()); + } else { + searchBuilder.setShowNsfw(false); + } + + searchBuilder.addSearch(postSearchCriteria.search()); + Results results = postSearchQueryService.results(searchBuilder); results.setPerPage(postSearchCriteria.perPage()); if (postSearchCriteria.cursorBasedPageable() != null) { @@ -66,7 +80,8 @@ public List allPostsBySearchCriteria(final PostSearchCriteria postSearchCr @Override public List allPostsByCommunityAndPersonAndRemoved(Community community, Person person, - List removedStates) { + List removedStates) + { if (community == null || person == null) { throw new IllegalArgumentException("Community and person cannot be null"); @@ -93,12 +108,14 @@ public List allPostsByCommunityAndPersonAndRemoved(Community community, Pe cq.where(predicates.toArray(new Predicate[0])); - return em.createQuery(cq).getResultList(); + return em.createQuery(cq) + .getResultList(); } @Override public List allPostsByPersonAndRemoved(@Nullable Person person, - @Nullable List removedStates) { + @Nullable List removedStates) + { final CriteriaBuilder cb = em.getCriteriaBuilder(); final CriteriaQuery cq = cb.createQuery(Post.class); @@ -122,6 +139,7 @@ public List allPostsByPersonAndRemoved(@Nullable Person person, cq.where(predicates.toArray(new Predicate[0])); - return em.createQuery(cq).getResultList(); + return em.createQuery(cq) + .getResultList(); } } diff --git a/src/main/java/com/sublinks/sublinksapi/post/services/PostSearchQueryService.java b/src/main/java/com/sublinks/sublinksapi/post/services/PostSearchQueryService.java index 1e542847..df0e1091 100644 --- a/src/main/java/com/sublinks/sublinksapi/post/services/PostSearchQueryService.java +++ b/src/main/java/com/sublinks/sublinksapi/post/services/PostSearchQueryService.java @@ -48,9 +48,8 @@ public static class Results { private final Builder builder; private int perPage = 20; - public Results( - Builder builder - ) { + public Results(Builder builder) + { this.builder = builder; } @@ -73,14 +72,16 @@ private TypedQuery getQuery() { if (builder.getSorter() != null) { builder.getSorter() .applySorting(builder); - if (builder.getCursor() != null && !builder.getCursor().isBlank()) { + if (builder.getCursor() != null && !builder.getCursor() + .isBlank()) { builder.getSorter() .applyCursor(builder); } } builder.getCriteriaQuery() - .where(builder.getPredicates().toArray(new Predicate[0])); + .where(builder.getPredicates() + .toArray(new Predicate[0])); return builder.getEntityManager() .createQuery(builder.getCriteriaQuery()); @@ -118,11 +119,9 @@ public static class Builder { private String cursor; private SortingTypeInterface sorter; - public Builder( - EntityManager entityManager, - CriteriaBuilder criteriaBuilder, - SortFactory sortFactory - ) { + public Builder(EntityManager entityManager, CriteriaBuilder criteriaBuilder, + SortFactory sortFactory) + { this.entityManager = entityManager; this.criteriaBuilder = criteriaBuilder; @@ -192,7 +191,8 @@ public Builder setSortType(final SortType sortType) { public Builder setCursor(final String cursor) { - byte[] decodedBytes = Base64.getDecoder().decode(cursor); + byte[] decodedBytes = Base64.getDecoder() + .decode(cursor); this.cursor = new String(decodedBytes); return this; } @@ -202,5 +202,27 @@ public Builder setPerson(Person person) { this.person = person; return this; } + + public Builder setShowNsfw(Boolean isShowNsfw) { + + if (!isShowNsfw) { + predicates.add(criteriaBuilder.isFalse(postTable.get("isNsfw"))); + } + return this; + } + + public Builder addSearch(String search) { + + if (search != null && !search.isBlank()) { + + Expression searchVector = this.postTable.get("search_vector"); + Predicate searchPredicate = this.criteriaBuilder.equal( + this.criteriaBuilder.function("@@", Boolean.class, searchVector, + this.criteriaBuilder.function("to_tsquery", String.class, + this.criteriaBuilder.literal(search))), true); + predicates.add(searchPredicate); + } + return this; + } } } From cf8726af4b3bb950d6fbb580981fe5e711150a81 Mon Sep 17 00:00:00 2001 From: rooki Date: Sun, 2 Jun 2024 22:43:22 +0200 Subject: [PATCH 050/115] Optimize search functionality and improve code structure Refactored code for increased readability, and added new methods for NSFW post filtering and search functionality in PostSearchQueryService.java. Enhanced overall code structuring across various files, and incorporated search function parameters within IndexCommunity.java. Signed-off-by: rooki --- .../v1/comment/models/IndexComment.java | 11 +++++ .../v1/community/models/IndexCommunity.java | 11 +++++ .../services/SublinksCommunityService.java | 26 +++++------- .../SublinksInstanceAggregateMapper.java | 2 +- .../mappers/SublinksInstanceConfigMapper.java | 2 +- .../person/mappers/SublinksPersonMapper.java | 11 +++-- .../controllers/SublinksPostController.java | 42 +++++++++++++------ .../mappers/SublinksLinkMetaDataMapper.java | 2 +- .../SublinksPostAggregationMapper.java | 8 ++-- .../v1/post/mappers/SublinksPostMapper.java | 28 +++++++------ .../sublinks/v1/post/models/IndexPost.java | 14 +++++++ .../sublinks/v1/post/models/LinkMetaData.java | 1 + .../v1/post/models/PostAggregateResponse.java | 4 +- .../sublinks/v1/post/models/PostResponse.java | 1 - .../v1/post/services/SublinksPostService.java | 12 +++--- .../controllers/SublinksSearchController.java | 5 +-- .../api/sublinks/v1/search/models/Search.java | 4 +- .../services/SublinksSearchService.java | 39 +++++++++++++---- .../sublinksapi/comment/entities/Comment.java | 5 ++- .../repositories/CommentRepositoryImpl.java | 8 ++-- .../models/CommunitySearchCriteria.java | 6 +-- .../repositories/CommunityRepositoryImpl.java | 10 ++++- .../sublinksapi/post/entities/Post.java | 7 +++- .../post/services/PostSearchQueryService.java | 9 ++-- ...20231003__Create_initial_entity_tables.sql | 12 ++++++ 25 files changed, 188 insertions(+), 92 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java index 2738165d..a06024c6 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java @@ -20,4 +20,15 @@ public record IndexComment( @Schema(description = "Per page", requiredMode = RequiredMode.NOT_REQUIRED) Integer perPage, @Schema(description = "Page", requiredMode = RequiredMode.NOT_REQUIRED) Integer page) { + @Override + public Integer perPage() { + + return perPage != null ? perPage : 20; + } + + @Override + public Integer page() { + + return page != null ? page : 1; + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java index f4469a85..acbb23cf 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java @@ -25,4 +25,15 @@ public record IndexCommunity( example = "1", requiredMode = RequiredMode.NOT_REQUIRED) Integer page) { + @Override + public Integer perPage() { + + return perPage != null ? perPage : 20; + } + + @Override + public Integer page() { + + return page != null ? page : 1; + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index f22c4f9a..904b92d7 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -47,17 +47,17 @@ public class SublinksCommunityService { /** * Retrieves a list of CommunityResponse objects based on the search criteria provided. * - * @param indexCommunityParam The search criteria for retrieving community responses. - * @param person The person requesting the community responses. + * @param indexCommunityForm The search criteria for retrieving community responses. + * @param person The person requesting the community responses. * @return The list of CommunityResponse objects that match the search criteria. */ - public List index(IndexCommunity indexCommunityParam, Person person) { + public List index(IndexCommunity indexCommunityForm, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.READ_COMMUNITY, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_read_community")); - com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType sortType = indexCommunityParam.sortType(); + com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType sortType = indexCommunityForm.sortType(); if (sortType == null) { if (person != null && person.getDefaultSortType() != null) { @@ -68,7 +68,7 @@ public List index(IndexCommunity indexCommunityParam, Person } } - SublinksListingType sublinksListingType = indexCommunityParam.listingType(); + SublinksListingType sublinksListingType = indexCommunityForm.listingType(); if (sublinksListingType == null) { if (person != null && person.getDefaultListingType() != null) { @@ -86,24 +86,18 @@ public List index(IndexCommunity indexCommunityParam, Person } boolean showNsfw = - (indexCommunityParam.showNsfw() != null && indexCommunityParam.showNsfw()) || ( - person != null && person.isShowNsfw()); + (indexCommunityForm.showNsfw() != null && indexCommunityForm.showNsfw()) || (person != null + && person.isShowNsfw()); final CommunitySearchCriteria.CommunitySearchCriteriaBuilder criteria = CommunitySearchCriteria.builder() - .perPage(indexCommunityParam.perPage()) - .page(indexCommunityParam.page()) + .perPage(indexCommunityForm.perPage()) + .page(indexCommunityForm.page()) .sortType(conversionService.convert(sortType, SortType.class)) .listingType(conversionService.convert(sublinksListingType, ListingType.class)) .showNsfw(showNsfw) + .search(indexCommunityForm.search()) .person(person); - if (indexCommunityParam.perPage() == 0) { - criteria.perPage(20); - } - if (indexCommunityParam.page() == 0) { - criteria.page(1); - } - final CommunitySearchCriteria communitySearchCriteria = criteria.build(); return communityRepository.allCommunitiesBySearchCriteria(communitySearchCriteria) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceAggregateMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceAggregateMapper.java index c7b35045..58392f94 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceAggregateMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceAggregateMapper.java @@ -22,6 +22,6 @@ public abstract class SublinksInstanceAggregateMapper implements @Mapping(target = "activeWeeklyUserCount", source = "instanceAggregate.activeWeeklyUserCount") @Mapping(target = "activeMonthlyUserCount", source = "instanceAggregate.activeMonthlyUserCount") @Mapping(target = "activeHalfYearlyUserCount", - source = "instanceAggregate.activeHalfYearlyUserCount") + source = "instanceAggregate.activeHalfYearUserCount") public abstract InstanceAggregateResponse convert(@Nullable InstanceAggregate instanceAggregate); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceConfigMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceConfigMapper.java index f832d85c..969c002a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceConfigMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/mappers/SublinksInstanceConfigMapper.java @@ -29,7 +29,7 @@ public abstract class SublinksInstanceConfigMapper implements @Mapping(target = "federationEnabled", source = "instanceConfig.federationEnabled") @Mapping(target = "captchaEnabled", source = "instanceConfig.captchaEnabled") @Mapping(target = "captchaDifficulty", source = "instanceConfig.captchaDifficulty") - @Mapping(target = "nameMaxLength", source = "instanceConfig.nameMaxLength") + @Mapping(target = "nameMaxLength", source = "instanceConfig.actorNameMaxLength") @Mapping(target = "defaultTheme", source = "instanceConfig.defaultTheme") @Mapping(target = "defaultPostListingType", source = "instanceConfig.defaultPostListingType") @Mapping(target = "legalInformation", source = "instanceConfig.legalInformation") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java index 408769d9..23b934fc 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java @@ -6,7 +6,6 @@ import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.person.entities.Person; import java.text.SimpleDateFormat; -import java.util.Optional; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; @@ -24,7 +23,7 @@ public abstract class SublinksPersonMapper implements Converter mapBanExpiresAt(Person person) { + @Named("ban_expires_at") + String mapBanExpiresAt(Person person) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(); simpleDateFormat.applyPattern(DateUtils.FRONT_END_DATE_FORMAT); - return Optional.ofNullable(person.getRole() + return person.getRole() .getExpiresAt() != null ? simpleDateFormat.format(person.getRole() - .getExpiresAt()) : null); + .getExpiresAt()) : null; } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java index 540487d2..7d6562c0 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java @@ -1,10 +1,17 @@ package com.sublinks.sublinksapi.api.sublinks.v1.post.controllers; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.IndexPost; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.post.services.SublinksPostService; +import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import java.util.Optional; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -17,21 +24,33 @@ @Tag(name = "Post", description = "Post API") public class SublinksPostController extends AbstractSublinksApiController { + private final SublinksPostService sublinksPostService; + + public SublinksPostController(SublinksPostService sublinksPostService) { + + super(); + this.sublinksPostService = sublinksPostService; + } + @Operation(summary = "Get a list of posts") @GetMapping @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void index() { - // TODO: implement - // @todo: implement cursor based pagination + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List index( + final Optional indexPost, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Optional person = getOptionalPerson(sublinksJwtPerson); + + return sublinksPostService.index(indexPost.orElse(IndexPost.builder() + .build()), person.orElse(null)); } @Operation(summary = "Get a specific post") @GetMapping("/{id}") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public void show(@PathVariable String id) { // TODO: implement } @@ -39,8 +58,7 @@ public void show(@PathVariable String id) { @Operation(summary = "Create a new post") @PostMapping @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public void create() { // TODO: implement } @@ -48,8 +66,7 @@ public void create() { @Operation(summary = "Update an post") @PostMapping("/{id}") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public void update(@PathVariable String id) { // TODO: implement } @@ -57,8 +74,7 @@ public void update(@PathVariable String id) { @Operation(summary = "Delete an post") @DeleteMapping("/{id}") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public void delete(@PathVariable String id) { // TODO: implement } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksLinkMetaDataMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksLinkMetaDataMapper.java index 672acbe5..6a8d13c0 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksLinkMetaDataMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksLinkMetaDataMapper.java @@ -12,7 +12,7 @@ public abstract class SublinksLinkMetaDataMapper implements Converter { @Override - @Mapping(target = "key", source = "post.linkUrl") + @Mapping(target = "postKey", source = "post.titleSlug") @Mapping(target = "linkUrl", source = "post.linkUrl") @Mapping(target = "linkTitle", source = "post.linkTitle") @Mapping(target = "linkDescription", source = "post.linkDescription") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostAggregationMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostAggregationMapper.java index 996f998b..bd5e0351 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostAggregationMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostAggregationMapper.java @@ -15,11 +15,11 @@ public abstract class SublinksPostAggregationMapper implements @Override @Mapping(target = "key", source = "postAggregate.id") @Mapping(target = "commentCount", source = "postAggregate.commentCount") - @Mapping(target = "downvoteCount", source = "postAggregate.downvoteCount") - @Mapping(target = "upvoteCount", source = "postAggregate.upvoteCount") + @Mapping(target = "downvoteCount", source = "postAggregate.downVoteCount") + @Mapping(target = "upvoteCount", source = "postAggregate.upVoteCount") @Mapping(target = "score", source = "postAggregate.score") - @Mapping(target = "hotScore", source = "postAggregate.hotRank") - @Mapping(target = "controversyScore", source = "postAggregate.controversyScore") + @Mapping(target = "hotRank", source = "postAggregate.hotRank") + @Mapping(target = "controversyRank", source = "postAggregate.controversyRank") public abstract PostAggregateResponse convert(@Nullable PostAggregate postAggregate); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostMapper.java index 2392ef25..4a9cbeb8 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostMapper.java @@ -10,19 +10,20 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; -import org.mapstruct.Named; +import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = { - SublinksLinkMetaDataMapper.class, SublinksPostAggregationMapper.class, - SublinksPersonMapper.class, SublinksCommunityMapper.class}) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, + uses = {SublinksLinkMetaDataMapper.class, SublinksPostAggregationMapper.class, + SublinksPersonMapper.class, SublinksCommunityMapper.class, ConversionService.class}) public abstract class SublinksPostMapper implements Converter { - SublinksPostAggregationMapper sublinksPostAggregationMapper; - SublinksLinkMetaDataMapper sublinksLinkMetaDataMapper; - SublinksPersonMapper sublinksPersonMapper; - SublinksCommunityMapper sublinksCommunityMapper; + protected SublinksPostAggregationMapper sublinksPostAggregationMapper; + protected SublinksLinkMetaDataMapper sublinksLinkMetaDataMapper; + protected SublinksPersonMapper sublinksPersonMapper; + protected SublinksCommunityMapper sublinksCommunityMapper; + protected ConversionService conversionService; @Override @Mapping(target = "key", source = "post.titleSlug") @@ -37,7 +38,9 @@ public abstract class SublinksPostMapper implements Converter communityKeys, @Schema(description = "Show NSFW", requiredMode = RequiredMode.NOT_REQUIRED) Boolean showNsfw, @Schema(description = "Saved only", requiredMode = RequiredMode.NOT_REQUIRED) Boolean savedOnly, + String pageCursor, @Schema(description = "Per page", requiredMode = RequiredMode.NOT_REQUIRED) Integer perPage, @Schema(description = "Page", requiredMode = RequiredMode.NOT_REQUIRED) Integer page) { + @Override + public Integer perPage() { + + return perPage != null ? perPage : 20; + } + + @Override + public Integer page() { + + return page != null ? page : 1; + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/LinkMetaData.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/LinkMetaData.java index 1d95436b..1019abbf 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/LinkMetaData.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/LinkMetaData.java @@ -4,6 +4,7 @@ @Builder public record LinkMetaData( + String postKey, String linkUrl, String linkTitle, String linkDescription, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostAggregateResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostAggregateResponse.java index 1d44b383..04788698 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostAggregateResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostAggregateResponse.java @@ -6,7 +6,7 @@ public record PostAggregateResponse( String downvoteCount, String upvoteCount, String score, - String hotScore, - String controversyScore) { + String hotRank, + String controversyRank) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostResponse.java index 99406553..eac293c9 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostResponse.java @@ -9,7 +9,6 @@ public record PostResponse( String titleSlug, String body, LinkMetaData linkMetaData, - String type, Boolean isLocal, Boolean isDeleted, Boolean isRemoved, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index da49d8e8..af662a76 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -10,7 +10,6 @@ import com.sublinks.sublinksapi.post.repositories.PostRepository; import com.sublinks.sublinksapi.post.services.PostService; import java.util.List; -import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; import org.springframework.stereotype.Service; @@ -25,13 +24,13 @@ public class SublinksPostService { private final CommunityRepository communityRepository; /** - * Retrieves a list of PostResponse objects based on the search criteria provided. + * Retrieves a list of PostResponse objects based on the provided search criteria. * - * @param indexPostForm The search criteria for retrieving posts. Must not be null. - * @param person An optional Person object representing the requesting user. May be empty. + * @param indexPostForm The IndexPost object containing the search criteria. + * @param person The Person object representing the user. * @return A list of PostResponse objects matching the search criteria. */ - public List index(final IndexPost indexPostForm, final Optional person) { + public List index(final IndexPost indexPostForm, final Person person) { final List communities = indexPostForm.communityKeys() == null ? null : communityRepository.findCommunityByTitleSlugIn(indexPostForm.communityKeys()); @@ -49,7 +48,8 @@ public List index(final IndexPost indexPostForm, final Optional person = getOptionalPerson(principal); - final SearchResponse.SearchResponseBuilder searchResponseBuilder = SearchResponse.builder(); - + return sublinksSearchService.list(search, person); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/Search.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/Search.java index 9fe872b9..65458e19 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/Search.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/Search.java @@ -4,13 +4,13 @@ import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import lombok.Builder; -// @todo: Add Communities, Posts, Comments, and Messages @Builder public record Search( String search, SortType type, - SublinksListingType sublinksListingType, + SublinksListingType listingType, Boolean showNsfw, + Boolean savedOnly, Integer perPage, Integer page) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java index 6a3dfc02..f1de81cf 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java @@ -6,6 +6,8 @@ import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.IndexPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.IndexPost; +import com.sublinks.sublinksapi.api.sublinks.v1.post.services.SublinksPostService; import com.sublinks.sublinksapi.api.sublinks.v1.search.models.Search; import com.sublinks.sublinksapi.api.sublinks.v1.search.models.SearchResponse; import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommentTypes; @@ -14,6 +16,7 @@ import com.sublinks.sublinksapi.authorization.enums.RolePermissionPostTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.utils.PaginationUtils; import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.http.HttpStatus; @@ -29,7 +32,15 @@ public class SublinksSearchService { private final SublinksPersonService sublinksPersonService; private final SublinksCommentService sublinksCommentService; private final SublinksCommunityService sublinksCommunityService; - + private final SublinksPostService sublinksPostService; + + /** + * Perform a search and return the search result. + * + * @param searchForm the search form containing search parameters + * @param person an optional person object representing the user performing the search + * @return the search response containing the search results + */ public SearchResponse list(final Search searchForm, final Optional person) { rolePermissionService.isPermitted(person.orElse(null), @@ -38,8 +49,9 @@ public SearchResponse list(final Search searchForm, final Optional perso final String search = searchForm.search(); - final Integer page = searchForm.page() == null ? 1 : searchForm.page(); - final Integer perPage = searchForm.perPage() == null ? 20 : searchForm.perPage(); + final Integer page = searchForm.page() == null ? 1 : Math.max(searchForm.page(), 1); + final Integer perPage = searchForm.perPage() == null ? 20 : PaginationUtils.Clamp( + searchForm.perPage(), 1, 20); final SearchResponse.SearchResponseBuilder searchResponseBuilder = SearchResponse.builder(); @@ -50,18 +62,31 @@ public SearchResponse list(final Search searchForm, final Optional perso .search(search) .page(page) .perPage(perPage) - .sublinksListingType(searchForm.sublinksListingType()) + .sublinksListingType(searchForm.listingType()) .sortType(searchForm.type()) .build())); } - // @todo: posts + if (rolePermissionService.isPermitted(person.orElse(null), + RolePermissionPostTypes.READ_POSTS)) { + + searchResponseBuilder.posts(sublinksPostService.index(IndexPost.builder() + .search(search) + .sortType(searchForm.type()) + .listingType(searchForm.listingType()) + .showNsfw(searchForm.showNsfw()) + .savedOnly(searchForm.savedOnly()) + .perPage(perPage) + .page(page) + .build(), person.orElse(null))); + } + if (rolePermissionService.isPermitted(person.orElse(null), RolePermissionCommentTypes.READ_COMMENTS)) { searchResponseBuilder.comments(sublinksCommentService.index(IndexComment.builder() .search(search) - .sublinksListingType(searchForm.sublinksListingType()) + .listingType(searchForm.listingType()) .showNsfw(searchForm.showNsfw()) .page(searchForm.page()) .perPage(searchForm.perPage()) @@ -75,7 +100,7 @@ public SearchResponse list(final Search searchForm, final Optional perso .search(search) .page(page) .perPage(perPage) - .sublinksListingType(searchForm.sublinksListingType()) + .listingType(searchForm.listingType()) .showNsfw(searchForm.showNsfw()) .sortType(searchForm.type()) .build(), person.orElse(null))); diff --git a/src/main/java/com/sublinks/sublinksapi/comment/entities/Comment.java b/src/main/java/com/sublinks/sublinksapi/comment/entities/Comment.java index 2b566594..6ceb8b32 100644 --- a/src/main/java/com/sublinks/sublinksapi/comment/entities/Comment.java +++ b/src/main/java/com/sublinks/sublinksapi/comment/entities/Comment.java @@ -106,11 +106,14 @@ public class Comment implements Serializable, AclEntityInterface { @Column(nullable = false) private String path; + @Column(nullable = false, name = "search_vector") + private String searchVector; + @CreationTimestamp(source = SourceType.DB) @Column(updatable = false, nullable = false, name = "created_at") private Date createdAt; - + @UpdateTimestamp(source = SourceType.DB) @Column(updatable = false, name = "updated_at") private Date updatedAt; diff --git a/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentRepositoryImpl.java b/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentRepositoryImpl.java index b0964ad3..8b62680d 100644 --- a/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentRepositoryImpl.java +++ b/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentRepositoryImpl.java @@ -53,11 +53,9 @@ public List allCommentsBySearchCriteria(CommentSearchCriteria commentSe if (commentSearchCriteria.search() != null && !commentSearchCriteria.search() .isEmpty()) { - Expression searchVector = commentTable.get("search_vector"); - Predicate searchPredicate = cb.equal(cb.function("@@", Boolean.class, searchVector, - cb.function("to_tsquery", String.class, cb.literal(commentSearchCriteria.search()))), - true); - predicates.add(searchPredicate); + predicates.add(cb.equal( + cb.function("fn_search_vector_is_same", Boolean.class, + commentTable.get("searchVector"), cb.literal(commentSearchCriteria.search())), true)); } cq.where(predicates.toArray(new Predicate[0])); diff --git a/src/main/java/com/sublinks/sublinksapi/community/models/CommunitySearchCriteria.java b/src/main/java/com/sublinks/sublinksapi/community/models/CommunitySearchCriteria.java index 65eb6242..e20e6650 100644 --- a/src/main/java/com/sublinks/sublinksapi/community/models/CommunitySearchCriteria.java +++ b/src/main/java/com/sublinks/sublinksapi/community/models/CommunitySearchCriteria.java @@ -7,12 +7,12 @@ @Builder public record CommunitySearchCriteria( + String search, SortType sortType, ListingType listingType, int perPage, int page, - boolean showNsfw, - Person person -) { + Boolean showNsfw, + Person person) { } diff --git a/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepositoryImpl.java b/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepositoryImpl.java index 904bb7d7..1749c7af 100644 --- a/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepositoryImpl.java +++ b/src/main/java/com/sublinks/sublinksapi/community/repositories/CommunityRepositoryImpl.java @@ -22,7 +22,8 @@ public class CommunityRepositoryImpl implements CommunitySearchRepository { @Override public List allCommunitiesBySearchCriteria( - final CommunitySearchCriteria communitySearchCriteria) { + final CommunitySearchCriteria communitySearchCriteria) + { final CriteriaBuilder cb = em.getCriteriaBuilder(); final CriteriaQuery cq = cb.createQuery(Community.class); @@ -56,6 +57,13 @@ public List allCommunitiesBySearchCriteria( break; } + if (communitySearchCriteria.search() != null && !communitySearchCriteria.search() + .isEmpty()) { + predicates.add(cb.equal( + cb.function("fn_search_vector_is_same", Boolean.class, communityTable.get("searchVector"), + cb.literal(communitySearchCriteria.search())), true)); + } + cq.where(predicates.toArray(new Predicate[0])); switch (communitySearchCriteria.sortType()) { diff --git a/src/main/java/com/sublinks/sublinksapi/post/entities/Post.java b/src/main/java/com/sublinks/sublinksapi/post/entities/Post.java index 04a8c2b7..80c1530d 100644 --- a/src/main/java/com/sublinks/sublinksapi/post/entities/Post.java +++ b/src/main/java/com/sublinks/sublinksapi/post/entities/Post.java @@ -61,7 +61,9 @@ public class Post implements AclEntityInterface { Set linkPersonPost; @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; @ManyToOne(fetch = FetchType.EAGER) @@ -148,6 +150,9 @@ public class Post implements AclEntityInterface { @Column(nullable = true, name = "private_key") private String privateKey; + @Column(nullable = false, updatable = false, insertable = false, name = "search_vector") + private String searchVector; + @CreationTimestamp(source = SourceType.DB) @Column(updatable = false, nullable = false, name = "created_at") private Date createdAt; diff --git a/src/main/java/com/sublinks/sublinksapi/post/services/PostSearchQueryService.java b/src/main/java/com/sublinks/sublinksapi/post/services/PostSearchQueryService.java index df0e1091..2674b52e 100644 --- a/src/main/java/com/sublinks/sublinksapi/post/services/PostSearchQueryService.java +++ b/src/main/java/com/sublinks/sublinksapi/post/services/PostSearchQueryService.java @@ -215,12 +215,9 @@ public Builder addSearch(String search) { if (search != null && !search.isBlank()) { - Expression searchVector = this.postTable.get("search_vector"); - Predicate searchPredicate = this.criteriaBuilder.equal( - this.criteriaBuilder.function("@@", Boolean.class, searchVector, - this.criteriaBuilder.function("to_tsquery", String.class, - this.criteriaBuilder.literal(search))), true); - predicates.add(searchPredicate); + predicates.add(criteriaBuilder.equal( + criteriaBuilder.function("fn_search_vector_is_same", Boolean.class, + postTable.get("searchVector"), criteriaBuilder.literal(search)), true)); } return this; } 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 04a6c68f..f7b4be90 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 @@ -11,6 +11,18 @@ BEGIN END; $$ language 'plpgsql'; +/** + A that checks if a search_vector is the same as a text with the @@ operator + */ + +CREATE OR REPLACE FUNCTION fn_search_vector_is_same(search_vector TSVECTOR, text TEXT) + RETURNS BOOLEAN AS +$$ +BEGIN + RETURN search_vector @@ to_tsquery('english', text); +END; +$$ language 'plpgsql'; + /** * Comments table */ From ac6a1dd29beef63d3b2d249625c6c4bc3a3574d7 Mon Sep 17 00:00:00 2001 From: rooki Date: Mon, 3 Jun 2024 09:42:47 +0200 Subject: [PATCH 051/115] Refactor PostReport and PostLike repositories This commit refactors the PostReportRepositoryImpl and PostLikeRepositoryImpl classes, aiming to improve the code readability and maintainability. Changes include updating the method argument structure, introducing the use of the applyPagination utility function to manage pagination in queries, and reformatting the code for increased readability. Signed-off-by: rooki --- .../repositories/PostLikeRepositoryImpl.java | 8 +++--- .../PostReportRepositoryImpl.java | 27 +++++++++++-------- 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/post/repositories/PostLikeRepositoryImpl.java b/src/main/java/com/sublinks/sublinksapi/post/repositories/PostLikeRepositoryImpl.java index 5a1ced01..e71e51a1 100644 --- a/src/main/java/com/sublinks/sublinksapi/post/repositories/PostLikeRepositoryImpl.java +++ b/src/main/java/com/sublinks/sublinksapi/post/repositories/PostLikeRepositoryImpl.java @@ -19,8 +19,8 @@ public class PostLikeRepositoryImpl implements PostLikeRepositorySearch { private final EntityManager em; - public List allPostLikesBySearchCriteria( - PostLikeSearchCriteria postLikeSearchCriteria) { + public List allPostLikesBySearchCriteria(PostLikeSearchCriteria postLikeSearchCriteria) + { final CriteriaBuilder cb = em.getCriteriaBuilder(); final CriteriaQuery cq = cb.createQuery(PostLike.class); @@ -29,8 +29,8 @@ public List allPostLikesBySearchCriteria( final List predicates = new ArrayList<>(); - predicates.add( - cb.equal(commentLikeTable.get("post").get("id"), postLikeSearchCriteria.postId())); + predicates.add(cb.equal(commentLikeTable.get("post") + .get("id"), postLikeSearchCriteria.postId())); cq.where(predicates.toArray(new Predicate[0])); diff --git a/src/main/java/com/sublinks/sublinksapi/post/repositories/PostReportRepositoryImpl.java b/src/main/java/com/sublinks/sublinksapi/post/repositories/PostReportRepositoryImpl.java index 01c0b933..a120db71 100644 --- a/src/main/java/com/sublinks/sublinksapi/post/repositories/PostReportRepositoryImpl.java +++ b/src/main/java/com/sublinks/sublinksapi/post/repositories/PostReportRepositoryImpl.java @@ -1,5 +1,7 @@ package com.sublinks.sublinksapi.post.repositories; +import static com.sublinks.sublinksapi.utils.PaginationUtils.applyPagination; + import com.sublinks.sublinksapi.community.entities.Community; import com.sublinks.sublinksapi.person.entities.LinkPersonPost; import com.sublinks.sublinksapi.person.entities.Person; @@ -27,7 +29,8 @@ public class PostReportRepositoryImpl implements PostReportRepositorySearch { @Override public List allPostReportsBySearchCriteria( - PostReportSearchCriteria postReportSearchCriteria) { + PostReportSearchCriteria postReportSearchCriteria) + { final CriteriaBuilder cb = em.getCriteriaBuilder(); final CriteriaQuery cq = cb.createQuery(PostReport.class); @@ -44,10 +47,11 @@ public List allPostReportsBySearchCriteria( // Join PostG and check community id final Join postJoin = postTable.join("post", JoinType.LEFT); List communityPredicates = new ArrayList<>(); - postReportSearchCriteria.community().forEach(community -> { + postReportSearchCriteria.community() + .forEach(community -> { - communityPredicates.add(cb.equal(postJoin.get("community"), community)); - }); + communityPredicates.add(cb.equal(postJoin.get("community"), community)); + }); predicates.add(cb.or(communityPredicates.toArray(new Predicate[0]))); } @@ -56,18 +60,17 @@ public List allPostReportsBySearchCriteria( cq.orderBy(cb.desc(postTable.get("createdAt"))); int perPage = Math.min(Math.abs(postReportSearchCriteria.perPage()), 20); - int page = Math.max(postReportSearchCriteria.page() - 1, 0); TypedQuery query = em.createQuery(cq); - query.setMaxResults(perPage); - query.setFirstResult(page * perPage); + + applyPagination(query, postReportSearchCriteria.page(), perPage); return query.getResultList(); } @Override - public long countAllPostReportsByResolvedFalseAndCommunity( - @Nullable List communities) { + public long countAllPostReportsByResolvedFalseAndCommunity(@Nullable List communities) + { final CriteriaBuilder cb = em.getCriteriaBuilder(); final CriteriaQuery cq = cb.createQuery(Long.class); @@ -94,7 +97,8 @@ public long countAllPostReportsByResolvedFalseAndCommunity( cq.select(cb.count(postReportTable)); - return em.createQuery(cq).getSingleResult(); + return em.createQuery(cq) + .getSingleResult(); } @Override @@ -135,7 +139,8 @@ public void resolveAllPostReportsByPerson(Person person, Person resolver) { @Override public void resolveAllPostReportsByPersonAndCommunity(Person person, Community community, - Person resolver) { + Person resolver) + { final CriteriaBuilder cb = em.getCriteriaBuilder(); final CriteriaQuery cq = cb.createQuery(PostReport.class); From 1ee43535957907b6dd1142dcbe4956d1bb1b87ce Mon Sep 17 00:00:00 2001 From: rooki Date: Mon, 3 Jun 2024 11:22:53 +0200 Subject: [PATCH 052/115] Add show and create methods to SublinksPostService Several new methods and imports have been added to the SublinksPostService. The `show` method allows to find and display a specific post based on a key and a person, performing various checks to ensure post visibility. The `create` method executes complex functionality to create a new post. Furthermore, CreatePost model has also been updated with new parameters to support these changes. Signed-off-by: rooki --- .../sublinks/v1/post/models/CreatePost.java | 33 ++++++- .../v1/post/services/SublinksPostService.java | 86 ++++++++++++++++++- 2 files changed, 112 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/CreatePost.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/CreatePost.java index c097e0bb..01ab85b9 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/CreatePost.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/CreatePost.java @@ -10,14 +10,25 @@ public record CreatePost( String title, String body, - @Schema(description = "The language key of the comment", + @Schema(description = "The language key of the post", defaultValue = "und", example = "und", requiredMode = RequiredMode.NOT_REQUIRED) String languageKey, - @Schema(description = "Whether the comment is featured ( Requires permission to do so. )", + @Schema(description = "Whether the post is featured ( Requires permission to do so. )", defaultValue = "false", example = "false", - requiredMode = RequiredMode.NOT_REQUIRED) Boolean featured) { + requiredMode = RequiredMode.NOT_REQUIRED) Boolean featuredLocal, + @Schema(description = "Whether the post is featured in the community ( Requires permission to do so. )", + defaultValue = "false", + example = "false", + requiredMode = RequiredMode.NOT_REQUIRED) Boolean featuredCommunity, + + @Schema(description = "The community key of the post", + requiredMode = RequiredMode.NOT_REQUIRED) String communityKey, + @Schema(description = "Is the post nsfw", + defaultValue = "false", + example = "false", + requiredMode = RequiredMode.NOT_REQUIRED) Boolean nsfw) { public CreatePost { @@ -33,4 +44,20 @@ public String languageKey() { return languageKey == null ? "und" : languageKey; } + + public Boolean featuredLocal() { + + return featuredLocal != null && featuredLocal; + } + + public Boolean featuredCommunity() { + + return featuredCommunity != null && featuredCommunity; + } + + public Boolean nsfw() { + + return nsfw != null && nsfw; + } + } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index af662a76..4e7899fa 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -1,18 +1,30 @@ package com.sublinks.sublinksapi.api.sublinks.v1.post.services; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.CreatePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.IndexPost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionPostTypes; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.community.entities.Community; import com.sublinks.sublinksapi.community.repositories.CommunityRepository; +import com.sublinks.sublinksapi.language.entities.Language; +import com.sublinks.sublinksapi.language.repositories.LanguageRepository; import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.person.enums.LinkPersonCommunityType; +import com.sublinks.sublinksapi.person.enums.ListingType; +import com.sublinks.sublinksapi.person.enums.SortType; +import com.sublinks.sublinksapi.person.services.LinkPersonCommunityService; import com.sublinks.sublinksapi.post.entities.Post; +import com.sublinks.sublinksapi.post.entities.Post.PostBuilder; import com.sublinks.sublinksapi.post.models.PostSearchCriteria; import com.sublinks.sublinksapi.post.repositories.PostRepository; import com.sublinks.sublinksapi.post.services.PostService; import java.util.List; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; @AllArgsConstructor @Service @@ -22,6 +34,9 @@ public class SublinksPostService { private final PostRepository postRepository; private final ConversionService conversionService; private final CommunityRepository communityRepository; + private final RolePermissionService rolePermissionService; + private final LinkPersonCommunityService linkPersonCommunityService; + private final LanguageRepository languageRepository; /** * Retrieves a list of PostResponse objects based on the provided search criteria. @@ -37,10 +52,8 @@ public List index(final IndexPost indexPostForm, final Person pers List posts = postRepository.allPostsBySearchCriteria(PostSearchCriteria.builder() .search(indexPostForm.search()) - .sortType(conversionService.convert(indexPostForm.sortType(), - com.sublinks.sublinksapi.person.enums.SortType.class)) - .listingType(conversionService.convert(indexPostForm.listingType(), - com.sublinks.sublinksapi.person.enums.ListingType.class)) + .sortType(conversionService.convert(indexPostForm.sortType(), SortType.class)) + .listingType(conversionService.convert(indexPostForm.listingType(), ListingType.class)) .communityIds(communities == null ? null : communities.stream() .map(Community::getId) .toList()) @@ -57,4 +70,69 @@ public List index(final IndexPost indexPostForm, final Person pers .toList(); } + public PostResponse show(final String key, final Person person) { + + final Post post = postRepository.findByTitleSlug(key) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found")); + + if (post.isRemoved() || post.isDeleted()) { + + final boolean isPermittedToReadRemovedPosts = + person != null && (rolePermissionService.isPermitted(person, + RolePermissionPostTypes.ADMIN_SHOW_DELETED_POST) || ( + rolePermissionService.isPermitted(person, + RolePermissionPostTypes.MODERATOR_SHOW_DELETED_POST) + && linkPersonCommunityService.hasAnyLink(person, post.getCommunity(), + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)))); + + final boolean isOwner = person != null && postService.getPostCreator(post) == person; + + if (!isPermittedToReadRemovedPosts || !(isOwner && !post.isRemoved())) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found"); + } + } + + return conversionService.convert(post, PostResponse.class); + } + + public PostResponse create(final CreatePost createPostForm, final Person person) { + + final Language language = createPostForm.languageKey() != null && !createPostForm.languageKey() + .isEmpty() ? languageRepository.findLanguageByCode(createPostForm.languageKey()) + : languageRepository.findLanguageByCode("und"); + + final Community community = communityRepository.findCommunityByTitleSlug( + createPostForm.communityKey()) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "community_not_found")); + + final PostBuilder postBuilder = Post.builder() + .title(createPostForm.title()) + .postBody(createPostForm.body()) + .language(language) + .community(community) + .isNsfw(createPostForm.nsfw()) + .isFeatured(createPostForm.featuredLocal()) + .isFeaturedInCommunity(createPostForm.featuredCommunity()); + + if (createPostForm.featuredLocal()) { + if (!rolePermissionService.isPermitted(person, RolePermissionPostTypes.ADMIN_PIN_POST)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "feature_post_permission_denied"); + } + } + if (createPostForm.featuredCommunity()) { + if (!(rolePermissionService.isPermitted(person, RolePermissionPostTypes.MODERATOR_PIN_POST) + && linkPersonCommunityService.hasAnyLink(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) + && !rolePermissionService.isPermitted(person, RolePermissionPostTypes.ADMIN_PIN_POST)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "feature_post_permission_denied"); + } + } + + final Post post = postBuilder.build(); + postService.createPost(post, person); + + return conversionService.convert(post, PostResponse.class); + } + } From 3ddde7bf89b4c61928340704c4ca5a190a99eed8 Mon Sep 17 00:00:00 2001 From: rooki Date: Mon, 3 Jun 2024 12:29:07 +0200 Subject: [PATCH 053/115] Enhance Post functionalities with language support and slur filter This commit includes enhancements to the 'Post' object. Language restrictions are added, checks whether the post language is supported by the community or instance are implemented. A Slur filter has been integrated to handle exception scenarios and it can censor text in post bodies. Additionally, metadata from the linked website, if a link is provided in the post, is fetched and stored. Signed-off-by: rooki --- .../controllers/SublinksPostController.java | 20 +-- .../v1/post/mappers/SublinksPostMapper.java | 2 + .../sublinks/v1/post/models/CreatePost.java | 4 +- .../v1/post/services/SublinksPostService.java | 118 +++++++++++++++++- .../post/repositories/PostRepositoryImpl.java | 2 + 5 files changed, 133 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java index 7d6562c0..96ef25df 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java @@ -2,6 +2,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.CreatePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.IndexPost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; import com.sublinks.sublinksapi.api.sublinks.v1.post.services.SublinksPostService; @@ -36,8 +37,7 @@ public SublinksPostController(SublinksPostService sublinksPostService) { @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List index( - final Optional indexPost, + public List index(final Optional indexPost, final SublinksJwtPerson sublinksJwtPerson) { @@ -48,19 +48,25 @@ public List index( } @Operation(summary = "Get a specific post") - @GetMapping("/{id}") + @GetMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void show(@PathVariable String id) { - // TODO: implement + public void show(@PathVariable String key, final SublinksJwtPerson sublinksJwtPerson) { + + final Optional person = getOptionalPerson(sublinksJwtPerson); + + sublinksPostService.show(key, person.orElse(null)); } @Operation(summary = "Create a new post") @PostMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void create() { - // TODO: implement + public void create(final CreatePost createPost, final SublinksJwtPerson sublinksJwtPerson) { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + sublinksPostService.create(createPost, person); } @Operation(summary = "Update an post") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostMapper.java index 4a9cbeb8..91b83cf1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostMapper.java @@ -10,11 +10,13 @@ import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; +import org.mapstruct.ReportingPolicy; import org.springframework.core.convert.ConversionService; import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING, + unmappedTargetPolicy = ReportingPolicy.IGNORE, uses = {SublinksLinkMetaDataMapper.class, SublinksPostAggregationMapper.class, SublinksPersonMapper.class, SublinksCommunityMapper.class, ConversionService.class}) public abstract class SublinksPostMapper implements Converter { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/CreatePost.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/CreatePost.java index 01ab85b9..7716d039 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/CreatePost.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/CreatePost.java @@ -28,7 +28,9 @@ public record CreatePost( @Schema(description = "Is the post nsfw", defaultValue = "false", example = "false", - requiredMode = RequiredMode.NOT_REQUIRED) Boolean nsfw) { + requiredMode = RequiredMode.NOT_REQUIRED) Boolean nsfw, + @Schema(description = "The link of the post", + requiredMode = RequiredMode.NOT_REQUIRED) String link) { public CreatePost { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index 4e7899fa..fc05622b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -7,6 +7,7 @@ import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.community.entities.Community; import com.sublinks.sublinksapi.community.repositories.CommunityRepository; +import com.sublinks.sublinksapi.instance.entities.Instance; import com.sublinks.sublinksapi.language.entities.Language; import com.sublinks.sublinksapi.language.repositories.LanguageRepository; import com.sublinks.sublinksapi.person.entities.Person; @@ -14,11 +15,20 @@ import com.sublinks.sublinksapi.person.enums.ListingType; import com.sublinks.sublinksapi.person.enums.SortType; import com.sublinks.sublinksapi.person.services.LinkPersonCommunityService; +import com.sublinks.sublinksapi.person.services.PersonService; import com.sublinks.sublinksapi.post.entities.Post; import com.sublinks.sublinksapi.post.entities.Post.PostBuilder; +import com.sublinks.sublinksapi.post.entities.PostReport; import com.sublinks.sublinksapi.post.models.PostSearchCriteria; import com.sublinks.sublinksapi.post.repositories.PostRepository; +import com.sublinks.sublinksapi.post.services.PostReportService; import com.sublinks.sublinksapi.post.services.PostService; +import com.sublinks.sublinksapi.shared.RemovedState; +import com.sublinks.sublinksapi.slurfilter.exceptions.SlurFilterBlockedException; +import com.sublinks.sublinksapi.slurfilter.exceptions.SlurFilterReportException; +import com.sublinks.sublinksapi.slurfilter.services.SlurFilterService; +import com.sublinks.sublinksapi.utils.SiteMetadataUtil; +import com.sublinks.sublinksapi.utils.UrlUtil; import java.util.List; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; @@ -37,6 +47,12 @@ public class SublinksPostService { private final RolePermissionService rolePermissionService; private final LinkPersonCommunityService linkPersonCommunityService; private final LanguageRepository languageRepository; + private final Instance localInstance; + private final SlurFilterService slurFilterService; + private final UrlUtil urlUtil; + private final SiteMetadataUtil siteMetadataUtil; + private final PostReportService postReportService; + private final PersonService personService; /** * Retrieves a list of PostResponse objects based on the provided search criteria. @@ -70,6 +86,15 @@ public List index(final IndexPost indexPostForm, final Person pers .toList(); } + /** + * Retrieves the PostResponse object for the given key and person. + * + * @param key The key of the post. + * @param person The person object representing the user. + * @return The PostResponse object matching the key. + * @throws ResponseStatusException If the post is not found or the user does not have permission + * to access the post. + */ public PostResponse show(final String key, final Person person) { final Post post = postRepository.findByTitleSlug(key) @@ -95,17 +120,50 @@ public PostResponse show(final String key, final Person person) { return conversionService.convert(post, PostResponse.class); } + /** + * Creates a new Post with the provided form data and person. + * + * @param createPostForm The CreatePost object containing the form data for creating the post. + * @param person The Person object representing the user who is creating the post. + * @return The created PostResponse object. + * @throws ResponseStatusException If the community or feature post permission is not found or + * denied. + */ public PostResponse create(final CreatePost createPostForm, final Person person) { - final Language language = createPostForm.languageKey() != null && !createPostForm.languageKey() - .isEmpty() ? languageRepository.findLanguageByCode(createPostForm.languageKey()) - : languageRepository.findLanguageByCode("und"); - final Community community = communityRepository.findCommunityByTitleSlug( createPostForm.communityKey()) .orElseThrow( () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "community_not_found")); + final Language language = createPostForm.languageKey() != null && !createPostForm.languageKey() + .isEmpty() ? languageRepository.findLanguageByCode(createPostForm.languageKey()) + : personService.getPersonDefaultPostLanguage(person, community) + .orElse(languageRepository.findLanguageByCode("und")); + + if (language == null) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "language_not_found"); + } + + if (community.getLanguages() + .stream() + .noneMatch(communityLanguage -> communityLanguage.getCode() + .equals(language.getCode()))) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "language_not_supported_by_community"); + } + + if (community.getInstance() + .getLanguages() + .stream() + .noneMatch(instanceLanguage -> instanceLanguage.getCode() + .equals(language.getCode()))) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "language_not_supported_by_instance"); + } + + languageRepository.findLanguageByCode("und"); + final PostBuilder postBuilder = Post.builder() .title(createPostForm.title()) .postBody(createPostForm.body()) @@ -113,7 +171,46 @@ public PostResponse create(final CreatePost createPostForm, final Person person) .community(community) .isNsfw(createPostForm.nsfw()) .isFeatured(createPostForm.featuredLocal()) - .isFeaturedInCommunity(createPostForm.featuredCommunity()); + .isFeaturedInCommunity(createPostForm.featuredCommunity()) + .instance(localInstance) + .removedState(RemovedState.NOT_REMOVED); + + boolean shouldReport = false; + + try { + postBuilder.postBody(slurFilterService.censorText(createPostForm.body())); + } catch (SlurFilterReportException e) { + shouldReport = true; + postBuilder.postBody(createPostForm.body()); + } catch (SlurFilterBlockedException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "post_blocked_by_slur_filter"); + } + + try { + postBuilder.title(slurFilterService.censorText(createPostForm.title())); + } catch (SlurFilterReportException e) { + shouldReport = true; + postBuilder.title(createPostForm.title()); + } catch (SlurFilterBlockedException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "post_blocked_by_slur_filter"); + } + String url = createPostForm.link(); + SiteMetadataUtil.SiteMetadata metadata = null; + if (url != null) { + String metadataUrl = urlUtil.normalizeUrl(url); + urlUtil.checkUrlProtocol(metadataUrl); + metadata = siteMetadataUtil.fetchSiteMetadata(metadataUrl); + } + + if (url != null) { + postBuilder.linkUrl(url); + if (metadata != null) { + postBuilder.linkTitle(metadata.title()) + .linkDescription(metadata.description()) + .linkVideoUrl(metadata.videoUrl()) + .linkThumbnailUrl(metadata.imageUrl()); + } + } if (createPostForm.featuredLocal()) { if (!rolePermissionService.isPermitted(person, RolePermissionPostTypes.ADMIN_PIN_POST)) { @@ -132,6 +229,17 @@ public PostResponse create(final CreatePost createPostForm, final Person person) final Post post = postBuilder.build(); postService.createPost(post, person); + if (shouldReport) { + postReportService.createPostReport(PostReport.builder() + .post(post) + .creator(person) + .reason("AUTOMATED: Post creation triggered a slur filter") + .originalBody(post.getPostBody() == null ? "" : post.getPostBody()) + .originalTitle(post.getTitle() == null ? "" : post.getTitle()) + .originalUrl(post.getLinkUrl() == null ? "" : post.getLinkUrl()) + .build()); + } + return conversionService.convert(post, PostResponse.class); } diff --git a/src/main/java/com/sublinks/sublinksapi/post/repositories/PostRepositoryImpl.java b/src/main/java/com/sublinks/sublinksapi/post/repositories/PostRepositoryImpl.java index ca8bb25a..44f1d654 100644 --- a/src/main/java/com/sublinks/sublinksapi/post/repositories/PostRepositoryImpl.java +++ b/src/main/java/com/sublinks/sublinksapi/post/repositories/PostRepositoryImpl.java @@ -5,6 +5,7 @@ import com.sublinks.sublinksapi.person.entities.LinkPersonPost; import com.sublinks.sublinksapi.person.entities.Person; import com.sublinks.sublinksapi.person.enums.LinkPersonPostType; +import com.sublinks.sublinksapi.person.enums.ListingType; import com.sublinks.sublinksapi.post.entities.Post; import com.sublinks.sublinksapi.post.models.PostSearchCriteria; import com.sublinks.sublinksapi.post.services.PostSearchQueryService; @@ -47,6 +48,7 @@ public List allPostsBySearchCriteria(final PostSearchCriteria postSearchCr postSearchCriteria.person()); } else { searchBuilder.filterByListingType(postSearchCriteria.listingType()); + } if (postSearchCriteria.person() != null && postSearchCriteria.listingType() == ListingType.Subscribed) { searchBuilder.filterByListingType(postSearchCriteria.listingType()); From 58b825a7d7a82a8a00336da30e419a82b1617c0e Mon Sep 17 00:00:00 2001 From: rooki Date: Thu, 6 Jun 2024 14:03:27 +0200 Subject: [PATCH 054/115] Implement comment aggregate API and extend post functionalities This update introduces the comment aggregate API, providing insights on aggregate data like upvotes, downvotes, hot rank and reply count per comment. Furthermore, it broadens the post functionalities by introducing language restrictions and enforcing to check if the chosen language is supported by the respective community or instance. The slur filter integration helps in text censorship within post bodies, and links within posts now have metadata fetching and storing ability. Signed-off-by: rooki --- .../SublinksCommentAggerateController.java | 39 ++++++ .../models/CommentAggregateResponse.java | 22 +++ .../services/SublinksCommentService.java | 31 ++++ .../sublinks/v1/post/models/CreatePost.java | 9 +- .../sublinks/v1/post/models/UpdatePost.java | 45 ++++++ .../v1/post/services/SublinksPostService.java | 132 +++++++++++++++++- .../CommentAggregateRepository.java | 4 + 7 files changed, 270 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentAggregateResponse.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/UpdatePost.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java new file mode 100644 index 00000000..ce87a49e --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java @@ -0,0 +1,39 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.comment.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentAggregateResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.person.entities.Person; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.Optional; +import lombok.AllArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AllArgsConstructor +@RequestMapping("api/v1/comment/{key}/aggregate") +@Tag(name = "Comment Aggregation", description = "Comment Aggregate API") +public class SublinksCommentAggerateController extends AbstractSublinksApiController { + + private final SublinksCommentService sublinksCommentService; + + @Operation(summary = "Aggregate a comment") + @GetMapping + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public CommentAggregateResponse aggregate(@PathVariable final String key, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Optional person = getOptionalPerson(sublinksJwtPerson); + + return sublinksCommentService.aggregate(key, person.orElse(null)); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentAggregateResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentAggregateResponse.java new file mode 100644 index 00000000..cf203887 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentAggregateResponse.java @@ -0,0 +1,22 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.comment.models; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import lombok.Builder; + +@Builder +public record CommentAggregateResponse( + @Schema(description = "Search query", + requiredMode = RequiredMode.NOT_REQUIRED) String commentKey, + @Schema(description = "The number of upvotes", + requiredMode = RequiredMode.NOT_REQUIRED) Integer upVotes, + @Schema(description = "The number of downvotes", + requiredMode = RequiredMode.NOT_REQUIRED) Integer downVotes, + @Schema(description = "The number of hot rank", + requiredMode = RequiredMode.NOT_REQUIRED) Integer hotRank, + @Schema(description = "The number of controversy rank", + requiredMode = RequiredMode.NOT_REQUIRED) Integer controversyRank, + @Schema(description = "The number of reply count", + requiredMode = RequiredMode.NOT_REQUIRED) Integer replyCount) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index c912d3bd..cf2f3f2c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -1,5 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.comment.services; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentAggregateResponse; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CreateComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.IndexComment; @@ -14,6 +15,7 @@ import com.sublinks.sublinksapi.comment.entities.Comment; import com.sublinks.sublinksapi.comment.enums.CommentSortType; import com.sublinks.sublinksapi.comment.models.CommentSearchCriteria; +import com.sublinks.sublinksapi.comment.repositories.CommentAggregateRepository; import com.sublinks.sublinksapi.comment.repositories.CommentRepository; import com.sublinks.sublinksapi.comment.services.CommentService; import com.sublinks.sublinksapi.community.entities.Community; @@ -47,6 +49,7 @@ public class SublinksCommentService { private final LanguageRepository languageRepository; private final CommunityRepository communityRepository; private final CommentRepository commentRepository; + private final CommentAggregateRepository commentAggregateRepository; private final CommentService commentService; private final PostRepository postRepository; private final LanguageService languageService; @@ -231,6 +234,7 @@ public CommentResponse updateComment(UpdateComment updateCommentForm, Person per return conversionService.convert(comment, CommentResponse.class); } + /** * Removes a comment based on the provided key, comment remove form, and person. * @@ -290,6 +294,16 @@ public CommentResponse delete(String key, CommentDelete commentDeleteForm, Perso return conversionService.convert(comment, CommentResponse.class); } + /** + * Pins a comment based on the provided key, CommentPin object, and Person object. + * + * @param key The key of the comment to be pinned. + * @param commentPinForm The CommentPin object representing the pin status of the comment. + * @param person The Person object representing the user performing the pinning. + * @return A CommentResponse object representing the pinned comment. + * @throws ResponseStatusException If the user does not have permission to pin the comment or the + * comment is not found. + */ public CommentResponse pin(String key, CommentPin commentPinForm, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.MODERATOR_PIN_COMMENT, @@ -310,4 +324,21 @@ public CommentResponse pin(String key, CommentPin commentPinForm, Person person) return conversionService.convert(comment, CommentResponse.class); } + + /** + * Retrieves a {@link CommentAggregateResponse} based on the provided comment key and person. + * + * @param commentKey The key of the comment to retrieve the aggregate for. + * @param person The {@link Person} object representing the user performing the operation. + * @return A {@link CommentAggregateResponse} object representing the retrieved comment aggregate. + * @throws ResponseStatusException If the comment is not found. + */ + public CommentAggregateResponse aggregate(String commentKey, Person person) { + + return commentAggregateRepository.findByComment_Path(commentKey) + .map(commentAggregate -> conversionService.convert(commentAggregate, + CommentAggregateResponse.class)) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/CreatePost.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/CreatePost.java index 7716d039..3e5c5f90 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/CreatePost.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/CreatePost.java @@ -8,8 +8,10 @@ @Builder public record CreatePost( - String title, - String body, + @Schema(description = "The title of the post", + requiredMode = RequiredMode.REQUIRED) String title, + @Schema(description = "The body of the post", + requiredMode = RequiredMode.NOT_REQUIRED) String body, @Schema(description = "The language key of the post", defaultValue = "und", example = "und", @@ -37,9 +39,6 @@ public record CreatePost( if (title.isBlank()) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "title_can_not_be_blank"); } - if (body.isBlank()) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "body_can_not_be_blank"); - } } public String languageKey() { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/UpdatePost.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/UpdatePost.java new file mode 100644 index 00000000..7cc053a6 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/UpdatePost.java @@ -0,0 +1,45 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.models; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import lombok.Builder; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +@Builder +public record UpdatePost( + @Schema(description = "The title of the post", + requiredMode = RequiredMode.REQUIRED) String title, + @Schema(description = "The body of the post", + requiredMode = RequiredMode.NOT_REQUIRED) String body, + @Schema(description = "The language key of the post", + defaultValue = "und", + example = "und", + requiredMode = RequiredMode.NOT_REQUIRED) String languageKey, + @Schema(description = "Whether the post is featured ( Requires permission to do so. )", + defaultValue = "false", + example = "false", + requiredMode = RequiredMode.NOT_REQUIRED) Boolean featuredLocal, + @Schema(description = "Whether the post is featured in the community ( Requires permission to do so. )", + defaultValue = "false", + example = "false", + requiredMode = RequiredMode.NOT_REQUIRED) Boolean featuredCommunity, + @Schema(description = "Is the post nsfw", + defaultValue = "false", + example = "false", + requiredMode = RequiredMode.NOT_REQUIRED) Boolean nsfw, + @Schema(description = "The link of the post", + requiredMode = RequiredMode.NOT_REQUIRED) String link) { + + public UpdatePost { + + if (title.isBlank()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "title_can_not_be_blank"); + } + } + + public String languageKey() { + + return languageKey == null ? "und" : languageKey; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index fc05622b..c14f26bb 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -3,6 +3,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.post.models.CreatePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.IndexPost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.UpdatePost; import com.sublinks.sublinksapi.authorization.enums.RolePermissionPostTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.community.entities.Community; @@ -162,7 +163,21 @@ public PostResponse create(final CreatePost createPostForm, final Person person) "language_not_supported_by_instance"); } - languageRepository.findLanguageByCode("und"); + // @todo: modlog? + + if (createPostForm.featuredLocal()) { + if (!rolePermissionService.isPermitted(person, RolePermissionPostTypes.ADMIN_PIN_POST)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "feature_post_permission_denied"); + } + } + if (createPostForm.featuredCommunity()) { + if (!(rolePermissionService.isPermitted(person, RolePermissionPostTypes.MODERATOR_PIN_POST) + && linkPersonCommunityService.hasAnyLink(person, community, + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) + && !rolePermissionService.isPermitted(person, RolePermissionPostTypes.ADMIN_PIN_POST)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "feature_post_permission_denied"); + } + } final PostBuilder postBuilder = Post.builder() .title(createPostForm.title()) @@ -212,28 +227,131 @@ public PostResponse create(final CreatePost createPostForm, final Person person) } } - if (createPostForm.featuredLocal()) { + final Post post = postBuilder.build(); + postService.createPost(post, person); + + if (shouldReport) { + postReportService.createPostReport(PostReport.builder() + .post(post) + .creator(person) + .reason("AUTOMATED: Post creation triggered a slur filter") + .originalBody(post.getPostBody() == null ? "" : post.getPostBody()) + .originalTitle(post.getTitle() == null ? "" : post.getTitle()) + .originalUrl(post.getLinkUrl() == null ? "" : post.getLinkUrl()) + .build()); + } + + return conversionService.convert(post, PostResponse.class); + } + + public PostResponse update(final String postKey, final UpdatePost updatePostForm, + final Person person) + { + + final Post post = postRepository.findByTitleSlug(postKey) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found")); + final Community community = post.getCommunity(); + if (updatePostForm.languageKey() != null && !updatePostForm.languageKey() + .isEmpty()) { + + final Language language = languageRepository.findLanguageByCode(updatePostForm.languageKey()); + + if (language == null) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "language_not_found"); + } + + if (community.getLanguages() + .stream() + .noneMatch(communityLanguage -> communityLanguage.getCode() + .equals(language.getCode()))) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "language_not_supported_by_community"); + } + + if (community.getInstance() + .getLanguages() + .stream() + .noneMatch(instanceLanguage -> instanceLanguage.getCode() + .equals(language.getCode()))) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "language_not_supported_by_instance"); + } + } + + // @todo: modlog? + + if (updatePostForm.featuredLocal() != null) { if (!rolePermissionService.isPermitted(person, RolePermissionPostTypes.ADMIN_PIN_POST)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "feature_post_permission_denied"); } + + post.setFeatured(updatePostForm.featuredLocal()); } - if (createPostForm.featuredCommunity()) { + if (updatePostForm.featuredCommunity() != null) { if (!(rolePermissionService.isPermitted(person, RolePermissionPostTypes.MODERATOR_PIN_POST) - && linkPersonCommunityService.hasAnyLink(person, community, + && linkPersonCommunityService.hasAnyLink(person, post.getCommunity(), List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) && !rolePermissionService.isPermitted(person, RolePermissionPostTypes.ADMIN_PIN_POST)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "feature_post_permission_denied"); } + + post.setFeatured(updatePostForm.featuredCommunity()); } - final Post post = postBuilder.build(); - postService.createPost(post, person); + boolean shouldReport = false; + + if (updatePostForm.title() != null && !updatePostForm.title() + .isBlank()) { + try { + post.setPostBody(slurFilterService.censorText(updatePostForm.body())); + } catch (SlurFilterReportException e) { + shouldReport = true; + post.setPostBody(updatePostForm.body()); + } catch (SlurFilterBlockedException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "post_blocked_by_slur_filter"); + } + } + + if (updatePostForm.nsfw() != null) { + post.setNsfw(updatePostForm.nsfw()); + } + if (updatePostForm.body() != null && !updatePostForm.body() + .isBlank()) { + try { + post.setTitle(slurFilterService.censorText(updatePostForm.title())); + } catch (SlurFilterReportException e) { + shouldReport = true; + post.setTitle(updatePostForm.title()); + } catch (SlurFilterBlockedException e) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "post_blocked_by_slur_filter"); + } + } + + String url = updatePostForm.link(); + SiteMetadataUtil.SiteMetadata metadata = null; + if (url != null) { + String metadataUrl = urlUtil.normalizeUrl(url); + urlUtil.checkUrlProtocol(metadataUrl); + metadata = siteMetadataUtil.fetchSiteMetadata(metadataUrl); + } + + if (url != null) { + post.setLinkUrl(url); + if (metadata != null) { + post.setLinkUrl(metadata.title()); + post.setLinkDescription(metadata.description()); + post.setLinkVideoUrl(metadata.videoUrl()); + post.setLinkThumbnailUrl(metadata.imageUrl()); + } + } + + postService.updatePost(post); if (shouldReport) { postReportService.createPostReport(PostReport.builder() .post(post) .creator(person) - .reason("AUTOMATED: Post creation triggered a slur filter") + .reason("AUTOMATED: Post update triggered a slur filter") .originalBody(post.getPostBody() == null ? "" : post.getPostBody()) .originalTitle(post.getTitle() == null ? "" : post.getTitle()) .originalUrl(post.getLinkUrl() == null ? "" : post.getLinkUrl()) diff --git a/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentAggregateRepository.java b/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentAggregateRepository.java index 3caa6bea..45c1fca4 100644 --- a/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentAggregateRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentAggregateRepository.java @@ -1,8 +1,12 @@ package com.sublinks.sublinksapi.comment.repositories; +import com.sublinks.sublinksapi.comment.entities.Comment; import com.sublinks.sublinksapi.comment.entities.CommentAggregate; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; public interface CommentAggregateRepository extends JpaRepository { + Optional findByComment_Path(String path); + } From f37650b4095f02d9f594cdb26fe8d2942a554c73 Mon Sep 17 00:00:00 2001 From: rooki Date: Fri, 7 Jun 2024 13:20:21 +0200 Subject: [PATCH 055/115] Implement comment aggregate API and extend post functionalities This update introduces the comment aggregate API, providing insights on aggregate data like upvotes, downvotes, hot rank and reply count per comment. Furthermore, it broadens the post functionalities by introducing language restrictions and enforcing to check if the chosen language is supported by the respective community or instance. The slur filter integration helps in text censorship within post bodies, and links within posts now have metadata fetching and storing ability. Signed-off-by: rooki --- .../models/CommentResponseDto.java | 13 --- .../SublinksCommentAggerateController.java | 9 +++ .../SublinksCommentModerationController.java | 11 +++ .../v1/comment/models/CommentResponse.java | 5 ++ .../v1/comment/models/IndexComment.java | 1 + .../services/SublinksCommentService.java | 27 +++---- ...ublinksCommunityAggregationController.java | 6 +- .../SublinksCommunityController.java | 10 ++- ...SublinksCommunityModerationController.java | 20 +---- ...inksCommunityAggregatesResponseMapper.java | 6 +- ...e.java => CommunityAggregateResponse.java} | 2 +- ...munityRemove.java => DeleteCommunity.java} | 4 +- ...munityDelete.java => RemoveCommunity.java} | 2 +- .../services/SublinksCommunityService.java | 36 +++++++-- .../SublinksPersonAggregationController.java | 17 +--- .../controllers/SublinksPersonController.java | 13 +++ .../v1/person/models/DeletePerson.java | 15 ++++ .../services/SublinksPersonService.java | 65 ++++++++++++++- .../controllers/SublinksPostController.java | 33 +++++--- .../SublinksPostModerationController.java | 28 +++++++ .../sublinks/v1/post/models/DeletePost.java | 7 ++ .../v1/post/models/moderation/RemovePost.java | 7 ++ .../v1/post/services/SublinksPostService.java | 79 ++++++++++++++++++- .../enums/RolePermissionCommentTypes.java | 1 + .../repositories/CommentRepositoryImpl.java | 4 +- .../post/services/PostService.java | 12 ++- 26 files changed, 334 insertions(+), 99 deletions(-) delete mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CommentResponseDto.java rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/{CommunityAggregatesResponse.java => CommunityAggregateResponse.java} (88%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/{Moderation/CommunityRemove.java => DeleteCommunity.java} (70%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/{CommunityDelete.java => RemoveCommunity.java} (79%) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/DeletePerson.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/DeletePost.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/RemovePost.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CommentResponseDto.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CommentResponseDto.java deleted file mode 100644 index 948a4eb5..00000000 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CommentResponseDto.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models; - -public record CommentResponseDto( - String id, - String postId, - String personId, - String communityId, - String content, - String createdAt, - String updatedAt -) { - -} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java index ce87a49e..7aacc9fa 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java @@ -4,6 +4,8 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentAggregateResponse; import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommentTypes; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -11,10 +13,12 @@ import io.swagger.v3.oas.annotations.tags.Tag; import java.util.Optional; import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; @RestController @AllArgsConstructor @@ -23,6 +27,7 @@ public class SublinksCommentAggerateController extends AbstractSublinksApiController { private final SublinksCommentService sublinksCommentService; + private final RolePermissionService rolePermissionService; @Operation(summary = "Aggregate a comment") @GetMapping @@ -34,6 +39,10 @@ public CommentAggregateResponse aggregate(@PathVariable final String key, final Optional person = getOptionalPerson(sublinksJwtPerson); + rolePermissionService.isPermitted(person.orElse(null), + RolePermissionCommentTypes.READ_COMMENT_AGGREGATE, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_view_not_permitted")); + return sublinksCommentService.aggregate(key, person.orElse(null)); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java index dbdaca71..451490cb 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java @@ -7,6 +7,8 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.CommentRemove; import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommentTypes; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -14,12 +16,14 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.server.ResponseStatusException; @RestController @AllArgsConstructor @@ -28,6 +32,7 @@ public class SublinksCommentModerationController extends AbstractSublinksApiController { private final SublinksCommentService sublinksCommentService; + private final RolePermissionService rolePermissionService; @Operation(summary = "Remove a comment") @PostMapping("/remove") @@ -54,6 +59,9 @@ public CommentResponse delete(@PathVariable final String key, final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + rolePermissionService.isPermitted(person, RolePermissionCommentTypes.DELETE_COMMENT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_delete_not_permitted")); + return sublinksCommentService.delete(key, commentDeleteForm, person); } @@ -67,6 +75,9 @@ public CommentResponse highlight(@PathVariable final String key, final CommentPi final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + rolePermissionService.isPermitted(person, RolePermissionCommentTypes.MODERATOR_PIN_COMMENT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_highlight_not_permitted")); + return sublinksCommentService.pin(key, commentPinForm, person); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentResponse.java index d9922530..09366a2c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentResponse.java @@ -1,6 +1,9 @@ package com.sublinks.sublinksapi.api.sublinks.v1.comment.models; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import java.util.List; import lombok.Builder; @Builder @@ -15,6 +18,8 @@ public record CommentResponse( Boolean isRemoved, String createdAt, PersonResponse creator, + @Schema(description = "The replies to the comment.", + requiredMode = RequiredMode.NOT_REQUIRED) List replies, String updatedAt) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java index a06024c6..02f52e53 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java @@ -15,6 +15,7 @@ public record IndexComment( @Schema(description = "Community key", requiredMode = RequiredMode.NOT_REQUIRED) String communityKey, @Schema(description = "Post key", requiredMode = RequiredMode.NOT_REQUIRED) String postKey, + @Schema(description = "Parent Comment key", requiredMode = RequiredMode.NOT_REQUIRED) String parentCommentKey, @Schema(description = "Show NSFW", requiredMode = RequiredMode.NOT_REQUIRED) Boolean showNsfw, @Schema(description = "Saved only", requiredMode = RequiredMode.NOT_REQUIRED) Boolean savedOnly, @Schema(description = "Per page", requiredMode = RequiredMode.NOT_REQUIRED) Integer perPage, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index cf2f3f2c..e7f950f1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -73,7 +73,7 @@ public List index(final IndexComment indexCommentForm, final Pe Optional parentComment = Optional.empty(); if (indexCommentForm.postKey() != null) { - parentComment = commentRepository.findByPath(indexCommentForm.postKey()); + parentComment = commentRepository.findByPath(indexCommentForm.parentCommentKey()); } Optional community = Optional.empty(); @@ -264,7 +264,7 @@ public CommentResponse remove(String key, CommentRemove commentRemoveForm, Perso } /** - * Deletes a comment based on the provided key, comment delete form, and person. + * Deletes a comment based on the provided key, CommentDelete form, and Person. * * @param key The key of the comment to be deleted. * @param commentDeleteForm The CommentDelete object representing the delete form data. @@ -275,9 +275,6 @@ public CommentResponse remove(String key, CommentRemove commentRemoveForm, Perso */ public CommentResponse delete(String key, CommentDelete commentDeleteForm, Person person) { - rolePermissionService.isPermitted(person, RolePermissionCommentTypes.DELETE_COMMENT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_delete_not_permitted")); - Comment comment = commentRepository.findByPath(key) .orElseThrow( () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); @@ -295,20 +292,18 @@ public CommentResponse delete(String key, CommentDelete commentDeleteForm, Perso } /** - * Pins a comment based on the provided key, CommentPin object, and Person object. + * Pins or unpins a comment and returns the updated CommentResponse object. * - * @param key The key of the comment to be pinned. - * @param commentPinForm The CommentPin object representing the pin status of the comment. - * @param person The Person object representing the user performing the pinning. - * @return A CommentResponse object representing the pinned comment. - * @throws ResponseStatusException If the user does not have permission to pin the comment or the - * comment is not found. + * @param key The key of the comment. + * @param commentPinForm The CommentPin object representing the pin form data. + * @param person The Person object representing the user performing the + * pinning/unpinning. + * @return A CommentResponse object representing the updated comment. + * @throws ResponseStatusException If the comment is not found or the user does not have + * permission to perform the operation. */ public CommentResponse pin(String key, CommentPin commentPinForm, Person person) { - rolePermissionService.isPermitted(person, RolePermissionCommentTypes.MODERATOR_PIN_COMMENT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_highlight_not_permitted")); - Comment comment = commentRepository.findByPath(key) .orElseThrow( () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); @@ -329,7 +324,7 @@ public CommentResponse pin(String key, CommentPin commentPinForm, Person person) * Retrieves a {@link CommentAggregateResponse} based on the provided comment key and person. * * @param commentKey The key of the comment to retrieve the aggregate for. - * @param person The {@link Person} object representing the user performing the operation. + * @param person The {@link Person} object representing the user performing the operation. * @return A {@link CommentAggregateResponse} object representing the retrieved comment aggregate. * @throws ResponseStatusException If the comment is not found. */ diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java index e48aa54c..34593451 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java @@ -2,7 +2,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; -import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityAggregatesResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityAggregateResponse; import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommunityTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.community.entities.CommunityAggregate; @@ -36,7 +36,7 @@ public class SublinksCommunityAggregationController extends AbstractSublinksApiC @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommunityAggregatesResponse show(@PathVariable final String key, + public CommunityAggregateResponse show(@PathVariable final String key, final SublinksJwtPerson sublinksJwtPerson) { @@ -53,6 +53,6 @@ public CommunityAggregatesResponse show(@PathVariable final String key, throw new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found"); } - return conversionService.convert(communityAggregate, CommunityAggregatesResponse.class); + return conversionService.convert(communityAggregate, CommunityAggregateResponse.class); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index 0dd83a3c..11a8cf0e 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -4,6 +4,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.DeleteCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.IndexCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; @@ -99,7 +100,12 @@ public CommunityResponse update(@PathVariable final String key, @DeleteMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void delete(@PathVariable String key) { - // @TODO: implement + public CommunityResponse delete(@RequestBody final DeleteCommunity deleteCommunityForm, + @PathVariable final String key, final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksCommunityService.delete(key, deleteCommunityForm, person); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java index 18d613a3..44d4d464 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -4,9 +4,8 @@ import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityBanPerson; -import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityDelete; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityModeratorResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityRemove; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.RemoveCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommunityTypes; @@ -48,30 +47,17 @@ public class SublinksCommunityModerationController extends AbstractSublinksApiCo private final ConversionService conversionService; private final RolePermissionService rolePermissionService; - @Operation(summary = "Delete a community") - @PostMapping("/delete") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommunityResponse delete(@PathVariable final String key, - @RequestBody @Valid CommunityDelete communityDeleteForm, SublinksJwtPerson sublinksJwtPerson) - { - - final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - - return sublinksCommunityService.delete(key, communityDeleteForm, person); - } - @Operation(summary = "Remove a community") @GetMapping("/remove") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse remove(@PathVariable final String key, - @RequestBody @Valid CommunityRemove communityRemoveForm, SublinksJwtPerson sublinksJwtPerson) + @RequestBody @Valid RemoveCommunity removeCommunityForm, SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - return sublinksCommunityService.remove(key, communityRemoveForm, person); + return sublinksCommunityService.remove(key, removeCommunityForm, person); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityAggregatesResponseMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityAggregatesResponseMapper.java index 76f0d106..23c4b95f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityAggregatesResponseMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityAggregatesResponseMapper.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.mappers; -import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityAggregatesResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityAggregateResponse; import com.sublinks.sublinksapi.community.entities.CommunityAggregate; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @@ -10,7 +10,7 @@ @Mapper(componentModel = MappingConstants.ComponentModel.SPRING) public abstract class SublinksCommunityAggregatesResponseMapper implements - Converter { + Converter { @Override @Mapping(target = "communityKey", source = "communityAggregate.community.titleSlug") @@ -22,6 +22,6 @@ public abstract class SublinksCommunityAggregatesResponseMapper implements @Mapping(target = "activeMonthlyUserCount", source = "communityAggregate.activeMonthlyUserCount") @Mapping(target = "activeHalfYearUserCount", source = "communityAggregate.activeHalfYearUserCount") - public abstract CommunityAggregatesResponse convert( + public abstract CommunityAggregateResponse convert( @Nullable CommunityAggregate communityAggregate); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregateResponse.java similarity index 88% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregateResponse.java index 5fce7258..2ebb3a4e 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregatesResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregateResponse.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models; -public record CommunityAggregatesResponse( +public record CommunityAggregateResponse( String communityKey, Integer subscriberCount, Integer postCount, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityRemove.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/DeleteCommunity.java similarity index 70% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityRemove.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/DeleteCommunity.java index e770dc43..6fd7aa32 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityRemove.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/DeleteCommunity.java @@ -1,6 +1,6 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; +package com.sublinks.sublinksapi.api.sublinks.v1.community.models; -public record CommunityRemove( +public record DeleteCommunity( String reason, Boolean remove) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityDelete.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/RemoveCommunity.java similarity index 79% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityDelete.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/RemoveCommunity.java index d020adc9..a4c6e272 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityDelete.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/RemoveCommunity.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; -public record CommunityDelete( +public record RemoveCommunity( String reason, Boolean remove) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index 904b92d7..59cc061f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -3,12 +3,13 @@ import com.sublinks.sublinksapi.api.lemmy.v3.enums.ListingType; import com.sublinks.sublinksapi.api.lemmy.v3.enums.SortType; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityAggregateResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.DeleteCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.IndexCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityBanPerson; -import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityDelete; -import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityRemove; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.RemoveCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.utils.ActorIdUtils; import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommunityTypes; @@ -279,7 +280,7 @@ public Person banPerson(String key, String personKey, Person person, * @throws ResponseStatusException If the community is not found, or the person is not authorized * to remove the community. */ - public CommunityResponse remove(String key, CommunityRemove removeComment, Person person) { + public CommunityResponse remove(String key, RemoveCommunity removeComment, Person person) { final Community community = communityRepository.findCommunityByTitleSlug(key) .orElseThrow( @@ -302,14 +303,14 @@ public CommunityResponse remove(String key, CommunityRemove removeComment, Perso * Deletes a community based on the provided key, delete form, and person. * * @param key The key of the community to delete. - * @param communityDeleteForm The delete form specifying the reason for deletion and whether to + * @param deleteCommunityForm The delete form specifying the reason for deletion and whether to * remove the community. * @param person The person performing the deletion. * @return The response containing the deleted community. * @throws ResponseStatusException If the community is not found, or the person is not authorized * to delete the community. */ - public CommunityResponse delete(String key, CommunityDelete communityDeleteForm, Person person) { + public CommunityResponse delete(String key, DeleteCommunity deleteCommunityForm, Person person) { final Community community = communityRepository.findCommunityByTitleSlug(key) .orElseThrow( @@ -322,11 +323,34 @@ public CommunityResponse delete(String key, CommunityDelete communityDeleteForm, } community.setDeleted( - communityDeleteForm.remove() != null ? communityDeleteForm.remove() : true); + deleteCommunityForm.remove() != null ? deleteCommunityForm.remove() : true); communityService.updateCommunity(community); // @todo: modlog return conversionService.convert(community, CommunityResponse.class); } + + /** + * Retrieves the aggregated information of a community. + * + * @param communityKey The key of the community. + * @param person The person requesting the information. + * @return The CommunityAggregateResponse containing the aggregated information of the community. + * @throws ResponseStatusException If the person is not authorized to read the community + * aggregation or if the community is not found. + */ + public CommunityAggregateResponse showAggregate(String communityKey, Person person) { + + rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.READ_COMMUNITY_AGGREGATION, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_read_community_aggregation")); + + final Community community = communityRepository.findCommunityByTitleSlug(communityKey) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + + return conversionService.convert(community, CommunityAggregateResponse.class); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java index 5b1af996..99e4b5ea 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java @@ -3,12 +3,10 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonAggregateResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonIdentity; import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; import com.sublinks.sublinksapi.authorization.enums.RolePermissionPersonTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.person.entities.Person; -import com.sublinks.sublinksapi.person.entities.PersonAggregate; import com.sublinks.sublinksapi.person.repositories.PersonAggregateRepository; import com.sublinks.sublinksapi.person.repositories.PersonRepository; import io.swagger.v3.oas.annotations.Operation; @@ -20,8 +18,8 @@ import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.server.ResponseStatusException; @@ -41,7 +39,7 @@ public class SublinksPersonAggregationController extends AbstractSublinksApiCont @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public PersonAggregateResponse show(@RequestParam final String key, + public PersonAggregateResponse aggregate(@PathVariable final String key, final SublinksJwtPerson sublinksJwtPerson) { @@ -52,14 +50,7 @@ public PersonAggregateResponse show(@RequestParam final String key, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_read_community_aggregation")); - final PersonIdentity personIdentity = sublinksPersonService.getPersonIdentifiersFromKey(key); - - final Person foundPerson = personRepository.findOneByNameAndInstance_Domain( - personIdentity.name(), personIdentity.domain()) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); - - final PersonAggregate personAggregate = personAggregateRepository.findByPerson(foundPerson); - - return conversionService.convert(personAggregate, PersonAggregateResponse.class); + return conversionService.convert(sublinksPersonService.showAggregate(key, person.orElse(null)), + PersonAggregateResponse.class); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java index fb40d150..ec94df5c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java @@ -3,6 +3,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.CreatePerson; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.DeletePerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.IndexPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginResponse; @@ -21,6 +22,7 @@ import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -100,4 +102,15 @@ public PersonResponse update(@PathVariable String key, return sublinksPersonService.updatePerson(person, updatePersonForm); } + + @Operation(summary = "Delete an person") + @DeleteMapping("/{key}") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PersonResponse delete(@RequestBody final DeletePerson deletePersonForm, @PathVariable String key, final SublinksJwtPerson principal) { + + final Person person = getPersonOrThrowUnauthorized(principal); + + return sublinksPersonService.deletePerson(key, deletePersonForm, person); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/DeletePerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/DeletePerson.java new file mode 100644 index 00000000..7373f83e --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/DeletePerson.java @@ -0,0 +1,15 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.models; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + +public record DeletePerson( + @Schema(description = "The reason for deleting the person", + example = "I dont use this account anymore", + requiredMode = RequiredMode.NOT_REQUIRED) String reason, + @Schema(description = "Whether to remove your Post/Comments/Private Messages or not", + example = "true", + defaultValue = "false", + requiredMode = RequiredMode.NOT_REQUIRED) Boolean deleteContent) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index 7fed98a1..3146c8ee 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -6,6 +6,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.moderation.IndexBannedPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.enums.RegistrationState; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.CreatePerson; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.DeletePerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.IndexPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginResponse; @@ -19,6 +20,7 @@ import com.sublinks.sublinksapi.authorization.enums.RolePermissionPersonTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.authorization.services.RoleService; +import com.sublinks.sublinksapi.comment.services.CommentReportService; import com.sublinks.sublinksapi.email.entities.Email; import com.sublinks.sublinksapi.email.enums.EmailTemplatesEnum; import com.sublinks.sublinksapi.email.services.EmailService; @@ -37,6 +39,8 @@ import com.sublinks.sublinksapi.person.services.PersonRegistrationApplicationService; import com.sublinks.sublinksapi.person.services.PersonService; import com.sublinks.sublinksapi.person.services.UserDataService; +import com.sublinks.sublinksapi.post.services.PostReportService; +import com.sublinks.sublinksapi.privatemessages.services.PrivateMessageReportService; import com.sublinks.sublinksapi.utils.PaginationUtils; import java.util.List; import java.util.Locale; @@ -69,6 +73,9 @@ public class SublinksPersonService { private final RoleService roleService; private final RolePermissionService rolePermissionService; private final PersonAggregateRepository personAggregateRepository; + private final PostReportService postReportService; + private final CommentReportService commentReportService; + private final PrivateMessageReportService privateMessageReportService; /** * Retrieves the name and domain identifiers of a person from the given key. @@ -411,10 +418,20 @@ public PersonResponse banPerson(final BanPerson banPersonForm, final Person pers return conversionService.convert(person, PersonResponse.class); } - public PersonAggregateResponse showAggregate(final String key, final Optional person) { + /** + * Retrieves the aggregate information of a person. + * + * @param key The key containing the person's information. If the key contains "@", it is split + * into name and domain using "@" as the separator. Otherwise, the name is set as + * the key and the domain is obtained from the local instance context. + * @param person The Person object representing the person making the request. + * @return The PersonAggregateResponse object containing the aggregate information of the person. + * @throws ResponseStatusException If the person is not authorized to read the person aggregation + * or if the person is not found. + */ + public PersonAggregateResponse showAggregate(final String key, final Person person) { - rolePermissionService.isPermitted(person.orElse(null), - RolePermissionPersonTypes.READ_PERSON_AGGREGATION, + rolePermissionService.isPermitted(person, RolePermissionPersonTypes.READ_PERSON_AGGREGATION, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_read_community_aggregation")); @@ -428,4 +445,46 @@ public PersonAggregateResponse showAggregate(final String key, final Optional { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_delete_person"); + }); + + final PersonIdentity ids = getPersonIdentifiersFromKey(key); + + final Person personToDelete = personRepository.findOneByNameAndInstance_Domain(ids.name(), + ids.domain()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + + personToDelete.setDeleted(true); + // @todo: modlog + personRepository.save(personToDelete); + + // Resolve all reports by the person + postReportService.resolveAllReportsByPerson(personToDelete, person); + commentReportService.resolveAllReportsByCommentCreator(personToDelete, person); + privateMessageReportService.resolveAllReportsByPerson(personToDelete, person); + + personService.deleteUserAccount(personToDelete, deletePersonForm.deleteContent()); + + return conversionService.convert(personToDelete, PersonResponse.class); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java index 96ef25df..1d9ad95e 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java @@ -3,16 +3,20 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.CreatePost; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.DeletePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.IndexPost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.UpdatePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.services.SublinksPostService; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.parameters.RequestBody; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.Optional; +import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -23,16 +27,11 @@ @RestController @RequestMapping("api/v1/post") @Tag(name = "Post", description = "Post API") +@AllArgsConstructor public class SublinksPostController extends AbstractSublinksApiController { private final SublinksPostService sublinksPostService; - public SublinksPostController(SublinksPostService sublinksPostService) { - - super(); - this.sublinksPostService = sublinksPostService; - } - @Operation(summary = "Get a list of posts") @GetMapping @ApiResponses(value = { @@ -70,18 +69,28 @@ public void create(final CreatePost createPost, final SublinksJwtPerson sublinks } @Operation(summary = "Update an post") - @PostMapping("/{id}") + @PostMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void update(@PathVariable String id) { - // TODO: implement + public void update(final UpdatePost updatePostForm, @PathVariable final String key, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + sublinksPostService.update(key, updatePostForm, person); } @Operation(summary = "Delete an post") - @DeleteMapping("/{id}") + @DeleteMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void delete(@PathVariable String id) { - // TODO: implement + public void delete(@RequestBody final DeletePost deletePostForm, @PathVariable final String key, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + sublinksPostService.delete(key, deletePostForm, person); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java new file mode 100644 index 00000000..f0a12b57 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java @@ -0,0 +1,28 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.CreatePost; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.IndexPost; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.UpdatePost; +import com.sublinks.sublinksapi.api.sublinks.v1.post.services.SublinksPostService; +import com.sublinks.sublinksapi.person.entities.Person; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import org.springframework.web.bind.annotation.*; +import java.util.List; +import java.util.Optional; + +@RestController +@RequestMapping("api/v1/post/{key}/moderation") +@Tag(name = "Post", description = "Post API") +@AllArgsConstructor +public class SublinksPostModerationController extends AbstractSublinksApiController { + + private final SublinksPostService sublinksPostService; + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/DeletePost.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/DeletePost.java new file mode 100644 index 00000000..68ef50dc --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/DeletePost.java @@ -0,0 +1,7 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.models; + +public record DeletePost( + String reason, + Boolean remove) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/RemovePost.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/RemovePost.java new file mode 100644 index 00000000..3eb171ca --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/RemovePost.java @@ -0,0 +1,7 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation; + +public record RemovePost( + String reason, + Boolean remove) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index c14f26bb..48ab0dfc 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -1,9 +1,11 @@ package com.sublinks.sublinksapi.api.sublinks.v1.post.services; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.CreatePost; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.DeletePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.IndexPost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.UpdatePost; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.RemovePost; import com.sublinks.sublinksapi.authorization.enums.RolePermissionPostTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.community.entities.Community; @@ -244,6 +246,16 @@ public PostResponse create(final CreatePost createPostForm, final Person person) return conversionService.convert(post, PostResponse.class); } + /** + * Updates a post with the provided information. + * + * @param postKey The key of the post to update. + * @param updatePostForm The UpdatePost object containing the updated information. + * @param person The Person object representing the user making the update. + * @return The updated PostResponse object. + * @throws ResponseStatusException If the post is not found, the language is not supported, or the + * user does not have permission to update the post. + */ public PostResponse update(final String postKey, final UpdatePost updatePostForm, final Person person) { @@ -276,6 +288,8 @@ public PostResponse update(final String postKey, final UpdatePost updatePostForm throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "language_not_supported_by_instance"); } + + post.setLanguage(language); } // @todo: modlog? @@ -361,4 +375,67 @@ public PostResponse update(final String postKey, final UpdatePost updatePostForm return conversionService.convert(post, PostResponse.class); } -} + /** + * Removes a post. + * + * @param postKey The key of the post to remove. + * @param removePostForm The RemovePost object containing additional parameters for the removal. + * @param person The Person object representing the user performing the removal. + * @return The PostResponse object for the removed post. + * @throws ResponseStatusException If the post is not found or the user is not permitted to remove + * the post. + */ + public PostResponse remove(final String postKey, final RemovePost removePostForm, + final Person person) + { + + final Post post = postRepository.findByTitleSlug(postKey) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found")); + + if (!rolePermissionService.isPermitted(person, RolePermissionPostTypes.REMOVE_POST) && !( + rolePermissionService.isPermitted(person, RolePermissionPostTypes.MODERATOR_REMOVE_POST) + && linkPersonCommunityService.hasAnyLink(person, post.getCommunity(), + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)))) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "remove_post_permission_denied"); + } + + post.setRemovedState(removePostForm.remove() ? RemovedState.REMOVED : RemovedState.NOT_REMOVED); + + // @todo: modlog? + + postService.updatePost(post); + return conversionService.convert(post, PostResponse.class); + } + + /** + * Deletes a post with the specified post key, using the provided delete post form and person. + * + * @param postKey The key of the post to delete. + * @param deletePostForm The DeletePost object containing additional parameters for the deletion. + * @param person The Person object representing the user performing the deletion. + * @return The PostResponse object for the deleted post. + * @throws ResponseStatusException If the post is not found or the user is not permitted to delete + * the post. + */ + public PostResponse delete(final String postKey, final DeletePost deletePostForm, + final Person person) + { + + final Post post = postRepository.findByTitleSlug(postKey) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found")); + + if (rolePermissionService.isPermitted(person, RolePermissionPostTypes.DELETE_POST) + && postService.getPostCreator(post) + .getId() + .equals(person.getId()) && !post.isRemoved()) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "delete_post_permission_denied"); + } + + post.setDeleted(deletePostForm.remove()); + + // @todo: modlog? + + postService.updatePost(post); + return conversionService.convert(post, PostResponse.class); + } +} \ No newline at end of file diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommentTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommentTypes.java index 3c4f94f7..18122dbe 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommentTypes.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommentTypes.java @@ -5,6 +5,7 @@ public enum RolePermissionCommentTypes implements RolePermissionInterface { // Person permissions READ_COMMENT("comment", AuthorizeAction.READ), READ_COMMENTS("comments", AuthorizeAction.READ), + READ_COMMENT_AGGREGATE("comment-aggregate", AuthorizeAction.READ), MARK_COMMENT_AS_READ("comment-read", AuthorizeAction.UPDATE), CREATE_COMMENT("comment", AuthorizeAction.CREATE), UPDATE_COMMENT("comment", AuthorizeAction.UPDATE), diff --git a/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentRepositoryImpl.java b/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentRepositoryImpl.java index 8b62680d..40c029ed 100644 --- a/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentRepositoryImpl.java +++ b/src/main/java/com/sublinks/sublinksapi/comment/repositories/CommentRepositoryImpl.java @@ -54,8 +54,8 @@ public List allCommentsBySearchCriteria(CommentSearchCriteria commentSe if (commentSearchCriteria.search() != null && !commentSearchCriteria.search() .isEmpty()) { predicates.add(cb.equal( - cb.function("fn_search_vector_is_same", Boolean.class, - commentTable.get("searchVector"), cb.literal(commentSearchCriteria.search())), true)); + cb.function("fn_search_vector_is_same", Boolean.class, commentTable.get("searchVector"), + cb.literal(commentSearchCriteria.search())), true)); } cq.where(predicates.toArray(new Predicate[0])); diff --git a/src/main/java/com/sublinks/sublinksapi/post/services/PostService.java b/src/main/java/com/sublinks/sublinksapi/post/services/PostService.java index 91b6ab44..9fb62cd9 100644 --- a/src/main/java/com/sublinks/sublinksapi/post/services/PostService.java +++ b/src/main/java/com/sublinks/sublinksapi/post/services/PostService.java @@ -48,7 +48,8 @@ public String getStringMd5Hash(final String post) { } try { - final byte[] bytesOfLink = urlUtil.normalizeUrl(post).getBytes(StandardCharsets.UTF_8); + final byte[] bytesOfLink = urlUtil.normalizeUrl(post) + .getBytes(StandardCharsets.UTF_8); final MessageDigest md = MessageDigest.getInstance("MD5"); final byte[] bytesOfMD5Link = md.digest(bytesOfLink); return new BigInteger(1, bytesOfMD5Link).toString(16); @@ -59,7 +60,8 @@ public String getStringMd5Hash(final String post) { public Person getPostCreator(final Post post) { - if (post.getLinkPersonPost() == null || post.getLinkPersonPost().isEmpty()) { + if (post.getLinkPersonPost() == null || post.getLinkPersonPost() + .isEmpty()) { return null; } for (LinkPersonPost linkPersonPost : post.getLinkPersonPost()) { @@ -92,7 +94,8 @@ public void createPost(final Post post, final Person creator) { post.setPostAggregate(postAggregate); post.setActivityPubId(""); postRepository.save(post); // @todo fix second save making post look edited right away - post.setActivityPubId("%s/post/%d".formatted(post.getInstance().getDomain(), post.getId())); + post.setActivityPubId("%s/post/%d".formatted(post.getInstance() + .getDomain(), post.getId())); postRepository.save(post); linkPersonPostService.createLink(creator, post, LinkPersonPostType.creator); @@ -125,7 +128,8 @@ public List deleteAllPostsByPerson(final Person person) { @Transactional public void removeAllPostsFromCommunityAndUser(final Community community, final Person person, - final boolean removed) { + final boolean removed) + { postRepository.allPostsByCommunityAndPersonAndRemoved(community, person, List.of(removed ? RemovedState.NOT_REMOVED : RemovedState.REMOVED_BY_COMMUNITY)) From dbf7b5d6677c16863d2434b9c7b403b0a181ade2 Mon Sep 17 00:00:00 2001 From: rooki Date: Sun, 9 Jun 2024 13:19:11 +0200 Subject: [PATCH 056/115] Remove unnecessary comment from SQL migration script The diff shows a particular comment section being removed from a SQL migration script. The comment is no longer relevant or necessary, thereby enhancing the clarity and readability of the code. Signed-off-by: rooki --- .../db/migration/V20231003__Create_initial_entity_tables.sql | 4 ---- 1 file changed, 4 deletions(-) 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 f7b4be90..fc7e0d34 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 @@ -11,10 +11,6 @@ BEGIN END; $$ language 'plpgsql'; -/** - A that checks if a search_vector is the same as a text with the @@ operator - */ - CREATE OR REPLACE FUNCTION fn_search_vector_is_same(search_vector TSVECTOR, text TEXT) RETURNS BOOLEAN AS $$ From dec9b68c283f74dbc0fc8e1b56f6fa0477678e7f Mon Sep 17 00:00:00 2001 From: rooki Date: Sun, 16 Jun 2024 20:44:36 +0200 Subject: [PATCH 057/115] Add announcement functionality to Sublinks API This update added announcement handling functionality to Sublinks API. It includes creation of new model classes, updating service layers with query methods, and CRUD operations in the controller. It also added local people filtering to the `SublinksPersonService`. Signed-off-by: rooki --- .../SublinksAnnouncementController.java | 56 +++++++++++----- .../models/AnnouncementResponse.java | 20 ++++++ .../models/CreateAnnouncement.java | 24 +++++++ .../annoucement/models/IndexAnnouncement.java | 37 ++++++++++ .../services/SublinksAnnouncementService.java | 67 +++++++++++++++++++ .../v1/person/models/IndexPerson.java | 40 +++++++++-- .../v1/person/models/LoginPerson.java | 18 +++-- .../services/SublinksPersonService.java | 30 +++++++-- .../services/SublinksSearchService.java | 2 +- .../enums/RolePermissionInstanceTypes.java | 8 ++- .../services/InitialRoleSetupService.java | 2 + .../person/repositories/PersonRepository.java | 8 +++ 12 files changed, 276 insertions(+), 36 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/AnnouncementResponse.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CreateAnnouncement.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/IndexAnnouncement.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java index f6f0c7eb..92f04238 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java @@ -1,10 +1,17 @@ package com.sublinks.sublinksapi.api.sublinks.v1.annoucement.controllers; +import com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models.AnnouncementResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models.IndexAnnouncement; +import com.sublinks.sublinksapi.api.sublinks.v1.annoucement.services.SublinksAnnouncementService; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import java.util.Optional; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -17,48 +24,61 @@ @Tag(name = "Announcement", description = "Announcement API") public class SublinksAnnouncementController extends AbstractSublinksApiController { + private final SublinksAnnouncementService sublinksAnnouncementService; + + public SublinksAnnouncementController(SublinksAnnouncementService sublinksAnnouncementService) { + + super(); + this.sublinksAnnouncementService = sublinksAnnouncementService; + } + @Operation(summary = "Get a list of announcements") @GetMapping @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void index() { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List index(final IndexAnnouncement indexAnnouncementForm, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Optional person = getOptionalPerson(sublinksJwtPerson); + + return sublinksAnnouncementService.index(indexAnnouncementForm, person.orElse(null)); } @Operation(summary = "Get a specific announcement") - @GetMapping("/{id}") + @GetMapping("/{key}") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void show(@PathVariable String id) { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public AnnouncementResponse show(@PathVariable String key, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Optional person = getOptionalPerson(sublinksJwtPerson); + + return sublinksAnnouncementService.show(Long.parseLong(key), person.orElse(null)); } @Operation(summary = "Create a new announcement") @PostMapping @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void create() { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public AnnouncementResponse create() { // TODO: implement } @Operation(summary = "Update an announcement") @PostMapping("/{id}") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void update(@PathVariable String id) { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public AnnouncementResponse update(@PathVariable String id) { // TODO: implement } @Operation(summary = "Delete an announcement") @DeleteMapping("/{id}") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void delete(@PathVariable String id) { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public AnnouncementResponse delete(@PathVariable String id) { // TODO: implement } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/AnnouncementResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/AnnouncementResponse.java new file mode 100644 index 00000000..562d620b --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/AnnouncementResponse.java @@ -0,0 +1,20 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + +public record AnnouncementResponse( + @Schema(description = "The key of the announcement", + requiredMode = RequiredMode.REQUIRED, + example = "1") String key, + @Schema(description = "The content of the announcement", + requiredMode = RequiredMode.REQUIRED, + example = "This is an announcement") String content, + @Schema(description = "The created at date", + requiredMode = RequiredMode.REQUIRED, + example = "2021-01-01T00:00:00Z") String createdAt, + @Schema(description = "The updated at date", + requiredMode = RequiredMode.REQUIRED, + example = "2021-01-01T00:00:00Z") String updatedAt) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CreateAnnouncement.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CreateAnnouncement.java new file mode 100644 index 00000000..80ca8b4e --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CreateAnnouncement.java @@ -0,0 +1,24 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +public record CreateAnnouncement( + @Schema(description = "The content of the announcement", + requiredMode = RequiredMode.REQUIRED) String content) { + + public CreateAnnouncement { + + if (content == null) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "content_required"); + } + } + + @Override + public String content() { + + return content == null ? "" : content; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/IndexAnnouncement.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/IndexAnnouncement.java new file mode 100644 index 00000000..387020ac --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/IndexAnnouncement.java @@ -0,0 +1,37 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortOrder; +import com.sublinks.sublinksapi.utils.PaginationUtils; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + +public record IndexAnnouncement( + @Schema(description = "The sort order", + requiredMode = RequiredMode.NOT_REQUIRED, + defaultValue = "Asc") SortOrder sortOrder, + @Schema(description = "The page", + requiredMode = RequiredMode.NOT_REQUIRED, + defaultValue = "1") Integer page, + @Schema(description = "The number of items per page", + requiredMode = RequiredMode.NOT_REQUIRED, + defaultValue = "20") Integer perPage) { + + + @Override + public SortOrder sortOrder() { + + return sortOrder == null ? SortOrder.Asc : sortOrder; + } + + @Override + public Integer page() { + + return page == null ? 1 : Math.max(1, page); + } + + @Override + public Integer perPage() { + + return perPage == null ? 20 : PaginationUtils.Clamp(perPage, 1, 20); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java new file mode 100644 index 00000000..c1b36fe1 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java @@ -0,0 +1,67 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.annoucement.services; + +import com.sublinks.sublinksapi.announcement.entities.Announcement; +import com.sublinks.sublinksapi.announcement.repositories.AnnouncementRepository; +import com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models.AnnouncementResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models.IndexAnnouncement; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortOrder; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionInstanceTypes; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; +import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; +import com.sublinks.sublinksapi.person.entities.Person; +import java.util.List; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.ConversionService; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +@AllArgsConstructor +@Service +public class SublinksAnnouncementService { + + private final LocalInstanceContext localInstanceContext; + private final ConversionService conversionService; + private final RolePermissionService rolePermissionService; + private final AnnouncementRepository announcementRepository; + + public List index(final IndexAnnouncement indexAnnouncementForm, + final Person person) + { + + rolePermissionService.isPermitted(person, + RolePermissionInstanceTypes.INSTANCE_READ_ANNOUNCEMENTS, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "forbidden_to_read_announcements")); + + final List announcements = this.announcementRepository.findAll( + PageRequest.of(indexAnnouncementForm.page(), indexAnnouncementForm.perPage(), Sort.by( + indexAnnouncementForm.sortOrder() == SortOrder.Desc ? Direction.DESC : Direction.ASC, + "createdAt"))) + .toList(); + + return announcements.stream() + .map((announcement) -> this.conversionService.convert(announcement, + AnnouncementResponse.class)) + .toList(); + } + + public AnnouncementResponse show(final Long key, final Person person) { + + rolePermissionService.isPermitted(person, + RolePermissionInstanceTypes.INSTANCE_READ_ANNOUNCEMENT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "forbidden_to_read_announcement")); + + try { + final Announcement announcement = this.announcementRepository.getReferenceById(key); + + return this.conversionService.convert(announcement, AnnouncementResponse.class); + } catch (Exception e) { + throw new ResponseStatusException(HttpStatus.NOT_FOUND, "announcement_not_found"); + } + } + + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java index fcb01edf..ece6d667 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/IndexPerson.java @@ -2,14 +2,44 @@ import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import lombok.Builder; @Builder public record IndexPerson( - String search, - SublinksListingType sublinksListingType, - SortType sortType, - Integer perPage, - Integer page) { + @Schema(description = "The search query", + requiredMode = RequiredMode.NOT_REQUIRED) String search, + @Schema(description = "The listing type", + requiredMode = RequiredMode.NOT_REQUIRED, + defaultValue = "All") SublinksListingType listingType, + @Schema(description = "The sort type", + requiredMode = RequiredMode.NOT_REQUIRED, + defaultValue = "New") SortType sortType, + @Schema(description = "The page", + requiredMode = RequiredMode.NOT_REQUIRED, + defaultValue = "1") Integer page, + @Schema(description = "The number of items per page", + requiredMode = RequiredMode.NOT_REQUIRED, + defaultValue = "20") Integer perPage) { + @Override + public SublinksListingType listingType() { + return listingType == null ? SublinksListingType.Local : listingType; + } + + @Override + public SortType sortType() { + return sortType == null ? SortType.New : sortType; + } + + @Override + public Integer page() { + return page == null ? 1 : page; + } + + @Override + public Integer perPage() { + return perPage == null ? 20 : perPage; + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginPerson.java index 1a6829c4..955c58b1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/LoginPerson.java @@ -1,15 +1,25 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.models; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import lombok.Builder; import org.springframework.http.HttpStatus; import org.springframework.web.server.ResponseStatusException; @Builder public record LoginPerson( - String username, - String password, - String captcha_token, - String captcha_answer) { + @Schema(description = "The username", + requiredMode = RequiredMode.NOT_REQUIRED, + example = "john_doe") String username, + @Schema(description = "The password", + requiredMode = RequiredMode.NOT_REQUIRED, + example = "topsecretpasswordnooneknows") String password, + @Schema(description = "The captcha token", + requiredMode = RequiredMode.NOT_REQUIRED, + example = "03AGdBq26") String captcha_token, + @Schema(description = "The captcha answer", + requiredMode = RequiredMode.NOT_REQUIRED, + example = "3") String captcha_answer) { public LoginPerson { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index 3146c8ee..7b69b37f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -3,6 +3,7 @@ import com.sublinks.sublinksapi.api.lemmy.v3.enums.RegistrationMode; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtUtil; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortOrder; +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.moderation.IndexBannedPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.enums.RegistrationState; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.CreatePerson; @@ -113,18 +114,33 @@ public PersonIdentity getPersonIdentifiersFromKey(String key) { public List index(final IndexPerson indexPerson) { if (indexPerson.search() == null) { - return personRepository.findAll( - PageRequest.of(Math.max(indexPerson.page() != null ? indexPerson.page() : 1, 1), - PaginationUtils.Clamp(indexPerson.perPage() != null ? indexPerson.perPage() : 20, 1, - 20))) + + if (indexPerson.listingType() == SublinksListingType.Local) { + return personRepository.findAllByLocal(true, PageRequest.of(Math.max(indexPerson.page(), 1), + PaginationUtils.Clamp(indexPerson.perPage(), 1, 20))) + .stream() + .map(person -> conversionService.convert(person, PersonResponse.class)) + .toList(); + } + return personRepository.findAll(PageRequest.of(Math.max(indexPerson.page(), 1), + PaginationUtils.Clamp(indexPerson.perPage(), 1, 20))) + .stream() + .map(person -> conversionService.convert(person, PersonResponse.class)) + .toList(); + } + + if (indexPerson.listingType() == SublinksListingType.Local) { + return personRepository.findAllByNameAndBiographyAndLocal(indexPerson.search(), true, + PageRequest.of(Math.max(indexPerson.page(), 1), + PaginationUtils.Clamp(indexPerson.perPage(), 1, 20))) .stream() .map(person -> conversionService.convert(person, PersonResponse.class)) .toList(); } + return personRepository.findAllByNameAndBiography(indexPerson.search(), - PageRequest.of(Math.max(indexPerson.page() != null ? indexPerson.page() : 1, 1), - PaginationUtils.Clamp(indexPerson.perPage() != null ? indexPerson.perPage() : 20, 1, - 20))) + PageRequest.of(Math.max(indexPerson.page(), 1), + PaginationUtils.Clamp(indexPerson.perPage(), 1, 20))) .stream() .map(person -> conversionService.convert(person, PersonResponse.class)) .toList(); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java index f1de81cf..d2e6fd32 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java @@ -62,7 +62,7 @@ public SearchResponse list(final Search searchForm, final Optional perso .search(search) .page(page) .perPage(perPage) - .sublinksListingType(searchForm.listingType()) + .listingType(searchForm.listingType()) .sortType(searchForm.type()) .build())); } diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionInstanceTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionInstanceTypes.java index cc118e2b..2a8be60f 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionInstanceTypes.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionInstanceTypes.java @@ -19,7 +19,13 @@ public enum RolePermissionInstanceTypes implements RolePermissionInterface { INSTANCE_REMOVE_ADMIN("instance-admin", AuthorizeAction.DELETE), INSTANCE_SEARCH("instance-search", AuthorizeAction.READ), REPORT_INSTANCE_READ("report-instance", AuthorizeAction.READ), - REPORT_INSTANCE_RESOLVE("report-instance", AuthorizeAction.UPDATE); + REPORT_INSTANCE_RESOLVE("report-instance", AuthorizeAction.UPDATE), + INSTANCE_READ_ANNOUNCEMENT("instance-announcement", AuthorizeAction.READ), + INSTANCE_READ_ANNOUNCEMENTS("instance-announcements", AuthorizeAction.READ), + INSTANCE_CREATE_ANNOUNCEMENT("instance-announcement", AuthorizeAction.CREATE), + INSTANCE_UPDATE_ANNOUNCEMENT("instance-announcement", AuthorizeAction.UPDATE), + INSTANCE_DELETE_ANNOUNCEMENT("instance-announcement", AuthorizeAction.DELETE); + public final String entity; public final AuthorizeAction action; diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java index 364f6ebd..e9afbbce 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java @@ -82,6 +82,8 @@ private void applyCommonPermissions(Set rolePermissions rolePermissions.add(RolePermissionCommunityTypes.READ_COMMUNITY_MODERATORS); rolePermissions.add(RolePermissionPersonTypes.USER_LOGIN); rolePermissions.add(RolePermissionInstanceTypes.INSTANCE_SEARCH); + rolePermissions.add(RolePermissionInstanceTypes.INSTANCE_READ_ANNOUNCEMENT); + rolePermissions.add(RolePermissionInstanceTypes.INSTANCE_READ_ANNOUNCEMENTS); } /** diff --git a/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java b/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java index f8568afe..9447a758 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java @@ -25,11 +25,19 @@ public interface PersonRepository extends JpaRepository { HashSet findAllByRole(Role role); + List findAllByLocal(Boolean local, Pageable pageable); + @Query(value = "SELECT p FROM people p WHERE p.search_vector @@ to_tsquery('keyword', :keyword)", countQuery = "SELECT COUNT(p.id) FROM people p WHERE p.search_vector @@ to_tsquery('english', :keyword)", nativeQuery = true) List findAllByNameAndBiography(@Param("keyword") String keyword, Pageable pageable); + @Query(value = "SELECT p FROM people p WHERE p.search_vector @@ to_tsquery('keyword', :keyword) AND p.is_local = :local", + countQuery = "SELECT COUNT(p.id) FROM people p WHERE p.search_vector @@ to_tsquery('english', :keyword)", + nativeQuery = true) + List findAllByNameAndBiographyAndLocal(@Param("keyword") String keyword, + @Param("local") Boolean local, Pageable pageable); + @Query(value = "SELECT p FROM people p WHERE p.search_vector @@ to_tsquery('keyword', :keyword) AND p.role_id = :role", countQuery = "SELECT COUNT(p.id) FROM people p WHERE p.search_vector @@ to_tsquery('english', :keyword) AND p.role_id = :role", nativeQuery = true) From 6977dffd5cb2cda90939d547ebe7ea988da8efdc Mon Sep 17 00:00:00 2001 From: rooki Date: Mon, 17 Jun 2024 14:50:08 +0200 Subject: [PATCH 058/115] Add CRUD operations and models for announcements The commit introduces full CRUD functionality for announcements in the Sublinks API. It includes creation of new Announcement models with related update and removal forms. Implementation includes service updates, repository additions, and corresponding changes in the Announcement Controller. Signed-off-by: rooki --- .../announcement/entities/Announcement.java | 3 + .../SublinksAnnouncementController.java | 32 +++-- .../models/CreateAnnouncement.java | 6 - .../models/RemoveAnnouncement.java | 18 +++ .../models/UpdateAnnouncement.java | 18 +++ .../services/SublinksAnnouncementService.java | 122 +++++++++++++++++- ...ublinksCommunityAggregationController.java | 19 +-- ...20231003__Create_initial_entity_tables.sql | 1 + 8 files changed, 184 insertions(+), 35 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/RemoveAnnouncement.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/UpdateAnnouncement.java diff --git a/src/main/java/com/sublinks/sublinksapi/announcement/entities/Announcement.java b/src/main/java/com/sublinks/sublinksapi/announcement/entities/Announcement.java index 238b30d3..e2f4f0a2 100644 --- a/src/main/java/com/sublinks/sublinksapi/announcement/entities/Announcement.java +++ b/src/main/java/com/sublinks/sublinksapi/announcement/entities/Announcement.java @@ -39,6 +39,9 @@ public class Announcement { @Column(name = "content") private String content; + @Column(name = "is_active") + private Boolean active; + @Column(name = "local_site_id") private Long localSiteId; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java index 92f04238..575e6712 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java @@ -1,7 +1,9 @@ package com.sublinks.sublinksapi.api.sublinks.v1.annoucement.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models.AnnouncementResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models.CreateAnnouncement; import com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models.IndexAnnouncement; +import com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models.UpdateAnnouncement; import com.sublinks.sublinksapi.api.sublinks.v1.annoucement.services.SublinksAnnouncementService; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; @@ -12,6 +14,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.Optional; +import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -22,16 +25,11 @@ @RestController @RequestMapping("api/v1/announcement") @Tag(name = "Announcement", description = "Announcement API") +@AllArgsConstructor public class SublinksAnnouncementController extends AbstractSublinksApiController { private final SublinksAnnouncementService sublinksAnnouncementService; - public SublinksAnnouncementController(SublinksAnnouncementService sublinksAnnouncementService) { - - super(); - this.sublinksAnnouncementService = sublinksAnnouncementService; - } - @Operation(summary = "Get a list of announcements") @GetMapping @ApiResponses(value = { @@ -62,23 +60,33 @@ public AnnouncementResponse show(@PathVariable String key, @PostMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public AnnouncementResponse create() { - // TODO: implement + public AnnouncementResponse create(final CreateAnnouncement createAnnouncementForm, final SublinksJwtPerson sublinksJwtPerson) { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksAnnouncementService.create(createAnnouncementForm, person); } @Operation(summary = "Update an announcement") @PostMapping("/{id}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public AnnouncementResponse update(@PathVariable String id) { - // TODO: implement + public AnnouncementResponse update(@PathVariable final String id, final UpdateAnnouncement updateAnnouncementForm, final SublinksJwtPerson sublinksJwtPerson) { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksAnnouncementService.update(Long.parseLong(id), updateAnnouncementForm, person); } @Operation(summary = "Delete an announcement") @DeleteMapping("/{id}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public AnnouncementResponse delete(@PathVariable String id) { - // TODO: implement + public AnnouncementResponse delete(@PathVariable final String id, final SublinksJwtPerson sublinksJwtPerson) { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksAnnouncementService.delete(Long.parseLong(id), person); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CreateAnnouncement.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CreateAnnouncement.java index 80ca8b4e..31006b6c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CreateAnnouncement.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CreateAnnouncement.java @@ -15,10 +15,4 @@ public record CreateAnnouncement( throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "content_required"); } } - - @Override - public String content() { - - return content == null ? "" : content; - } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/RemoveAnnouncement.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/RemoveAnnouncement.java new file mode 100644 index 00000000..7ac8a931 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/RemoveAnnouncement.java @@ -0,0 +1,18 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + +public record RemoveAnnouncement( + @Schema(description = "The new content of the announcement", + requiredMode = RequiredMode.NOT_REQUIRED, + defaultValue = "true") Boolean removed, + @Schema(description = "The reason for removing the announcement", + requiredMode = RequiredMode.NOT_REQUIRED) String reason) { + + @Override + public Boolean removed() { + + return removed == null || removed; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/UpdateAnnouncement.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/UpdateAnnouncement.java new file mode 100644 index 00000000..d988a485 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/UpdateAnnouncement.java @@ -0,0 +1,18 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; + +public record UpdateAnnouncement( + @Schema(description = "The new content of the announcement", + requiredMode = RequiredMode.REQUIRED) String content) { + + public UpdateAnnouncement { + + if (content == null) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "content_required"); + } + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java index c1b36fe1..835f47bb 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java @@ -3,12 +3,16 @@ import com.sublinks.sublinksapi.announcement.entities.Announcement; import com.sublinks.sublinksapi.announcement.repositories.AnnouncementRepository; import com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models.AnnouncementResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models.CreateAnnouncement; import com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models.IndexAnnouncement; +import com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models.RemoveAnnouncement; +import com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models.UpdateAnnouncement; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortOrder; import com.sublinks.sublinksapi.authorization.enums.RolePermissionInstanceTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; import com.sublinks.sublinksapi.person.entities.Person; +import jakarta.persistence.EntityNotFoundException; import java.util.List; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; @@ -28,6 +32,16 @@ public class SublinksAnnouncementService { private final RolePermissionService rolePermissionService; private final AnnouncementRepository announcementRepository; + /** + * Retrieves a list of AnnouncementResponse objects based on the provided indexAnnouncementForm + * and person. + * + * @param indexAnnouncementForm The form containing the sorting and pagination details. + * @param person The person requesting the announcements. + * @return Returns a list of AnnouncementResponse objects representing the retrieved + * announcements. + * @throws ResponseStatusException if the person is not allowed to read the announcements. + */ public List index(final IndexAnnouncement indexAnnouncementForm, final Person person) { @@ -48,6 +62,17 @@ public List index(final IndexAnnouncement indexAnnouncemen .toList(); } + /** + * Retrieves the announcement with the given key and converts it to an + * {@link AnnouncementResponse} object. + * + * @param key The key of the announcement to be retrieved. + * @param person The person requesting the announcement. + * @return Returns the converted {@link AnnouncementResponse} object representing the retrieved + * announcement. + * @throws ResponseStatusException if the person is not allowed to read the announcement or if the + * announcement does not exist. + */ public AnnouncementResponse show(final Long key, final Person person) { rolePermissionService.isPermitted(person, @@ -58,10 +83,105 @@ public AnnouncementResponse show(final Long key, final Person person) { final Announcement announcement = this.announcementRepository.getReferenceById(key); return this.conversionService.convert(announcement, AnnouncementResponse.class); - } catch (Exception e) { + } catch (EntityNotFoundException e) { throw new ResponseStatusException(HttpStatus.NOT_FOUND, "announcement_not_found"); } } + /** + * Creates a new announcement based on the provided form and person. + * + * @param createAnnouncementForm The form containing the details of the announcement to be + * created. + * @param person The person creating the announcement. + * @return Returns an AnnouncementResponse object representing the created announcement. + */ + public AnnouncementResponse create(final CreateAnnouncement createAnnouncementForm, + final Person person) + { + + rolePermissionService.isPermitted(person, + RolePermissionInstanceTypes.INSTANCE_CREATE_ANNOUNCEMENT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "forbidden_to_create_announcement")); + + final Announcement announcement = Announcement.builder() + .content(createAnnouncementForm.content()) + .localSiteId(localInstanceContext.instance() + .getId()) + .build(); + + return this.conversionService.convert(announcement, AnnouncementResponse.class); + } + + /** + * Updates an announcement based on the given ID, update form, and person. + * + * @param id The ID of the announcement to be updated. + * @param updateAnnouncementForm The form containing the updated details. + * @param person The person making the update. + * @return Returns an AnnouncementResponse object representing the updated announcement. + */ + public AnnouncementResponse update(final Long id, final UpdateAnnouncement updateAnnouncementForm, + final Person person) + { + + rolePermissionService.isPermitted(person, + RolePermissionInstanceTypes.INSTANCE_UPDATE_ANNOUNCEMENT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "forbidden_to_update_announcement")); + final Announcement announcement = this.announcementRepository.getReferenceById(id); + + announcement.setContent(updateAnnouncementForm.content()); + this.announcementRepository.save(announcement); + return conversionService.convert(announcement, AnnouncementResponse.class); + } + + /** + * Deletes an announcement based on the given ID and person. + * + * @param id The ID of the announcement to be deleted. + * @param removeAnnouncementForm The form containing the removal details. + * @param person The person making the deletion. + * @return Returns an {@link AnnouncementResponse} object representing the deleted announcement. + */ + public AnnouncementResponse remove(final Long id, final RemoveAnnouncement removeAnnouncementForm, + final Person person) + { + + rolePermissionService.isPermitted(person, + RolePermissionInstanceTypes.INSTANCE_DELETE_ANNOUNCEMENT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "forbidden_to_delete_announcement")); + + final Announcement announcement = this.announcementRepository.getReferenceById(id); + + announcement.setActive(removeAnnouncementForm.removed()); + + announcementRepository.save(announcement); + + return conversionService.convert(announcement, AnnouncementResponse.class); + } + + /** + * Deletes an announcement based on the given ID and person. + * + * @param id The ID of the announcement to be deleted. + * @param person The person making the deletion. + * @return Returns an {@link AnnouncementResponse} object representing the deleted announcement. + */ + public AnnouncementResponse delete(final Long id, final Person person) { + + rolePermissionService.isPermitted(person, + RolePermissionInstanceTypes.INSTANCE_DELETE_ANNOUNCEMENT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "forbidden_to_delete_announcement")); + + final Announcement announcement = this.announcementRepository.getReferenceById(id); + + announcementRepository.delete(announcement); + + return conversionService.convert(announcement, AnnouncementResponse.class); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java index 34593451..0ff6135d 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java @@ -3,9 +3,8 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityAggregateResponse; -import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommunityTypes; +import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; -import com.sublinks.sublinksapi.community.entities.CommunityAggregate; import com.sublinks.sublinksapi.community.repositories.CommunityAggregateRepository; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; @@ -15,12 +14,10 @@ import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; -import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ResponseStatusException; @RestController @AllArgsConstructor @@ -31,6 +28,7 @@ public class SublinksCommunityAggregationController extends AbstractSublinksApiC private final CommunityAggregateRepository communityAggregateRepository; private final ConversionService conversionService; private final RolePermissionService rolePermissionService; + private final SublinksCommunityService sublinksCommunityService; @Operation(summary = "Get a community aggregate") @GetMapping @@ -42,17 +40,6 @@ public CommunityAggregateResponse show(@PathVariable final String key, final Optional person = getOptionalPerson(sublinksJwtPerson); - rolePermissionService.isPermitted(person.orElse(null), - RolePermissionCommunityTypes.READ_COMMUNITY_AGGREGATION, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_read_community_aggregation")); - - final CommunityAggregate communityAggregate = communityAggregateRepository.findByCommunityKey( - key); - if (communityAggregate == null) { - throw new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found"); - } - - return conversionService.convert(communityAggregate, CommunityAggregateResponse.class); + return sublinksCommunityService.showAggregate(key, person.orElse(null)); } } 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 fc7e0d34..d2d0b7d7 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 @@ -652,6 +652,7 @@ CREATE TABLE announcements ( id BIGSERIAL PRIMARY KEY, content TEXT NOT NULL, + is_active BOOL NOT NULL DEFAULT true, local_site_id BIGINT NOT NULL, created_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL, updated_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL From 9f157db1f4bd7cb2f62a408320672eef40cc4abf Mon Sep 17 00:00:00 2001 From: rooki Date: Mon, 24 Jun 2024 07:48:00 +0200 Subject: [PATCH 059/115] Add 'active' status and 'creator' field to announcements Added an 'active' status field to announcements, allowing for them to be toggled on and off. The 'creator' field was also added to link announcements to the user who created them. Renamed a comment mapper class, added a few new mapper classes and adjusted controller methods to accommodate these changes. Meanwhile, made adjustments to database queries and schemas to align with these updates. Signed-off-by: rooki --- .../announcement/entities/Announcement.java | 10 +++++++ .../SublinksAnnouncementController.java | 14 +++++++-- .../mappers/SublinksAnnouncementMapper.java | 29 +++++++++++++++++++ .../models/AnnouncementResponse.java | 5 ++++ .../models/CreateAnnouncement.java | 11 ++++++- .../models/UpdateAnnouncement.java | 13 +++------ .../services/SublinksAnnouncementService.java | 13 +++++++-- ...Mapper.java => SublinksCommentMapper.java} | 4 +-- ...20231003__Create_initial_entity_tables.sql | 1 + ...0231006__Create_foreign_key_references.sql | 8 ++++- 10 files changed, 90 insertions(+), 18 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/mappers/SublinksAnnouncementMapper.java rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/{LemmyCommentMapper.java => SublinksCommentMapper.java} (94%) diff --git a/src/main/java/com/sublinks/sublinksapi/announcement/entities/Announcement.java b/src/main/java/com/sublinks/sublinksapi/announcement/entities/Announcement.java index e2f4f0a2..019fdb79 100644 --- a/src/main/java/com/sublinks/sublinksapi/announcement/entities/Announcement.java +++ b/src/main/java/com/sublinks/sublinksapi/announcement/entities/Announcement.java @@ -1,10 +1,13 @@ package com.sublinks.sublinksapi.announcement.entities; +import com.sublinks.sublinksapi.person.entities.Person; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; import java.util.Objects; import lombok.AllArgsConstructor; @@ -29,6 +32,13 @@ @Table(name = "announcements") public class Announcement { + /** + * Relationships. + */ + @ManyToOne + @JoinColumn(name = "creator_id") + private Person creator; + /** * Attributes. */ diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java index 575e6712..6465ee62 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java @@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -60,7 +61,9 @@ public AnnouncementResponse show(@PathVariable String key, @PostMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public AnnouncementResponse create(final CreateAnnouncement createAnnouncementForm, final SublinksJwtPerson sublinksJwtPerson) { + public AnnouncementResponse create(@RequestBody final CreateAnnouncement createAnnouncementForm, + final SublinksJwtPerson sublinksJwtPerson) + { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); @@ -71,7 +74,10 @@ public AnnouncementResponse create(final CreateAnnouncement createAnnouncementFo @PostMapping("/{id}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public AnnouncementResponse update(@PathVariable final String id, final UpdateAnnouncement updateAnnouncementForm, final SublinksJwtPerson sublinksJwtPerson) { + public AnnouncementResponse update(@PathVariable final String id, + @RequestBody final UpdateAnnouncement updateAnnouncementForm, + final SublinksJwtPerson sublinksJwtPerson) + { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); @@ -82,7 +88,9 @@ public AnnouncementResponse update(@PathVariable final String id, final UpdateAn @DeleteMapping("/{id}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public AnnouncementResponse delete(@PathVariable final String id, final SublinksJwtPerson sublinksJwtPerson) { + public AnnouncementResponse delete(@PathVariable final String id, + final SublinksJwtPerson sublinksJwtPerson) + { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/mappers/SublinksAnnouncementMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/mappers/SublinksAnnouncementMapper.java new file mode 100644 index 00000000..cd7f4d27 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/mappers/SublinksAnnouncementMapper.java @@ -0,0 +1,29 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.annoucement.mappers; + +import com.sublinks.sublinksapi.announcement.entities.Announcement; +import com.sublinks.sublinksapi.api.sublinks.v1.annoucement.models.AnnouncementResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.mappers.SublinksPersonMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, + uses = {SublinksPersonMapper.class}) +public abstract class SublinksAnnouncementMapper implements + Converter { + + @Mapping(target = "key", source = "announcement.id") + @Mapping(target = "content", source = "announcement.content") + @Mapping(target = "createdAt", + source = "announcement.createdAt", + dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "updatedAt", + source = "announcement.updatedAt", + dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + public abstract AnnouncementResponse convert(@Nullable Announcement announcement); + + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/AnnouncementResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/AnnouncementResponse.java index 562d620b..69624025 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/AnnouncementResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/AnnouncementResponse.java @@ -13,6 +13,11 @@ public record AnnouncementResponse( @Schema(description = "The created at date", requiredMode = RequiredMode.REQUIRED, example = "2021-01-01T00:00:00Z") String createdAt, + @Schema( + description = "The active status of the announcement", + requiredMode = RequiredMode.REQUIRED, + example = "true" + ) Boolean active, @Schema(description = "The updated at date", requiredMode = RequiredMode.REQUIRED, example = "2021-01-01T00:00:00Z") String updatedAt) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CreateAnnouncement.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CreateAnnouncement.java index 31006b6c..5928e163 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CreateAnnouncement.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/CreateAnnouncement.java @@ -7,7 +7,10 @@ public record CreateAnnouncement( @Schema(description = "The content of the announcement", - requiredMode = RequiredMode.REQUIRED) String content) { + requiredMode = RequiredMode.REQUIRED) String content, + @Schema(description = "The active status of the announcement", + requiredMode = RequiredMode.NOT_REQUIRED, + defaultValue = "true") Boolean active) { public CreateAnnouncement { @@ -15,4 +18,10 @@ public record CreateAnnouncement( throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "content_required"); } } + + @Override + public Boolean active() { + + return active == null || active; + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/UpdateAnnouncement.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/UpdateAnnouncement.java index d988a485..db2bc526 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/UpdateAnnouncement.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/models/UpdateAnnouncement.java @@ -2,17 +2,12 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; -import org.springframework.http.HttpStatus; -import org.springframework.web.server.ResponseStatusException; public record UpdateAnnouncement( @Schema(description = "The new content of the announcement", - requiredMode = RequiredMode.REQUIRED) String content) { + requiredMode = RequiredMode.NOT_REQUIRED) String content, + @Schema(description = "The new active status of the announcement", + requiredMode = RequiredMode.NOT_REQUIRED, + defaultValue = "true") Boolean active) { - public UpdateAnnouncement { - - if (content == null) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "content_required"); - } - } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java index 835f47bb..b64e3533 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java @@ -51,7 +51,7 @@ public List index(final IndexAnnouncement indexAnnouncemen () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "forbidden_to_read_announcements")); final List announcements = this.announcementRepository.findAll( - PageRequest.of(indexAnnouncementForm.page(), indexAnnouncementForm.perPage(), Sort.by( + PageRequest.of(indexAnnouncementForm.page() - 1, indexAnnouncementForm.perPage(), Sort.by( indexAnnouncementForm.sortOrder() == SortOrder.Desc ? Direction.DESC : Direction.ASC, "createdAt"))) .toList(); @@ -109,8 +109,12 @@ public AnnouncementResponse create(final CreateAnnouncement createAnnouncementFo .content(createAnnouncementForm.content()) .localSiteId(localInstanceContext.instance() .getId()) + .active(createAnnouncementForm.active()) + .creator(person) .build(); + this.announcementRepository.save(announcement); + return this.conversionService.convert(announcement, AnnouncementResponse.class); } @@ -133,7 +137,12 @@ public AnnouncementResponse update(final Long id, final UpdateAnnouncement updat final Announcement announcement = this.announcementRepository.getReferenceById(id); - announcement.setContent(updateAnnouncementForm.content()); + if(updateAnnouncementForm.content() != null){ + announcement.setContent(updateAnnouncementForm.content()); + } + if(updateAnnouncementForm.active() != null){ + announcement.setActive(updateAnnouncementForm.active()); + } this.announcementRepository.save(announcement); return conversionService.convert(announcement, AnnouncementResponse.class); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/LemmyCommentMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java similarity index 94% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/LemmyCommentMapper.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java index 37311816..0b7eff24 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/LemmyCommentMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java @@ -12,7 +12,7 @@ @Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksPersonMapper.class}) -public abstract class LemmyCommentMapper implements Converter { +public abstract class SublinksCommentMapper implements Converter { SublinksPersonMapper personMapper; @@ -36,4 +36,4 @@ public abstract class LemmyCommentMapper implements Converter Date: Tue, 25 Jun 2024 14:14:16 +0200 Subject: [PATCH 060/115] Refactor permissions checks in various services Moved permissions checks from controller to service layer to ensure better encapsulation. This change affects SublinksCommunityController, SublinksCommentAggregateController, and SublinksCommentModerationController. An ADMIN_UPDATE_COMMUNITY permission was also added in RolePermissionCommunityTypes. This improves code maintainability and readability. Signed-off-by: rooki --- .../SublinksCommentAggerateController.java | 4 ---- .../SublinksCommentModerationController.java | 9 --------- .../services/SublinksCommentService.java | 9 +++++++++ .../SublinksCommunityController.java | 13 ++++++------- .../services/SublinksCommunityService.java | 19 ++++++++++++++++--- .../enums/RolePermissionCommunityTypes.java | 1 + 6 files changed, 32 insertions(+), 23 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java index 7aacc9fa..50af3f34 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java @@ -39,10 +39,6 @@ public CommentAggregateResponse aggregate(@PathVariable final String key, final Optional person = getOptionalPerson(sublinksJwtPerson); - rolePermissionService.isPermitted(person.orElse(null), - RolePermissionCommentTypes.READ_COMMENT_AGGREGATE, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_view_not_permitted")); - return sublinksCommentService.aggregate(key, person.orElse(null)); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java index 451490cb..cfcfa4f0 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java @@ -7,7 +7,6 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.CommentRemove; import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; -import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommentTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; @@ -16,14 +15,12 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AllArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ResponseStatusException; @RestController @AllArgsConstructor @@ -59,9 +56,6 @@ public CommentResponse delete(@PathVariable final String key, final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - rolePermissionService.isPermitted(person, RolePermissionCommentTypes.DELETE_COMMENT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_delete_not_permitted")); - return sublinksCommentService.delete(key, commentDeleteForm, person); } @@ -75,9 +69,6 @@ public CommentResponse highlight(@PathVariable final String key, final CommentPi final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - rolePermissionService.isPermitted(person, RolePermissionCommentTypes.MODERATOR_PIN_COMMENT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_highlight_not_permitted")); - return sublinksCommentService.pin(key, commentPinForm, person); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index e7f950f1..c131af2a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -275,6 +275,9 @@ public CommentResponse remove(String key, CommentRemove commentRemoveForm, Perso */ public CommentResponse delete(String key, CommentDelete commentDeleteForm, Person person) { + rolePermissionService.isPermitted(person, RolePermissionCommentTypes.DELETE_COMMENT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_delete_not_permitted")); + Comment comment = commentRepository.findByPath(key) .orElseThrow( () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); @@ -304,6 +307,9 @@ public CommentResponse delete(String key, CommentDelete commentDeleteForm, Perso */ public CommentResponse pin(String key, CommentPin commentPinForm, Person person) { + rolePermissionService.isPermitted(person, RolePermissionCommentTypes.MODERATOR_PIN_COMMENT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_highlight_not_permitted")); + Comment comment = commentRepository.findByPath(key) .orElseThrow( () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); @@ -330,6 +336,9 @@ public CommentResponse pin(String key, CommentPin commentPinForm, Person person) */ public CommentAggregateResponse aggregate(String commentKey, Person person) { + rolePermissionService.isPermitted(person, RolePermissionCommentTypes.READ_COMMENT_AGGREGATE, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_view_not_permitted")); + return commentAggregateRepository.findByComment_Path(commentKey) .map(commentAggregate -> conversionService.convert(commentAggregate, CommentAggregateResponse.class)) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index 11a8cf0e..e20faef3 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -20,7 +20,6 @@ import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; -import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -29,7 +28,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ResponseStatusException; @RestController @AllArgsConstructor @@ -62,12 +60,13 @@ public List index( @GetMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommunityResponse show(@PathVariable final String key) { + public CommunityResponse show(@PathVariable final String key, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Optional person = getOptionalPerson(sublinksJwtPerson); - return communityRepository.findCommunityByTitleSlug(key) - .map(comm -> conversionService.convert(comm, CommunityResponse.class)) - .orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Community not found")); + return sublinksCommunityService.show(key, person.orElse(null)); } @Operation(summary = "Create a new community") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index 59cc061f..218ab3d1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -44,6 +44,18 @@ public class SublinksCommunityService { private final LinkPersonCommunityService linkPersonCommunityService; private final PersonRepository personRepository; + public CommunityResponse show(String key, Person person) { + + rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.READ_COMMUNITY, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_read_community")); + + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + + return conversionService.convert(community, CommunityResponse.class); + } /** * Retrieves a list of CommunityResponse objects based on the search criteria provided. @@ -56,7 +68,7 @@ public List index(IndexCommunity indexCommunityForm, Person p rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.READ_COMMUNITY, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_read_community")); + "not_authorized_to_read_communities")); com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType sortType = indexCommunityForm.sortType(); @@ -170,8 +182,9 @@ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommu if (!(linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)) - && rolePermissionService.isPermitted(person, - RolePermissionCommunityTypes.UPDATE_COMMUNITY))) { + && rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.UPDATE_COMMUNITY)) + && !rolePermissionService.isPermitted(person, + RolePermissionCommunityTypes.ADMIN_UPDATE_COMMUNITY)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_update_community"); } diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommunityTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommunityTypes.java index 38c265b8..ed25fe7d 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommunityTypes.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionCommunityTypes.java @@ -29,6 +29,7 @@ public enum RolePermissionCommunityTypes implements RolePermissionInterface { /** * Unused */ + ADMIN_UPDATE_COMMUNITY("community-admin", AuthorizeAction.UPDATE), ADMIN_REMOVE_COMMUNITY("community-admin", AuthorizeAction.REMOVE), ADMIN_ADD_COMMUNITY_MODERATOR("community-admin-moderator", AuthorizeAction.CREATE), ADMIN_REMOVE_COMMUNITY_MODERATOR("community-admin-moderator", AuthorizeAction.REMOVE), From 46def334f2766f132a681f8beb706ca42382cb93 Mon Sep 17 00:00:00 2001 From: rooki Date: Sat, 29 Jun 2024 16:21:40 +0200 Subject: [PATCH 061/115] Add functionality for handling user metadata Introduced new endpoints in the SublinksPersonSessionController to allow managing user metadata including getting, invalidating, and deleting metadata. Specific permissions have been added to govern these actions. Two new models, PersonSessionData and PersonSessionDataResponse, have been included to manage data transfer. Enhancements are made in services for consistent permission checks and handling new operations. Signed-off-by: rooki --- .../controllers/SublinksPersonController.java | 8 +- .../SublinksPersonModerationController.java | 2 - .../SublinksPersonSessionController.java | 102 ++++++++++++ .../mappers/SublinksPersonMetaDataMapper.java | 39 +++++ .../v1/person/models/PersonSessionData.java | 14 ++ .../models/PersonSessionDataResponse.java | 12 ++ .../services/SublinksPersonService.java | 156 +++++++++++++++++- .../services/SublinksSearchService.java | 2 +- .../enums/RolePermissionPersonTypes.java | 9 + .../services/InitialRoleSetupService.java | 4 + .../repositories/UserDataRepository.java | 1 + 11 files changed, 335 insertions(+), 14 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonSessionController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMetaDataMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonSessionData.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonSessionDataResponse.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java index ec94df5c..57607a01 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java @@ -52,7 +52,7 @@ public List index(@RequestParam(required = false) final IndexPer final Optional person = getOptionalPerson(principal); return sublinksPersonService.index(indexPerson == null ? IndexPerson.builder() - .build() : indexPerson); + .build() : indexPerson, person.orElse(null)); } @Operation(summary = "Get a specific person") @@ -63,7 +63,7 @@ public PersonResponse show(@PathVariable String key, final SublinksJwtPerson pri final Optional person = getOptionalPerson(principal); - return sublinksPersonService.show(key); + return sublinksPersonService.show(key, person.orElse(null)); } @Operation(summary = "Register a new person") @@ -107,7 +107,9 @@ public PersonResponse update(@PathVariable String key, @DeleteMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public PersonResponse delete(@RequestBody final DeletePerson deletePersonForm, @PathVariable String key, final SublinksJwtPerson principal) { + public PersonResponse delete(@RequestBody final DeletePerson deletePersonForm, + @PathVariable String key, final SublinksJwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java index 7ccb77f1..4ecfc52f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java @@ -12,7 +12,6 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AllArgsConstructor; -import org.springframework.core.convert.ConversionService; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -27,7 +26,6 @@ public class SublinksPersonModerationController extends AbstractSublinksApiController { private final SublinksPersonService sublinksPersonService; - private final ConversionService conversionService; @Operation(summary = "Ban a person") @GetMapping("/ban") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonSessionController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonSessionController.java new file mode 100644 index 00000000..009f97ed --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonSessionController.java @@ -0,0 +1,102 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonSessionDataResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; +import com.sublinks.sublinksapi.person.entities.Person; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("api/v1/session/") +@Tag(name = "Person Moderation", description = "Person Session API") +@AllArgsConstructor +public class SublinksPersonSessionController extends AbstractSublinksApiController { + + private final SublinksPersonService sublinksPersonService; + + @Operation(summary = "Get metadata for a person ( requires permission to view other peoples sessions )") + @GetMapping("/person/{personKey}") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PersonSessionDataResponse getMetaData(@PathVariable String personKey, + final SublinksJwtPerson jwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(jwtPerson); + + return sublinksPersonService.getMetaData(personKey, person); + } + + @GetMapping("/data/{sessionKey}") + @Operation(summary = "Get one metadata ( requires permission to view other peoples sessions )") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PersonSessionDataResponse getOneMetaData(@PathVariable String sessionKey, + final SublinksJwtPerson jwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(jwtPerson); + + return sublinksPersonService.getMetaData(sessionKey, person); + } + + @Operation(summary = "Invalidate one metadata for a person ( requires permission to invalidate other peoples metadata )") + @DeleteMapping("/person/invalidate/{sessionKey}") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public void invalidateOneMetaData(@PathVariable String sessionKey, + final SublinksJwtPerson jwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(jwtPerson); + + sublinksPersonService.invalidateUserData(sessionKey, person); + } + + @Operation(summary = "Invalidate all metadata for a person ( requires permission to invalidate other peoples metadata )") + @DeleteMapping("/person/{personKey}/invalidate") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public void invalidateMetaData(@PathVariable String personKey, final SublinksJwtPerson jwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(jwtPerson); + + sublinksPersonService.invalidateAllUserData(personKey, person); + } + + @Operation(summary = "Deletes all metadata for a person ( requires permission to delete other peoples metadata )") + @DeleteMapping("/person/{personKey}") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public void deleteMetaData(@PathVariable String personKey, final SublinksJwtPerson jwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(jwtPerson); + + sublinksPersonService.deleteAllUserData(personKey, person); + } + + @Operation(summary = "Deletes one metadata for a person ( requires permission to delete other peoples metadata )") + @DeleteMapping("/person/{sessionKey}") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public void deleteOneMetaData(@PathVariable String sessionKey, final SublinksJwtPerson jwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(jwtPerson); + + sublinksPersonService.deleteUserData(sessionKey, person); + } + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMetaDataMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMetaDataMapper.java new file mode 100644 index 00000000..76e3c3a6 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMetaDataMapper.java @@ -0,0 +1,39 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.mappers; + +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonSessionData; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksRoleMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; +import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.person.entities.PersonMetaData; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.Named; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksRoleMapper.class}) +public abstract class SublinksPersonMetaDataMapper implements + Converter { + + @Override + @Mapping(target = "key", source = "metadata.id") + @Mapping(target = "ipAddress", source = "metadata.ipAddress") + @Mapping(target = "userAgent", source = "metadata.userAgent") + @Mapping(target = "active", source = "metadata.active") + @Mapping(target = "createdAt", + source = "metadata.createdAt", + dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "updatedAt", + source = "metadata.updatedAt", + dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + public abstract PersonSessionData convert( + @Nullable PersonMetaData metadata); + + @Named("personKey") + String mapPersonKey(Person person) { + + return person.getName() + "@" + person.getInstance() + .getDomain(); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonSessionData.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonSessionData.java new file mode 100644 index 00000000..b7eb757e --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonSessionData.java @@ -0,0 +1,14 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.models; + +import lombok.Builder; + +@Builder +public record PersonSessionData( + Long key, + String ipAddress, + String userAgent, + Boolean active, + String createdAt, + String updatedAt) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonSessionDataResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonSessionDataResponse.java new file mode 100644 index 00000000..b85e6b78 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonSessionDataResponse.java @@ -0,0 +1,12 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.person.models; + +import lombok.Builder; +import java.util.List; + +@Builder +public record PersonSessionDataResponse( + String personKey, + List sessions + ) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index 7b69b37f..d5dc4833 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -13,7 +13,9 @@ import com.sublinks.sublinksapi.api.sublinks.v1.person.models.LoginResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonAggregateResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonIdentity; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonSessionDataResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonSessionData; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.UpdatePerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.moderation.BanPerson; import com.sublinks.sublinksapi.authorization.entities.Role; @@ -31,11 +33,13 @@ import com.sublinks.sublinksapi.person.entities.Person; import com.sublinks.sublinksapi.person.entities.PersonAggregate; import com.sublinks.sublinksapi.person.entities.PersonEmailVerification; +import com.sublinks.sublinksapi.person.entities.PersonMetaData; import com.sublinks.sublinksapi.person.entities.PersonRegistrationApplication; import com.sublinks.sublinksapi.person.enums.PersonRegistrationApplicationStatus; import com.sublinks.sublinksapi.person.repositories.PersonAggregateRepository; import com.sublinks.sublinksapi.person.repositories.PersonRegistrationApplicationRepository; import com.sublinks.sublinksapi.person.repositories.PersonRepository; +import com.sublinks.sublinksapi.person.repositories.UserDataRepository; import com.sublinks.sublinksapi.person.services.PersonEmailVerificationService; import com.sublinks.sublinksapi.person.services.PersonRegistrationApplicationService; import com.sublinks.sublinksapi.person.services.PersonService; @@ -46,6 +50,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; @@ -77,6 +82,7 @@ public class SublinksPersonService { private final PostReportService postReportService; private final CommentReportService commentReportService; private final PrivateMessageReportService privateMessageReportService; + private final UserDataRepository userDataRepository; /** * Retrieves the name and domain identifiers of a person from the given key. @@ -111,21 +117,23 @@ public PersonIdentity getPersonIdentifiersFromKey(String key) { * @return A list of PersonResponse objects representing the persons that match the search * criteria. */ - public List index(final IndexPerson indexPerson) { + public List index(final IndexPerson indexPerson, final Person person) { - if (indexPerson.search() == null) { + rolePermissionService.isPermitted(person, RolePermissionPersonTypes.READ_USERS, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_read_persons")); + if (indexPerson.search() == null) { if (indexPerson.listingType() == SublinksListingType.Local) { return personRepository.findAllByLocal(true, PageRequest.of(Math.max(indexPerson.page(), 1), PaginationUtils.Clamp(indexPerson.perPage(), 1, 20))) .stream() - .map(person -> conversionService.convert(person, PersonResponse.class)) + .map(p -> conversionService.convert(p, PersonResponse.class)) .toList(); } return personRepository.findAll(PageRequest.of(Math.max(indexPerson.page(), 1), PaginationUtils.Clamp(indexPerson.perPage(), 1, 20))) .stream() - .map(person -> conversionService.convert(person, PersonResponse.class)) + .map(p -> conversionService.convert(p, PersonResponse.class)) .toList(); } @@ -134,7 +142,7 @@ public List index(final IndexPerson indexPerson) { PageRequest.of(Math.max(indexPerson.page(), 1), PaginationUtils.Clamp(indexPerson.perPage(), 1, 20))) .stream() - .map(person -> conversionService.convert(person, PersonResponse.class)) + .map(p -> conversionService.convert(p, PersonResponse.class)) .toList(); } @@ -142,7 +150,7 @@ public List index(final IndexPerson indexPerson) { PageRequest.of(Math.max(indexPerson.page(), 1), PaginationUtils.Clamp(indexPerson.perPage(), 1, 20))) .stream() - .map(person -> conversionService.convert(person, PersonResponse.class)) + .map(p -> conversionService.convert(p, PersonResponse.class)) .toList(); } @@ -155,12 +163,15 @@ public List index(final IndexPerson indexPerson) { * @return The PersonResponse object representing the person information. * @throws ResponseStatusException if the person is not found. */ - public PersonResponse show(final String key) { + public PersonResponse show(final String key, final Person person) { + + rolePermissionService.isPermitted(person, RolePermissionPersonTypes.READ_USER, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_read_person")); final PersonIdentity ids = getPersonIdentifiersFromKey(key); return personRepository.findOneByNameAndInstance_Domain(ids.name(), ids.domain()) - .map(person -> conversionService.convert(person, PersonResponse.class)) + .map(p -> conversionService.convert(p, PersonResponse.class)) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); } @@ -283,6 +294,9 @@ public LoginResponse login(final LoginPerson loginPersonForm, final String ip, final Person person = personRepository.findOneByNameIgnoreCase(loginPersonForm.username()) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + rolePermissionService.isPermitted(person, RolePermissionPersonTypes.USER_LOGIN, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_login")); + if (!rolePermissionService.isPermitted(person, RolePermissionPersonTypes.USER_LOGIN)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_login"); } @@ -341,6 +355,10 @@ public PersonResponse updatePerson(Person person, UpdatePerson updatePersonForm) rolePermissionService.isPermitted(person, RolePermissionPersonTypes.UPDATE_USER_SETTINGS, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_update_person")); + if (person.isDeleted()) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "person_deleted"); + } + if (Optional.ofNullable(updatePersonForm.languagesKeys()) .isPresent()) { person.setLanguages(languageRepository.findAllByCodeIsIn(updatePersonForm.languagesKeys())); @@ -503,4 +521,126 @@ public PersonResponse deletePerson(final String key, final DeletePerson deletePe return conversionService.convert(personToDelete, PersonResponse.class); } + + + public PersonSessionDataResponse getMetaData(final String targetKey, final Person person) { + + final PersonIdentity targetIds = getPersonIdentifiersFromKey(targetKey); + + final Person target = personRepository.findOneByNameAndInstance_Domain(targetIds.name(), + targetIds.domain()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + + if (!(rolePermissionService.isPermitted(person, + RolePermissionPersonTypes.READ_USER_OWN_METADATAS) && person.equals(target)) + && !rolePermissionService.isPermitted(person, + RolePermissionPersonTypes.READ_USER_METADATAS)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_read_person_metadata"); + } + + final List personMetaData = userDataRepository.findAllByPerson(target); + + return PersonSessionDataResponse.builder() + .personKey(targetIds.name() + "@" + targetIds.domain()) + .sessions(personMetaData.stream() + .map(m -> conversionService.convert(m, + PersonSessionData.class)) + .toList()) + .build(); + } + + public PersonSessionDataResponse getOneMetaData(final String targetsessionKey, final Person person) + { + + final PersonMetaData personMetaData = userDataRepository.findById( + Long.parseLong(targetsessionKey)) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_metadata_not_found")); + + if (!(rolePermissionService.isPermitted(person, + RolePermissionPersonTypes.READ_USER_OWN_METADATAS) && personMetaData.getPerson() + .equals(person)) && !rolePermissionService.isPermitted(person, + RolePermissionPersonTypes.READ_USER_METADATAS)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_read_person_metadatas"); + } + + return PersonSessionDataResponse.builder() + .personKey(personMetaData.getPerson() + .getName() + "@" + personMetaData.getPerson() + .getInstance() + .getDomain()) + .sessions(List.of(Objects.requireNonNull(conversionService.convert(personMetaData, + PersonSessionData.class)))) + .build(); + } + + public void invalidateUserData(String targetUserData, final Person person) { + + final PersonMetaData personMetaData = userDataRepository.findById( + Long.parseLong(targetUserData)) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_metadata_not_found")); + if (!(rolePermissionService.isPermitted(person, + RolePermissionPersonTypes.INVALIDATE_USER_OWN_METADATA) && personMetaData.getPerson() + .equals(person)) && !rolePermissionService.isPermitted(person, + RolePermissionPersonTypes.INVALIDATE_USER_METADATA)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_invalidate_person_metadata"); + } + + userDataService.invalidate(personMetaData); + } + + public void invalidateAllUserData(final String targetPersonKey, final Person person) { + + final Person target = personRepository.findOneByNameAndInstance_Domain(targetPersonKey, + localInstanceContext.instance() + .getDomain()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + if (!(rolePermissionService.isPermitted(person, + RolePermissionPersonTypes.INVALIDATE_USER_OWN_METADATA) && target.equals(person)) + && !rolePermissionService.isPermitted(person, + RolePermissionPersonTypes.INVALIDATE_USER_METADATA)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_invalidate_person_metadata"); + } + + userDataService.invalidateAllUserData(target); + } + + public void deleteUserData(final String targetUserDataKey, final Person person) { + + final PersonMetaData personMetaData = userDataRepository.findById( + Long.parseLong(targetUserDataKey)) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_metadata_not_found")); + if (rolePermissionService.isPermitted(person, + RolePermissionPersonTypes.DELETE_USER_OWN_METADATA) && !personMetaData.getPerson() + .equals(person) && !rolePermissionService.isPermitted(person, + RolePermissionPersonTypes.DELETE_USER_METADATA)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_delete_person_metadata"); + } + + userDataRepository.delete(personMetaData); + } + + public void deleteAllUserData(final String targetPersonKey, final Person person) + { + + final Person target = personRepository.findOneByNameAndInstance_Domain(targetPersonKey, + localInstanceContext.instance() + .getDomain()) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + if (!(rolePermissionService.isPermitted(person, + RolePermissionPersonTypes.DELETE_USER_OWN_METADATA) && target.equals(person)) + && !rolePermissionService.isPermitted(person, + RolePermissionPersonTypes.DELETE_USER_METADATA)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_invalidate_person_metadata"); + } + userDataRepository.deleteAll(userDataRepository.findAllByPerson(target)); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java index d2e6fd32..3692ef6a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java @@ -64,7 +64,7 @@ public SearchResponse list(final Search searchForm, final Optional perso .perPage(perPage) .listingType(searchForm.listingType()) .sortType(searchForm.type()) - .build())); + .build(), person.orElse(null))); } if (rolePermissionService.isPermitted(person.orElse(null), diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java index 462b676a..77858eae 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPersonTypes.java @@ -19,6 +19,15 @@ public enum RolePermissionPersonTypes implements RolePermissionInterface { USER_LOGIN("user-login", AuthorizeAction.READ), USER_EXPORT("user-export", AuthorizeAction.READ), + READ_USER_METADATA("user-metadata", AuthorizeAction.READ), + READ_USER_METADATAS("user-metadatas", AuthorizeAction.READ), + READ_USER_OWN_METADATA("user-own-metadata", AuthorizeAction.READ), + READ_USER_OWN_METADATAS("user-own-metadatas", AuthorizeAction.READ), + INVALIDATE_USER_METADATA("user-metadata", AuthorizeAction.UPDATE), + INVALIDATE_USER_OWN_METADATA("user-own-metadata", AuthorizeAction.UPDATE), + DELETE_USER_METADATA("user-metadata", AuthorizeAction.DELETE), + DELETE_USER_OWN_METADATA("user-own-metadata", AuthorizeAction.DELETE), + /** * Unused */ diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java index e9afbbce..49a97729 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java @@ -173,6 +173,10 @@ private void createRegisteredRole() { rolePermissions.add(RolePermissionPersonTypes.UPDATE_USER_SETTINGS); rolePermissions.add(RolePermissionPersonTypes.RESET_PASSWORD); rolePermissions.add(RolePermissionPersonTypes.USER_EXPORT); + rolePermissions.add(RolePermissionPersonTypes.READ_USER_OWN_METADATAS); + rolePermissions.add(RolePermissionPersonTypes.READ_USER_OWN_METADATA); + rolePermissions.add(RolePermissionPersonTypes.INVALIDATE_USER_OWN_METADATA); + rolePermissions.add(RolePermissionPersonTypes.DELETE_USER_OWN_METADATA); rolePermissions.add(RolePermissionPersonTypes.MARK_MENTION_AS_READ); rolePermissions.add(RolePermissionPersonTypes.MARK_REPLIES_AS_READ); rolePermissions.add(RolePermissionPersonTypes.READ_MENTION_USER); diff --git a/src/main/java/com/sublinks/sublinksapi/person/repositories/UserDataRepository.java b/src/main/java/com/sublinks/sublinksapi/person/repositories/UserDataRepository.java index 036a41b8..69c25a85 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/repositories/UserDataRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/person/repositories/UserDataRepository.java @@ -31,6 +31,7 @@ Optional findFirstByPersonAndTokenAndIpAddressAndActiveIsTrue(Pe List findAllByLastUsedAtBefore(Date lastUsedAt); + List findAllByPerson(Person person); @Modifying @Query("update PersonMetaData u set u.active = false where u.person = :person") From 9a0a7eb547a93063bf6d01fd09a6007ceeff065c Mon Sep 17 00:00:00 2001 From: rooki Date: Sat, 29 Jun 2024 18:24:02 +0200 Subject: [PATCH 062/115] Refactor security configuration in SecurityConfig.java This commit simplifies the code in the SecurityConfig.java file for the security configuration of the Lemmy V3 API. It removes some duplication in the code related to session management and the authorization of HTTP requests, and changes the order of the `addFilterBefore` method to execute after session management. Signed-off-by: rooki --- .../v3/authentication/config/SecurityConfig.java | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java index 666296e1..6c8c9fb5 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/config/SecurityConfig.java @@ -34,23 +34,14 @@ public class SecurityConfig { @Bean public SecurityFilterChain filterChain(final HttpSecurity http) throws Exception { - http.addFilterBefore(lemmyJwtFilter, UsernamePasswordAuthenticationFilter.class); http.csrf(AbstractHttpConfigurer::disable) .securityMatcher("/api/v3/**") .authorizeHttpRequests((requests) -> requests.anyRequest() .permitAll()) .sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy( - SessionCreationPolicy.STATELESS)); + SessionCreationPolicy.STATELESS)) + .addFilterBefore(lemmyJwtFilter, UsernamePasswordAuthenticationFilter.class); - http - .authorizeHttpRequests((requests) -> requests - .anyRequest().permitAll() - ) - .sessionManagement( - (sessionManagement) -> sessionManagement.sessionCreationPolicy( - SessionCreationPolicy.STATELESS) - ) - .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } } \ No newline at end of file From bd0ebc1f14dd2f970ce4143b249be04fe56a586a Mon Sep 17 00:00:00 2001 From: rooki Date: Sat, 29 Jun 2024 18:41:47 +0200 Subject: [PATCH 063/115] Rearrange JWT filter addition in SecurityConfig The JWT filter addition is rearranged in the SecurityConfig. Previously, it was added before configuring CSRF, securityMatcher, authorization, and sessionManagement. Now, it is added after configuring all these. This change ensures that the JWT filter gets executed at the correct point in the filter chain. Signed-off-by: rooki --- .../v1/authentication/config/SublinksSecurityConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SublinksSecurityConfig.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SublinksSecurityConfig.java index 7c7fe42c..afd917c8 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SublinksSecurityConfig.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/config/SublinksSecurityConfig.java @@ -34,13 +34,13 @@ public class SublinksSecurityConfig { @Bean public SecurityFilterChain sublinksFilterChain(final HttpSecurity http) throws Exception { - http.addFilterBefore(sublinksJwtFilter, UsernamePasswordAuthenticationFilter.class); http.csrf(AbstractHttpConfigurer::disable) .securityMatcher("/api/v1/**") .authorizeHttpRequests((requests) -> requests.anyRequest() .permitAll()) .sessionManagement((sessionManagement) -> sessionManagement.sessionCreationPolicy( - SessionCreationPolicy.STATELESS)); + SessionCreationPolicy.STATELESS)) + .addFilterBefore(sublinksJwtFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } From e99b1459ebf6cc8ae263f5114749e7a7d7e3339c Mon Sep 17 00:00:00 2001 From: rooki Date: Sun, 30 Jun 2024 15:04:31 +0200 Subject: [PATCH 064/115] Refactor SublinksPersonMetaDataMapper class The commit removes unused methods and imports from the SublinksPersonMetaDataMapper class. The 'updatedAt' mapping source has also been changed from 'metadata.updatedAt' to 'metadata.lastUsedAt'. Besides, the 'mapPersonKey' method and associated Person import have been removed since they are no longer being used. Signed-off-by: rooki --- .../mappers/SublinksPersonMetaDataMapper.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMetaDataMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMetaDataMapper.java index 76e3c3a6..dfeca37b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMetaDataMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMetaDataMapper.java @@ -3,12 +3,10 @@ import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonSessionData; import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksRoleMapper; import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; -import com.sublinks.sublinksapi.person.entities.Person; import com.sublinks.sublinksapi.person.entities.PersonMetaData; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; -import org.mapstruct.Named; import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; @@ -25,15 +23,7 @@ public abstract class SublinksPersonMetaDataMapper implements source = "metadata.createdAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) @Mapping(target = "updatedAt", - source = "metadata.updatedAt", + source = "metadata.lastUsedAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) - public abstract PersonSessionData convert( - @Nullable PersonMetaData metadata); - - @Named("personKey") - String mapPersonKey(Person person) { - - return person.getName() + "@" + person.getInstance() - .getDomain(); - } + public abstract PersonSessionData convert(@Nullable PersonMetaData metadata); } From d550b84acbbfadc15a5f8044a5b4ccac54e07e5d Mon Sep 17 00:00:00 2001 From: rooki Date: Sun, 30 Jun 2024 16:28:23 +0200 Subject: [PATCH 065/115] Refactor code and enhance User Data Management capabilities Significant improvements have been added to the management of user data and user sessions. The changes include the correction of certain URL Utilities, updates to the pagination functionalities, and enhancements to the handling of user metadata and validation mechanisms. Additionally, there are changes to the roles functionality, focusing on improving permissions handling, and also improvements on the structure of the API responses, and creation of essential utilities for person key handling. Signed-off-by: rooki --- .../sublinksapi/SublinksApiApplication.java | 8 ++ .../mappers/SublinksCommentMapper.java | 3 +- .../SublinksPersonSessionController.java | 4 +- .../SublinksPersonAggregationMapper.java | 11 +- .../person/mappers/SublinksPersonMapper.java | 11 +- .../v1/person/models/IndexPerson.java | 2 +- .../services/SublinksPersonService.java | 104 +++++++++++++++--- .../v1/roles/mappers/SublinksRoleMapper.java | 7 +- .../api/sublinks/v1/utils/PersonKeyUtils.java | 25 +++++ .../person/repositories/PersonRepository.java | 4 +- .../person/services/UserDataService.java | 32 +++--- .../sublinks/sublinksapi/utils/UrlUtil.java | 25 ++++- src/main/resources/application.properties | 1 - 13 files changed, 189 insertions(+), 48 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/PersonKeyUtils.java diff --git a/src/main/java/com/sublinks/sublinksapi/SublinksApiApplication.java b/src/main/java/com/sublinks/sublinksapi/SublinksApiApplication.java index 22b51feb..e9994859 100644 --- a/src/main/java/com/sublinks/sublinksapi/SublinksApiApplication.java +++ b/src/main/java/com/sublinks/sublinksapi/SublinksApiApplication.java @@ -13,6 +13,8 @@ import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableScheduling; +import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; +import org.springframework.web.util.UrlPathHelper; /** * Application Boot. @@ -76,4 +78,10 @@ public GroupedOpenApi v1SublinksApi(@Value("${springdoc.version}") String appVer .pathsToMatch("/api/v1/**") .build(); } + + public void configurePathMatch(PathMatchConfigurer configurer) { + UrlPathHelper urlPathHelper = new UrlPathHelper(); + urlPathHelper.setUrlDecode(false); + configurer.setUrlPathHelper(urlPathHelper); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java index 0b7eff24..d8139dba 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java @@ -14,8 +14,6 @@ uses = {SublinksPersonMapper.class}) public abstract class SublinksCommentMapper implements Converter { - SublinksPersonMapper personMapper; - @Override @Mapping(target = "key", source = "comment.path") @Mapping(target = "activityPubId", source = "comment.activityPubId") @@ -33,6 +31,7 @@ public abstract class SublinksCommentMapper implements Converter { + @Autowired + private PersonKeyUtils personKeyUtils; + @Override @Mapping(target = "personKey", source = "person", qualifiedByName = "personKey") @Mapping(target = "postCount", source = "postCount") @@ -26,7 +33,7 @@ public abstract class SublinksPersonAggregationMapper implements @Named("personKey") String mapPersonKey(Person person) { - return person.getName() + "@" + person.getInstance() - .getDomain(); + return personKeyUtils.getPersonKey(person); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java index 23b934fc..5e1737bb 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java @@ -3,20 +3,24 @@ import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksRoleMapper; import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; +import com.sublinks.sublinksapi.api.sublinks.v1.utils.PersonKeyUtils; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.utils.UrlUtil; import java.text.SimpleDateFormat; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; import org.mapstruct.Named; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksRoleMapper.class}) public abstract class SublinksPersonMapper implements Converter { - SublinksRoleMapper roleMapper; + @Autowired + public PersonKeyUtils personKeyUtils; @Override @Mapping(target = "key", source = "person", qualifiedByName = "personKey") @@ -28,7 +32,7 @@ public abstract class SublinksPersonMapper implements Converter index(final IndexPerson indexPerson, final Person pe if (indexPerson.search() == null) { if (indexPerson.listingType() == SublinksListingType.Local) { - return personRepository.findAllByLocal(true, PageRequest.of(Math.max(indexPerson.page(), 1), - PaginationUtils.Clamp(indexPerson.perPage(), 1, 20))) + return personRepository.findAllByIsLocal(true, + PageRequest.of(Math.max(indexPerson.page(), 0), + PaginationUtils.Clamp(indexPerson.perPage(), 1, 20))) .stream() .map(p -> conversionService.convert(p, PersonResponse.class)) .toList(); @@ -523,6 +527,20 @@ public PersonResponse deletePerson(final String key, final DeletePerson deletePe } + /** + * Retrieves the meta data of a person's sessions based on the provided target key and person. + * + * @param targetKey The key containing the target person's information. If the key contains "@", + * it is split into name and domain using "@" as the separator. Otherwise, the + * name is set as the key and the domain is obtained from the local instance + * context. + * @param person The Person object representing the person making the request. + * @return The PersonSessionDataResponse object containing the meta data of the target person's + * sessions. + * @throws ResponseStatusException If the person is not authorized to read the person's meta data + * or if the person does not have permission to read the target + * person's meta data or if the target person is not found. + */ public PersonSessionDataResponse getMetaData(final String targetKey, final Person person) { final PersonIdentity targetIds = getPersonIdentifiersFromKey(targetKey); @@ -544,13 +562,24 @@ public PersonSessionDataResponse getMetaData(final String targetKey, final Perso return PersonSessionDataResponse.builder() .personKey(targetIds.name() + "@" + targetIds.domain()) .sessions(personMetaData.stream() - .map(m -> conversionService.convert(m, - PersonSessionData.class)) + .map(m -> conversionService.convert(m, PersonSessionData.class)) .toList()) .build(); } - public PersonSessionDataResponse getOneMetaData(final String targetsessionKey, final Person person) + /** + * Retrieves the meta data of a single session belonging to a person based on the provided session + * key and person. + * + * @param targetSessionKey The session key containing the session's information. + * @param person The Person object representing the person making the request. + * @return The PersonSessionDataResponse object containing the meta data of the session. + * @throws ResponseStatusException If the person is not authorized to read the person's meta data + * or if the person does not have permission to read the session's + * meta data or if the session is not found. + */ + public PersonSessionDataResponse getOneMetaData(final String targetsessionKey, + final Person person) { final PersonMetaData personMetaData = userDataRepository.findById( @@ -571,11 +600,21 @@ public PersonSessionDataResponse getOneMetaData(final String targetsessionKey, f .getName() + "@" + personMetaData.getPerson() .getInstance() .getDomain()) - .sessions(List.of(Objects.requireNonNull(conversionService.convert(personMetaData, - PersonSessionData.class)))) + .sessions(List.of(Objects.requireNonNull( + conversionService.convert(personMetaData, PersonSessionData.class)))) .build(); } + /** + * Invalidates the data associated with a specific user. + * + * @param targetUserData The key containing the user's data. If the key contains "@", it is split + * into name and domain using "@" as the separator. Otherwise, the key is + * assumed to be the user ID. + * @param person The Person object representing the user performing the operation. + * @throws ResponseStatusException If the user is not authorized to invalidate the user data or if + * the user data is not found. + */ public void invalidateUserData(String targetUserData, final Person person) { final PersonMetaData personMetaData = userDataRepository.findById( @@ -593,12 +632,24 @@ public void invalidateUserData(String targetUserData, final Person person) { userDataService.invalidate(personMetaData); } + /** + * Invalidates the data associated with all user data for a specific person. + * + * @param targetPersonKey The key containing the target person's information. If the key contains + * "@", it is split into name and domain using "@" as the separator. + * Otherwise, the key is assumed to be the user ID. + * @param person The Person object representing the user performing the operation. + * @throws ResponseStatusException If the user is not authorized to invalidate the user data or if + * the person is not found. + */ public void invalidateAllUserData(final String targetPersonKey, final Person person) { - final Person target = personRepository.findOneByNameAndInstance_Domain(targetPersonKey, - localInstanceContext.instance() - .getDomain()) + final PersonIdentity personIdentity = personKeyUtils.getPersonIdentity(targetPersonKey); + + final Person target = personRepository.findOneByNameAndInstance_Domain(personIdentity.name(), + personIdentity.domain()) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); + if (!(rolePermissionService.isPermitted(person, RolePermissionPersonTypes.INVALIDATE_USER_OWN_METADATA) && target.equals(person)) && !rolePermissionService.isPermitted(person, @@ -610,6 +661,14 @@ public void invalidateAllUserData(final String targetPersonKey, final Person per userDataService.invalidateAllUserData(target); } + /** + * Deletes the user data for a given targetUserDataKey and person. + * + * @param targetUserDataKey The key of the user data to delete. + * @param person The person associated with the user data. + * @throws ResponseStatusException If the user data is not found or the person is not authorized + * to delete it. + */ public void deleteUserData(final String targetUserDataKey, final Person person) { final PersonMetaData personMetaData = userDataRepository.findById( @@ -627,12 +686,21 @@ public void deleteUserData(final String targetUserDataKey, final Person person) userDataRepository.delete(personMetaData); } + /** + * Deletes all user data for a given person. + * + * @param targetPersonKey The unique key of the target person. + * @param person The person performing the deletion. + * @throws ResponseStatusException if the target person is not found or the person is not + * authorized to delete the data. + */ public void deleteAllUserData(final String targetPersonKey, final Person person) { - final Person target = personRepository.findOneByNameAndInstance_Domain(targetPersonKey, - localInstanceContext.instance() - .getDomain()) + final PersonIdentity targetPersonIdentity = personKeyUtils.getPersonIdentity(targetPersonKey); + + final Person target = personRepository.findOneByNameAndInstance_Domain( + targetPersonIdentity.name(), targetPersonIdentity.domain()) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); if (!(rolePermissionService.isPermitted(person, RolePermissionPersonTypes.DELETE_USER_OWN_METADATA) && target.equals(person)) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java index 09c99244..9851485e 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java @@ -11,8 +11,8 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = { - RolePermissionService.class}) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, + uses = {RolePermissionService.class}) public abstract class SublinksRoleMapper implements Converter { @@ -37,6 +37,9 @@ public abstract com.sublinks.sublinksapi.api.sublinks.v1.roles.models.Role conve @Named("is_expired") Boolean mapIsExpired(Role role) { + if (role.getExpiresAt() == null) { + return false; + } return new Date().after(role.getExpiresAt()); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/PersonKeyUtils.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/PersonKeyUtils.java new file mode 100644 index 00000000..49960744 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/utils/PersonKeyUtils.java @@ -0,0 +1,25 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.utils; + +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonIdentity; +import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.utils.UrlUtil; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; + + +@Component +@AllArgsConstructor +public class PersonKeyUtils { + + private final UrlUtil urlUtil; + + public String getPersonKey(Person person) { + return person.getName() + "@" + urlUtil.cleanUrlProtocol(person.getInstance().getDomain()); + } + + public PersonIdentity getPersonIdentity(String personKey) { + String[] parts = personKey.split("@"); + return new PersonIdentity(parts[0], parts[1]); + } + +} diff --git a/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java b/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java index 9447a758..05be9503 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java @@ -19,13 +19,15 @@ public interface PersonRepository extends JpaRepository { Optional findOneByNameIgnoreCase(String name); + @Query(value = "SELECT p.*, lpi.instance_id, lpi.person_id, i.id as iid FROM people p JOIN link_person_instances lpi ON lpi.person_id = p.id JOIN instances i ON i.id = lpi.instance_id WHERE p.name = :name AND i.domain ILIKE concat('%','/',:instance_domain)", + nativeQuery = true) Optional findOneByNameAndInstance_Domain(String name, String instance_domain); Optional findOneByEmail(String email); HashSet findAllByRole(Role role); - List findAllByLocal(Boolean local, Pageable pageable); + List findAllByIsLocal(Boolean local, Pageable pageable); @Query(value = "SELECT p FROM people p WHERE p.search_vector @@ to_tsquery('keyword', :keyword)", countQuery = "SELECT COUNT(p.id) FROM people p WHERE p.search_vector @@ to_tsquery('english', :keyword)", diff --git a/src/main/java/com/sublinks/sublinksapi/person/services/UserDataService.java b/src/main/java/com/sublinks/sublinksapi/person/services/UserDataService.java index 75269e4c..ba3abcfa 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/services/UserDataService.java +++ b/src/main/java/com/sublinks/sublinksapi/person/services/UserDataService.java @@ -29,22 +29,24 @@ public class UserDataService { public void invalidate(PersonMetaData personMetaData) { personMetaData.setActive(false); - userDataRepository.save(personMetaData); + userDataRepository.saveAndFlush(personMetaData); userDataInvalidationEventPublisher.publish(personMetaData); } public void checkAndAddIpRelation(Person person, String ipAddress, String token, - @Nullable String userAgent) { + @Nullable String userAgent) + { boolean saveUserIps = userDataConfig.isSaveUserData(); - Optional foundData = getActiveUserDataByPersonAndIpAddress(person, token, ipAddress, - userAgent); + Optional foundData = getActiveUserDataByPersonAndIpAddress(person, token, + ipAddress, userAgent).or(() -> getActiveUserDataByPersonAndToken(person, token)); if (foundData.isPresent()) { PersonMetaData personMetaData = foundData.get(); - if (saveUserIps && userAgent != null && !personMetaData.getUserAgent().equals(userAgent)) { + if (saveUserIps && userAgent != null && !personMetaData.getUserAgent() + .equals(userAgent)) { final PersonMetaData newPersonMetaData = PersonMetaData.builder() .person(person) .ipAddress(ipAddress) @@ -53,12 +55,13 @@ public void checkAndAddIpRelation(Person person, String ipAddress, String token, .active(true) .build(); - userDataRepository.save(newPersonMetaData); + userDataRepository.saveAndFlush(newPersonMetaData); userDataCreatedEventPublisher.publish(newPersonMetaData); return; } - if (saveUserIps && !personMetaData.getIpAddress().equals(ipAddress)) { + if (saveUserIps && !personMetaData.getIpAddress() + .equals(ipAddress)) { final PersonMetaData newPersonMetaData = PersonMetaData.builder() .person(person) .ipAddress(ipAddress) @@ -67,14 +70,14 @@ public void checkAndAddIpRelation(Person person, String ipAddress, String token, .active(true) .build(); - userDataRepository.save(newPersonMetaData); + userDataRepository.saveAndFlush(newPersonMetaData); userDataCreatedEventPublisher.publish(newPersonMetaData); return; } personMetaData.setLastUsedAt(new Date()); - userDataRepository.save(personMetaData); + userDataRepository.saveAndFlush(personMetaData); userDataUpdatedPublisher.publish(personMetaData); return; } @@ -85,7 +88,7 @@ public void checkAndAddIpRelation(Person person, String ipAddress, String token, .token(token) .active(true) .build(); - PersonMetaData createdPersonMetaData = userDataRepository.save(personMetaData); + PersonMetaData createdPersonMetaData = userDataRepository.saveAndFlush(personMetaData); userDataCreatedEventPublisher.publish(createdPersonMetaData); } @@ -94,16 +97,19 @@ public Optional getActiveUserDataByPersonAndToken(Person person, return userDataRepository.findFirstByPersonAndTokenAndActiveIsTrue(person, token); } - private Optional getActiveUserDataByPersonAndIpAddress(Person person, String token, - String ipAddress, String userAgent) { + private Optional getActiveUserDataByPersonAndIpAddress(Person person, + String token, String ipAddress, String userAgent) + { - return userDataRepository.findFirstByPersonAndTokenAndActiveIsTrue(person, token); + return userDataRepository.findFirstByPersonAndTokenAndIpAddressAndUserAgentAndActiveIsTrue( + person, token, ipAddress, userAgent); } @Transactional public void invalidateAllUserData(Person person) { userDataRepository.updateAllByPersonSetActiveToFalse(person); + userDataRepository.flush(); userDataInvalidationEventPublisher.publish(person); } diff --git a/src/main/java/com/sublinks/sublinksapi/utils/UrlUtil.java b/src/main/java/com/sublinks/sublinksapi/utils/UrlUtil.java index 812802ec..7a41f227 100644 --- a/src/main/java/com/sublinks/sublinksapi/utils/UrlUtil.java +++ b/src/main/java/com/sublinks/sublinksapi/utils/UrlUtil.java @@ -57,7 +57,8 @@ private String removeTrackingParameters(final String queryString) { if (parameters.isEmpty()) { return null; } - return parameters.entrySet().stream() + return parameters.entrySet() + .stream() .map(e -> e.getKey() + "=" + e.getValue()) .collect(Collectors.joining("&")); } @@ -75,11 +76,31 @@ public void checkUrlProtocol(String providedUrl) { try { final URL url = new URL(providedUrl); - if (!List.of("http", "https", "magnet").contains(url.getProtocol())) { + if (!List.of("http", "https", "magnet") + .contains(url.getProtocol())) { throw new RuntimeException("Invalid URL Scheme"); } } catch (Exception e) { throw new RuntimeException("Invalid URL Scheme"); } } + + public String cleanUrlProtocol(String providedUrl) { + + try { + final URL url = new URL(providedUrl); + + final StringBuilder sb = new StringBuilder(); + + sb.append(url.getHost()); + if (url.getPort() != -1) { + sb.append(":") + .append(url.getPort()); + } + + return sb.toString(); + } catch (MalformedURLException e) { + return providedUrl; + } + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index a77035de..30be3eb9 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -86,4 +86,3 @@ sublinks.keep_comment_history=${KEEP_COMMENT_HISTORY:false} spring.thymeleaf.check-template-location=false # enable enable_lazy_load_no_trans spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true - From 2ae010c069aef42b8047dc80b8023e5ca719a4bc Mon Sep 17 00:00:00 2001 From: rooki Date: Sun, 30 Jun 2024 18:59:10 +0200 Subject: [PATCH 066/115] Refactor UserDataService to simplify metadata updates The UserDataService has been refactored for simpler active user data retrieval and updating. The fetching process has been simplified by removing the need to fetch data using person and IP address together. Also, the metadata update procedure has been improved; it no longer needs to instantiate a new metadata every time there's a change, but instead, modifies the existing metadata instance. Signed-off-by: rooki --- .../person/services/UserDataService.java | 41 ++++--------------- 1 file changed, 7 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/person/services/UserDataService.java b/src/main/java/com/sublinks/sublinksapi/person/services/UserDataService.java index ba3abcfa..5237b749 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/services/UserDataService.java +++ b/src/main/java/com/sublinks/sublinksapi/person/services/UserDataService.java @@ -38,47 +38,20 @@ public void checkAndAddIpRelation(Person person, String ipAddress, String token, { boolean saveUserIps = userDataConfig.isSaveUserData(); - Optional foundData = getActiveUserDataByPersonAndIpAddress(person, token, - ipAddress, userAgent).or(() -> getActiveUserDataByPersonAndToken(person, token)); + Optional foundData = getActiveUserDataByPersonAndToken(person, token); if (foundData.isPresent()) { PersonMetaData personMetaData = foundData.get(); - - if (saveUserIps && userAgent != null && !personMetaData.getUserAgent() - .equals(userAgent)) { - final PersonMetaData newPersonMetaData = PersonMetaData.builder() - .person(person) - .ipAddress(ipAddress) - .userAgent(userAgent) - .token(token) - .active(true) - .build(); - - userDataRepository.saveAndFlush(newPersonMetaData); - userDataCreatedEventPublisher.publish(newPersonMetaData); - return; - } - - if (saveUserIps && !personMetaData.getIpAddress() - .equals(ipAddress)) { - final PersonMetaData newPersonMetaData = PersonMetaData.builder() - .person(person) - .ipAddress(ipAddress) - .userAgent(userAgent) - .token(token) - .active(true) - .build(); - - userDataRepository.saveAndFlush(newPersonMetaData); - userDataCreatedEventPublisher.publish(newPersonMetaData); - return; + if (saveUserIps) { + personMetaData.setIpAddress(ipAddress); + if (userAgent != null) { + personMetaData.setUserAgent(userAgent); + } } - personMetaData.setLastUsedAt(new Date()); - userDataRepository.saveAndFlush(personMetaData); - userDataUpdatedPublisher.publish(personMetaData); + userDataUpdatedPublisher.publish(userDataRepository.saveAndFlush(personMetaData)); return; } PersonMetaData personMetaData = PersonMetaData.builder() From 67b75a793f813b1be49f25fc74790a28547d5852 Mon Sep 17 00:00:00 2001 From: rooki Date: Tue, 2 Jul 2024 20:30:59 +0200 Subject: [PATCH 067/115] Reorder import statements in UserDataService The import statements in the UserDataService class have been reordered. This change improves readability and adheres to code style guidelines. No functional changes have been made to the code. Signed-off-by: rooki --- .../sublinksapi/person/services/UserDataService.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/person/services/UserDataService.java b/src/main/java/com/sublinks/sublinksapi/person/services/UserDataService.java index 5237b749..54ac6bc0 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/services/UserDataService.java +++ b/src/main/java/com/sublinks/sublinksapi/person/services/UserDataService.java @@ -8,12 +8,12 @@ import com.sublinks.sublinksapi.person.events.UserDataUpdatedPublisher; import com.sublinks.sublinksapi.person.repositories.UserDataRepository; import jakarta.annotation.Nullable; -import java.util.Date; -import java.util.List; -import java.util.Optional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; +import java.util.Date; +import java.util.List; +import java.util.Optional; @Component @RequiredArgsConstructor From abd18c63e4d34a3b188ec6f78975d37aedf5375c Mon Sep 17 00:00:00 2001 From: rooki Date: Fri, 5 Jul 2024 21:53:01 +0200 Subject: [PATCH 068/115] Add authorization to instance config actions The methods for showing and updating instance configurations now require the user's identity. The server will throw unauthorized responses when requested by unauthorized people. Permission types for reading and updating instances were added to the RolePermissionInstanceTypes enumeration. Signed-off-by: rooki --- .../SublinksInstanceConfigController.java | 17 +++++++++----- .../service/SublinksInstanceService.java | 22 ++++++++++++++++++- .../SublinksPersonSessionController.java | 1 - .../enums/RolePermissionInstanceTypes.java | 1 + .../services/InitialRoleSetupService.java | 1 + 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceConfigController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceConfigController.java index 26976c29..2730e130 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceConfigController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceConfigController.java @@ -1,5 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.instance.controllers; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.InstanceConfigResponse; import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.UpdateInstanceConfig; @@ -7,6 +8,7 @@ import com.sublinks.sublinksapi.instance.repositories.InstanceRepository; import com.sublinks.sublinksapi.instance.services.InstanceConfigService; import com.sublinks.sublinksapi.instance.services.InstanceService; +import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -37,11 +39,13 @@ public class SublinksInstanceConfigController extends AbstractSublinksApiControl @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public InstanceConfigResponse show(@PathVariable String key) + public InstanceConfigResponse show(@PathVariable String key, + final SublinksJwtPerson sublinksJwtPerson) { - return conversionService.convert(instanceRepository.findInstanceByDomain(key), - InstanceConfigResponse.class); + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksInstanceService.showConfig(key, person); } @Operation(summary = "Update an instance config") @@ -49,9 +53,12 @@ public InstanceConfigResponse show(@PathVariable String key) @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public InstanceConfigResponse update(@PathVariable String key, - @RequestBody @Valid UpdateInstanceConfig updateInstanceConfigForm) + @RequestBody @Valid UpdateInstanceConfig updateInstanceConfigForm, + final SublinksJwtPerson sublinksJwtPerson) { - return sublinksInstanceService.updateConfig(key, updateInstanceConfigForm); + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksInstanceService.updateConfig(key, updateInstanceConfigForm, person); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/service/SublinksInstanceService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/service/SublinksInstanceService.java index 41e80e2d..a90ca183 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/service/SublinksInstanceService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/service/SublinksInstanceService.java @@ -5,12 +5,15 @@ import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.InstanceConfigResponse; import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.InstanceResponse; import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.UpdateInstanceConfig; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionInstanceTypes; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.instance.entities.InstanceConfig; import com.sublinks.sublinksapi.instance.repositories.InstanceAggregateRepository; import com.sublinks.sublinksapi.instance.repositories.InstanceConfigRepository; import com.sublinks.sublinksapi.instance.repositories.InstanceRepository; import com.sublinks.sublinksapi.instance.services.InstanceConfigService; import com.sublinks.sublinksapi.instance.services.InstanceService; +import com.sublinks.sublinksapi.person.entities.Person; import java.util.List; import java.util.stream.Collectors; import lombok.AllArgsConstructor; @@ -30,6 +33,7 @@ public class SublinksInstanceService { private final InstanceService instanceService; private final InstanceConfigService instanceConfigService; private final ConversionService conversionService; + private final RolePermissionService rolePermissionService; public List index(final IndexInstance indexInstance) { @@ -60,10 +64,26 @@ public InstanceAggregateResponse showAggregate(final String key) { InstanceAggregateResponse.class); } + public InstanceConfigResponse showConfig(final String key, final Person person) { + + rolePermissionService.isPermitted(person, + RolePermissionInstanceTypes.INSTANCE_READ_ANNOUNCEMENT, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_read_instance_config")); + + return conversionService.convert(instanceConfigRepository.findByInstance_Domain(key) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "instance_not_found")), + InstanceConfigResponse.class); + } + public InstanceConfigResponse updateConfig(final String key, - final UpdateInstanceConfig updateInstanceConfigForm) + final UpdateInstanceConfig updateInstanceConfigForm, final Person person) { + rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_UPDATE_SETTINGS, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_authorized_to_update_instance_config")); + final InstanceConfig config = instanceConfigRepository.findByInstance_Domain(key) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "instance_not_found")); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonSessionController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonSessionController.java index cf1bef81..200b995e 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonSessionController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonSessionController.java @@ -98,5 +98,4 @@ public void deleteOneMetaData(@PathVariable String sessionKey, final SublinksJwt sublinksPersonService.deleteUserData(sessionKey, person); } - } diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionInstanceTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionInstanceTypes.java index 2a8be60f..619738d9 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionInstanceTypes.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionInstanceTypes.java @@ -7,6 +7,7 @@ public enum RolePermissionInstanceTypes implements RolePermissionInterface { // Person permissions + INSTANCE_READ_CONFIG("instance-config", AuthorizeAction.READ), INSTANCE_UPDATE_SETTINGS("instance", AuthorizeAction.UPDATE), INSTANCE_BAN_USER("user-admin", AuthorizeAction.BAN), INSTANCE_BAN_READ("user-admin-ban", AuthorizeAction.READ), diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java index 49a97729..cb8f50bc 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java @@ -84,6 +84,7 @@ private void applyCommonPermissions(Set rolePermissions rolePermissions.add(RolePermissionInstanceTypes.INSTANCE_SEARCH); rolePermissions.add(RolePermissionInstanceTypes.INSTANCE_READ_ANNOUNCEMENT); rolePermissions.add(RolePermissionInstanceTypes.INSTANCE_READ_ANNOUNCEMENTS); + rolePermissions.add(RolePermissionInstanceTypes.INSTANCE_READ_CONFIG); } /** From 3769ed4ec81908b79a16d7a069978bd64cb068b1 Mon Sep 17 00:00:00 2001 From: rooki Date: Sat, 6 Jul 2024 14:00:11 +0200 Subject: [PATCH 069/115] Refactor moderation models and update relevant methods Renamed moderation models for consistency and clarity, and updated all relevant methods in service and controller classes accordingly. Two new models for Post moderation were added. Additionally, fixed logic in checking delete post permission in SublinksPostService. Signed-off-by: rooki --- .../SublinksCommentModerationController.java | 20 ++++++++-------- ...{CommentDelete.java => DeleteComment.java} | 2 +- .../{CommentPin.java => PinComment.java} | 2 +- ...{CommentRemove.java => RemoveComment.java} | 2 +- .../services/SublinksCommentService.java | 24 +++++++++---------- .../v1/post/models/moderation/DeletePost.java | 12 ++++++++++ .../v1/post/models/moderation/PInPost.java | 12 ++++++++++ .../v1/post/models/moderation/RemovePost.java | 5 ++++ .../v1/post/services/SublinksPostService.java | 4 ++-- 9 files changed, 56 insertions(+), 27 deletions(-) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/{CommentDelete.java => DeleteComment.java} (80%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/{CommentPin.java => PinComment.java} (66%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/{CommentRemove.java => RemoveComment.java} (80%) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/DeletePost.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/PInPost.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java index cfcfa4f0..28c9b67a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java @@ -2,9 +2,9 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.CommentDelete; -import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.CommentPin; -import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.CommentRemove; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.DeleteComment; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.PinComment; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.RemoveComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; @@ -36,39 +36,39 @@ public class SublinksCommentModerationController extends AbstractSublinksApiCont @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommentResponse remove(@PathVariable final String key, - @RequestBody @Valid final CommentRemove commentRemove, + @RequestBody @Valid final RemoveComment removeComment, final SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - return sublinksCommentService.remove(key, commentRemove, person); + return sublinksCommentService.remove(key, removeComment, person); } - @Operation(summary = "Delete a comment") + @Operation(summary = "Purge a comment") @PostMapping("/delete") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommentResponse delete(@PathVariable final String key, - @RequestBody @Valid final CommentDelete commentDeleteForm, + @RequestBody @Valid final DeleteComment deleteCommentForm, final SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - return sublinksCommentService.delete(key, commentDeleteForm, person); + return sublinksCommentService.delete(key, deleteCommentForm, person); } @Operation(summary = "Pin/Unpin a comment") @GetMapping("/pin") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommentResponse highlight(@PathVariable final String key, final CommentPin commentPinForm, + public CommentResponse pin(@PathVariable final String key, final PinComment pinCommentForm, final SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - return sublinksCommentService.pin(key, commentPinForm, person); + return sublinksCommentService.pin(key, pinCommentForm, person); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/CommentDelete.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/DeleteComment.java similarity index 80% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/CommentDelete.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/DeleteComment.java index 368c6d63..911f5b78 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/CommentDelete.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/DeleteComment.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation; -public record CommentDelete( +public record DeleteComment( String reason, Boolean remove) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/CommentPin.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/PinComment.java similarity index 66% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/CommentPin.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/PinComment.java index 0825cc41..dffa394a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/CommentPin.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/PinComment.java @@ -1,5 +1,5 @@ package com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation; -public record CommentPin(Boolean pin) { +public record PinComment(Boolean pin) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/CommentRemove.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/RemoveComment.java similarity index 80% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/CommentRemove.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/RemoveComment.java index cab15acf..b125faf3 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/CommentRemove.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/RemoveComment.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation; -public record CommentRemove( +public record RemoveComment( String reason, Boolean remove) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index c131af2a..48bd8b9c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -4,9 +4,9 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CreateComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.IndexComment; -import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.CommentDelete; -import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.CommentPin; -import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.CommentRemove; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.DeleteComment; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.PinComment; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.RemoveComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.UpdateComment; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; @@ -239,13 +239,13 @@ public CommentResponse updateComment(UpdateComment updateCommentForm, Person per * Removes a comment based on the provided key, comment remove form, and person. * * @param key The key of the comment to be removed. - * @param commentRemoveForm The CommentRemove object representing the remove form data. + * @param removeCommentForm The CommentRemove object representing the remove form data. * @param person The Person object representing the user performing the removal. * @return A CommentResponse object representing the removed comment. * @throws ResponseStatusException If the user does not have permission to remove the comment or * the comment is not found. */ - public CommentResponse remove(String key, CommentRemove commentRemoveForm, Person person) { + public CommentResponse remove(String key, RemoveComment removeCommentForm, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.REMOVE_COMMENT, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_remove_not_permitted")); @@ -255,7 +255,7 @@ public CommentResponse remove(String key, CommentRemove commentRemoveForm, Perso () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); comment.setRemovedState( - commentRemoveForm.remove() != null ? commentRemoveForm.remove() ? RemovedState.REMOVED + removeCommentForm.remove() != null ? removeCommentForm.remove() ? RemovedState.REMOVED : RemovedState.NOT_REMOVED : RemovedState.REMOVED); commentService.updateComment(comment); // @todo: modlog @@ -267,13 +267,13 @@ public CommentResponse remove(String key, CommentRemove commentRemoveForm, Perso * Deletes a comment based on the provided key, CommentDelete form, and Person. * * @param key The key of the comment to be deleted. - * @param commentDeleteForm The CommentDelete object representing the delete form data. + * @param deleteCommentForm The CommentDelete object representing the delete form data. * @param person The Person object representing the user performing the deletion. * @return A CommentResponse object representing the deleted comment. * @throws ResponseStatusException If the user does not have permission to delete the comment or * the comment is not found. */ - public CommentResponse delete(String key, CommentDelete commentDeleteForm, Person person) { + public CommentResponse delete(String key, DeleteComment deleteCommentForm, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.DELETE_COMMENT, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_delete_not_permitted")); @@ -287,7 +287,7 @@ public CommentResponse delete(String key, CommentDelete commentDeleteForm, Perso throw new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_delete_not_permitted"); } - comment.setDeleted(commentDeleteForm.remove()); + comment.setDeleted(deleteCommentForm.remove()); commentService.updateComment(comment); // @todo: modlog @@ -298,14 +298,14 @@ public CommentResponse delete(String key, CommentDelete commentDeleteForm, Perso * Pins or unpins a comment and returns the updated CommentResponse object. * * @param key The key of the comment. - * @param commentPinForm The CommentPin object representing the pin form data. + * @param pinCommentForm The CommentPin object representing the pin form data. * @param person The Person object representing the user performing the * pinning/unpinning. * @return A CommentResponse object representing the updated comment. * @throws ResponseStatusException If the comment is not found or the user does not have * permission to perform the operation. */ - public CommentResponse pin(String key, CommentPin commentPinForm, Person person) { + public CommentResponse pin(String key, PinComment pinCommentForm, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.MODERATOR_PIN_COMMENT, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_highlight_not_permitted")); @@ -319,7 +319,7 @@ public CommentResponse pin(String key, CommentPin commentPinForm, Person person) } comment.setFeatured( - commentPinForm.pin() != null ? commentPinForm.pin() : !comment.isFeatured()); + pinCommentForm.pin() != null ? pinCommentForm.pin() : !comment.isFeatured()); commentService.updateComment(comment); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/DeletePost.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/DeletePost.java new file mode 100644 index 00000000..b751b608 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/DeletePost.java @@ -0,0 +1,12 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation; + +public record DeletePost( + String reason, + Boolean remove) { + + @Override + public Boolean remove() { + + return remove == null || remove; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/PInPost.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/PInPost.java new file mode 100644 index 00000000..0415d2ce --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/PInPost.java @@ -0,0 +1,12 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation; + +public record PInPost( + String reason, + Boolean remove) { + + @Override + public Boolean remove() { + + return remove == null || remove; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/RemovePost.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/RemovePost.java index 3eb171ca..38b68733 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/RemovePost.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/RemovePost.java @@ -4,4 +4,9 @@ public record RemovePost( String reason, Boolean remove) { + @Override + public Boolean remove() { + + return remove == null || remove; + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index 48ab0dfc..eabe9c1a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -424,10 +424,10 @@ public PostResponse delete(final String postKey, final DeletePost deletePostForm final Post post = postRepository.findByTitleSlug(postKey) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found")); - if (rolePermissionService.isPermitted(person, RolePermissionPostTypes.DELETE_POST) + if (!(rolePermissionService.isPermitted(person, RolePermissionPostTypes.DELETE_POST) && postService.getPostCreator(post) .getId() - .equals(person.getId()) && !post.isRemoved()) { + .equals(person.getId()) && !post.isRemoved())) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "delete_post_permission_denied"); } From b3be4a2bb1465e8c461b99ba114a1cfe5c40a56e Mon Sep 17 00:00:00 2001 From: rooki Date: Sat, 6 Jul 2024 14:10:36 +0200 Subject: [PATCH 070/115] Add post aggregate functionality Renamed Aggregate model classes to unify nam Signed-off-by: rooki --- .../SublinksCommentAggerateController.java | 9 ++--- ...nse.java => AggregateCommentResponse.java} | 2 +- .../services/SublinksCommentService.java | 10 ++--- .../SublinksPostAggerateController.java | 39 +++++++++++++++++++ .../SublinksPostAggregationMapper.java | 6 +-- ...sponse.java => AggregatePostResponse.java} | 2 +- .../sublinks/v1/post/models/PostResponse.java | 2 +- .../v1/post/services/SublinksPostService.java | 15 +++++++ .../enums/RolePermissionPostTypes.java | 1 + .../repositories/PostAggregateRepository.java | 3 ++ 10 files changed, 72 insertions(+), 17 deletions(-) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/{CommentAggregateResponse.java => AggregateCommentResponse.java} (95%) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostAggerateController.java rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/{PostAggregateResponse.java => AggregatePostResponse.java} (85%) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java index 50af3f34..6c508365 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java @@ -1,10 +1,9 @@ package com.sublinks.sublinksapi.api.sublinks.v1.comment.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; -import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentAggregateResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.AggregateCommentResponse; import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; -import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommentTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; @@ -13,12 +12,10 @@ import io.swagger.v3.oas.annotations.tags.Tag; import java.util.Optional; import lombok.AllArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import org.springframework.web.server.ResponseStatusException; @RestController @AllArgsConstructor @@ -33,8 +30,8 @@ public class SublinksCommentAggerateController extends AbstractSublinksApiContro @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommentAggregateResponse aggregate(@PathVariable final String key, - final SublinksJwtPerson sublinksJwtPerson) + public AggregateCommentResponse aggregate(@PathVariable final String key, + final SublinksJwtPerson sublinksJwtPerson) { final Optional person = getOptionalPerson(sublinksJwtPerson); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentAggregateResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/AggregateCommentResponse.java similarity index 95% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentAggregateResponse.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/AggregateCommentResponse.java index cf203887..f860141c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentAggregateResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/AggregateCommentResponse.java @@ -5,7 +5,7 @@ import lombok.Builder; @Builder -public record CommentAggregateResponse( +public record AggregateCommentResponse( @Schema(description = "Search query", requiredMode = RequiredMode.NOT_REQUIRED) String commentKey, @Schema(description = "The number of upvotes", diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index 48bd8b9c..e60a091a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.comment.services; -import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentAggregateResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.AggregateCommentResponse; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CreateComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.IndexComment; @@ -327,21 +327,21 @@ public CommentResponse pin(String key, PinComment pinCommentForm, Person person) } /** - * Retrieves a {@link CommentAggregateResponse} based on the provided comment key and person. + * Retrieves a {@link AggregateCommentResponse} based on the provided comment key and person. * * @param commentKey The key of the comment to retrieve the aggregate for. * @param person The {@link Person} object representing the user performing the operation. - * @return A {@link CommentAggregateResponse} object representing the retrieved comment aggregate. + * @return A {@link AggregateCommentResponse} object representing the retrieved comment aggregate. * @throws ResponseStatusException If the comment is not found. */ - public CommentAggregateResponse aggregate(String commentKey, Person person) { + public AggregateCommentResponse aggregate(String commentKey, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.READ_COMMENT_AGGREGATE, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_view_not_permitted")); return commentAggregateRepository.findByComment_Path(commentKey) .map(commentAggregate -> conversionService.convert(commentAggregate, - CommentAggregateResponse.class)) + AggregateCommentResponse.class)) .orElseThrow( () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostAggerateController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostAggerateController.java new file mode 100644 index 00000000..db3efc85 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostAggerateController.java @@ -0,0 +1,39 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.AggregatePostResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.post.services.SublinksPostService; +import com.sublinks.sublinksapi.person.entities.Person; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.Optional; +import lombok.AllArgsConstructor; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AllArgsConstructor +@RequestMapping("api/v1/comment/{key}/aggregate") +@Tag(name = "Comment Aggregation", description = "Comment Aggregate API") +public class SublinksPostAggerateController extends AbstractSublinksApiController { + + private final SublinksPostService sublinksPostService; + + @Operation(summary = "Aggregate a comment") + @GetMapping + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public AggregatePostResponse aggregate(@PathVariable final String key, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Optional person = getOptionalPerson(sublinksJwtPerson); + + return sublinksPostService.aggregate(key, person.orElse(null)); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostAggregationMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostAggregationMapper.java index bd5e0351..43488da0 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostAggregationMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/mappers/SublinksPostAggregationMapper.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.post.mappers; -import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostAggregateResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.AggregatePostResponse; import com.sublinks.sublinksapi.post.entities.PostAggregate; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @@ -10,7 +10,7 @@ @Mapper(componentModel = MappingConstants.ComponentModel.SPRING) public abstract class SublinksPostAggregationMapper implements - Converter { + Converter { @Override @Mapping(target = "key", source = "postAggregate.id") @@ -20,7 +20,7 @@ public abstract class SublinksPostAggregationMapper implements @Mapping(target = "score", source = "postAggregate.score") @Mapping(target = "hotRank", source = "postAggregate.hotRank") @Mapping(target = "controversyRank", source = "postAggregate.controversyRank") - public abstract PostAggregateResponse convert(@Nullable PostAggregate postAggregate); + public abstract AggregatePostResponse convert(@Nullable PostAggregate postAggregate); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostAggregateResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/AggregatePostResponse.java similarity index 85% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostAggregateResponse.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/AggregatePostResponse.java index 04788698..044cd046 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostAggregateResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/AggregatePostResponse.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.post.models; -public record PostAggregateResponse( +public record AggregatePostResponse( String key, String commentCount, String downvoteCount, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostResponse.java index eac293c9..7f77f202 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/PostResponse.java @@ -18,7 +18,7 @@ public record PostResponse( Boolean isFeaturedInCommunity, CommunityResponse community, PersonResponse creator, - PostAggregateResponse postAggregate, + AggregatePostResponse postAggregate, String activityPubId, String publicKey, String createdAt, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index eabe9c1a..eef6f252 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -1,11 +1,14 @@ package com.sublinks.sublinksapi.api.sublinks.v1.post.services; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.AggregateCommentResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.AggregatePostResponse; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.CreatePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.DeletePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.IndexPost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.UpdatePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.RemovePost; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommentTypes; import com.sublinks.sublinksapi.authorization.enums.RolePermissionPostTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.community.entities.Community; @@ -23,6 +26,7 @@ import com.sublinks.sublinksapi.post.entities.Post.PostBuilder; import com.sublinks.sublinksapi.post.entities.PostReport; import com.sublinks.sublinksapi.post.models.PostSearchCriteria; +import com.sublinks.sublinksapi.post.repositories.PostAggregateRepository; import com.sublinks.sublinksapi.post.repositories.PostRepository; import com.sublinks.sublinksapi.post.services.PostReportService; import com.sublinks.sublinksapi.post.services.PostService; @@ -56,6 +60,7 @@ public class SublinksPostService { private final SiteMetadataUtil siteMetadataUtil; private final PostReportService postReportService; private final PersonService personService; + private final PostAggregateRepository postAggregateRepository; /** * Retrieves a list of PostResponse objects based on the provided search criteria. @@ -438,4 +443,14 @@ public PostResponse delete(final String postKey, final DeletePost deletePostForm postService.updatePost(post); return conversionService.convert(post, PostResponse.class); } + + public AggregatePostResponse aggregate(String postKey, Person person) { + + rolePermissionService.isPermitted(person, RolePermissionPostTypes.READ_POST_AGGREGATE, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "aggregate_post_permission_denied")); + + return postAggregateRepository.findByPost_TitleSlug(postKey) + .map(postAggregate -> conversionService.convert(postAggregate, AggregatePostResponse.class)) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found")); + } } \ No newline at end of file diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPostTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPostTypes.java index 15897083..2d3d7ec9 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPostTypes.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPostTypes.java @@ -4,6 +4,7 @@ public enum RolePermissionPostTypes implements RolePermissionInterface { // Person permissions READ_POST("post", AuthorizeAction.READ), + READ_POST_AGGREGATE("post-aggregate", AuthorizeAction.READ), READ_POSTS("posts", AuthorizeAction.READ), MARK_POST_AS_READ("post-read", AuthorizeAction.UPDATE), CREATE_POST("post", AuthorizeAction.CREATE), diff --git a/src/main/java/com/sublinks/sublinksapi/post/repositories/PostAggregateRepository.java b/src/main/java/com/sublinks/sublinksapi/post/repositories/PostAggregateRepository.java index 61f80c29..91a8ad1d 100644 --- a/src/main/java/com/sublinks/sublinksapi/post/repositories/PostAggregateRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/post/repositories/PostAggregateRepository.java @@ -1,8 +1,11 @@ package com.sublinks.sublinksapi.post.repositories; import com.sublinks.sublinksapi.post.entities.PostAggregate; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; public interface PostAggregateRepository extends JpaRepository { + Optional findByPost_TitleSlug(String titleSlug); + } From 68da5cb4a420b3e799c18247ceaf3bd46972a68b Mon Sep 17 00:00:00 2001 From: rooki Date: Sun, 7 Jul 2024 14:46:00 +0200 Subject: [PATCH 071/115] Update moderation actions from "remove" to "pin" The code alters several parts where a moderator could remove certain entities. The action has now been changed to pinning the entities instead. This includes changes in comments, posts, and community moderation. Additional adjustments have been made to ensure correct behaviour of the new pin functionality. Signed-off-by: rooki --- .../CommentModActionsController.java | 2 +- .../CommunityModActionsController.java | 2 +- .../controllers/PostModActionsController.java | 2 +- .../SublinksCommentController.java | 7 +- .../SublinksCommentModerationController.java | 16 ++- .../{DeleteComment.java => PurgeComment.java} | 2 +- .../services/SublinksCommentService.java | 15 ++- .../sublinks/v1/common/RequestResponse.java | 10 ++ .../services/SublinksCommunityService.java | 10 +- .../v1/person/models/DeletePerson.java | 2 +- .../controllers/SublinksPostController.java | 32 +++++- .../SublinksPostModerationController.java | 27 +++-- .../post/models/moderation/FavoritePost.java | 15 +++ .../v1/post/models/moderation/PInPost.java | 12 -- .../v1/post/models/moderation/PinPost.java | 5 + .../v1/post/services/SublinksPostService.java | 105 +++++++++++++++++- .../services/LinkPersonPostService.java | 4 +- 17 files changed, 213 insertions(+), 55 deletions(-) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/{DeleteComment.java => PurgeComment.java} (80%) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/RequestResponse.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/FavoritePost.java delete mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/PInPost.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/PinPost.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/comment/controllers/CommentModActionsController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/comment/controllers/CommentModActionsController.java index 75d0edf8..9e37e3f1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/comment/controllers/CommentModActionsController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/comment/controllers/CommentModActionsController.java @@ -82,7 +82,7 @@ public class CommentModActionsController extends AbstractLemmyApiController { * @param principal The authenticated user. * @return The updated comment response. */ - @Operation(summary = "A moderator remove for a comment.") + @Operation(summary = "A moderator pin for a comment.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = CommentResponse.class))}), @ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ResponseStatusException.class)))}) diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java index 702f4985..d6861b08 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java @@ -154,7 +154,7 @@ CommunityResponse delete(@Valid final DeleteCommunity deleteCommunityForm, JwtPe .build(); } - @Operation(summary = "A moderator remove for a community.") + @Operation(summary = "A moderator pin for a community.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = CommunityResponse.class))})}) 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 82466e2e..8cb63a2d 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 @@ -74,7 +74,7 @@ public class PostModActionsController extends AbstractLemmyApiController { private final PostService postService; private final ModerationLogService moderationLogService; - @Operation(summary = "A moderator remove for a post.") + @Operation(summary = "A moderator pin for a post.") @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PostResponse.class))}), diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java index eb376652..2deaba54 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java @@ -6,6 +6,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.IndexComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.UpdateComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; +import com.sublinks.sublinksapi.api.sublinks.v1.common.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; @@ -90,7 +91,11 @@ public CommentResponse update(@PathVariable String key, final UpdateComment crea @DeleteMapping("/{id}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void delete(@PathVariable String id) { + public RequestResponse delete(@PathVariable String id) { // TODO: implement + + return RequestResponse.builder() + .success(true) + .build(); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java index 28c9b67a..bcc00177 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java @@ -2,10 +2,11 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.DeleteComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.PinComment; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.PurgeComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.RemoveComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; +import com.sublinks.sublinksapi.api.sublinks.v1.common.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.person.entities.Person; @@ -46,17 +47,22 @@ public CommentResponse remove(@PathVariable final String key, } @Operation(summary = "Purge a comment") - @PostMapping("/delete") + @PostMapping("/purge") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommentResponse delete(@PathVariable final String key, - @RequestBody @Valid final DeleteComment deleteCommentForm, + public RequestResponse delete(@PathVariable final String key, + @RequestBody @Valid final PurgeComment purgeCommentForm, final SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - return sublinksCommentService.delete(key, deleteCommentForm, person); + // @todo: Implement purge + + return RequestResponse.builder() + .success(false) + .error("not_implemented") + .build(); } @Operation(summary = "Pin/Unpin a comment") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/DeleteComment.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/PurgeComment.java similarity index 80% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/DeleteComment.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/PurgeComment.java index 911f5b78..b766174b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/DeleteComment.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/Moderation/PurgeComment.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation; -public record DeleteComment( +public record PurgeComment( String reason, Boolean remove) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index e60a091a..b616d36b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -4,7 +4,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CreateComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.IndexComment; -import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.DeleteComment; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.PurgeComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.PinComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.RemoveComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.UpdateComment; @@ -236,13 +236,13 @@ public CommentResponse updateComment(UpdateComment updateCommentForm, Person per /** - * Removes a comment based on the provided key, comment remove form, and person. + * Removes a comment based on the provided key, comment pin form, and person. * * @param key The key of the comment to be removed. - * @param removeCommentForm The CommentRemove object representing the remove form data. + * @param removeCommentForm The CommentRemove object representing the pin form data. * @param person The Person object representing the user performing the removal. * @return A CommentResponse object representing the removed comment. - * @throws ResponseStatusException If the user does not have permission to remove the comment or + * @throws ResponseStatusException If the user does not have permission to pin the comment or * the comment is not found. */ public CommentResponse remove(String key, RemoveComment removeCommentForm, Person person) { @@ -267,13 +267,13 @@ public CommentResponse remove(String key, RemoveComment removeCommentForm, Perso * Deletes a comment based on the provided key, CommentDelete form, and Person. * * @param key The key of the comment to be deleted. - * @param deleteCommentForm The CommentDelete object representing the delete form data. + * @param purgeCommentForm The CommentDelete object representing the delete form data. * @param person The Person object representing the user performing the deletion. * @return A CommentResponse object representing the deleted comment. * @throws ResponseStatusException If the user does not have permission to delete the comment or * the comment is not found. */ - public CommentResponse delete(String key, DeleteComment deleteCommentForm, Person person) { + public void delete(String key, PurgeComment purgeCommentForm, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.DELETE_COMMENT, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_delete_not_permitted")); @@ -287,11 +287,10 @@ public CommentResponse delete(String key, DeleteComment deleteCommentForm, Perso throw new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_delete_not_permitted"); } - comment.setDeleted(deleteCommentForm.remove()); + comment.setDeleted(purgeCommentForm.remove()); commentService.updateComment(comment); // @todo: modlog - return conversionService.convert(comment, CommentResponse.class); } /** diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/RequestResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/RequestResponse.java new file mode 100644 index 00000000..8592f4cb --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/RequestResponse.java @@ -0,0 +1,10 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.common; + +import lombok.Builder; + +@Builder +public record RequestResponse( + Boolean success, + String error) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index 218ab3d1..d4107007 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -283,15 +283,15 @@ public Person banPerson(String key, String personKey, Person person, } /** - * Removes a community based on the provided key, remove comment, and person. + * Removes a community based on the provided key, pin comment, and person. * - * @param key The key of the community to remove. - * @param removeComment The comment specifying the reason for removal and whether to remove all + * @param key The key of the community to pin. + * @param removeComment The comment specifying the reason for removal and whether to pin all * content. * @param person The person performing the removal. * @return The response containing the removed community. * @throws ResponseStatusException If the community is not found, or the person is not authorized - * to remove the community. + * to pin the community. */ public CommunityResponse remove(String key, RemoveCommunity removeComment, Person person) { @@ -317,7 +317,7 @@ public CommunityResponse remove(String key, RemoveCommunity removeComment, Perso * * @param key The key of the community to delete. * @param deleteCommunityForm The delete form specifying the reason for deletion and whether to - * remove the community. + * pin the community. * @param person The person performing the deletion. * @return The response containing the deleted community. * @throws ResponseStatusException If the community is not found, or the person is not authorized diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/DeletePerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/DeletePerson.java index 7373f83e..49163642 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/DeletePerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/DeletePerson.java @@ -7,7 +7,7 @@ public record DeletePerson( @Schema(description = "The reason for deleting the person", example = "I dont use this account anymore", requiredMode = RequiredMode.NOT_REQUIRED) String reason, - @Schema(description = "Whether to remove your Post/Comments/Private Messages or not", + @Schema(description = "Whether to pin your Post/Comments/Private Messages or not", example = "true", defaultValue = "false", requiredMode = RequiredMode.NOT_REQUIRED) Boolean deleteContent) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java index 1d9ad95e..2a740243 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java @@ -1,12 +1,14 @@ package com.sublinks.sublinksapi.api.sublinks.v1.post.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.common.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.CreatePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.DeletePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.IndexPost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.UpdatePost; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.FavoritePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.services.SublinksPostService; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; @@ -50,11 +52,11 @@ public List index(final Optional indexPost, @GetMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void show(@PathVariable String key, final SublinksJwtPerson sublinksJwtPerson) { + public PostResponse show(@PathVariable String key, final SublinksJwtPerson sublinksJwtPerson) { final Optional person = getOptionalPerson(sublinksJwtPerson); - sublinksPostService.show(key, person.orElse(null)); + return sublinksPostService.show(key, person.orElse(null)); } @Operation(summary = "Create a new post") @@ -72,25 +74,43 @@ public void create(final CreatePost createPost, final SublinksJwtPerson sublinks @PostMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void update(final UpdatePost updatePostForm, @PathVariable final String key, + public PostResponse update(final UpdatePost updatePostForm, @PathVariable final String key, final SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - sublinksPostService.update(key, updatePostForm, person); + return sublinksPostService.update(key, updatePostForm, person); } @Operation(summary = "Delete an post") @DeleteMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void delete(@RequestBody final DeletePost deletePostForm, @PathVariable final String key, - final SublinksJwtPerson sublinksJwtPerson) + public RequestResponse delete(@PathVariable final String key, + @RequestBody final DeletePost deletePostForm, final SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); sublinksPostService.delete(key, deletePostForm, person); + + return RequestResponse.builder() + .success(true) + .build(); } + + @Operation(summary = "Favorite a post") + @PostMapping("{key}/favorite") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PostResponse favorite(@PathVariable final String key, + @RequestBody final FavoritePost favoritePostForm, final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksPostService.favorite(key, favoritePostForm, person); + } + } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java index f0a12b57..9e8f4d82 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java @@ -2,10 +2,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; -import com.sublinks.sublinksapi.api.sublinks.v1.post.models.CreatePost; -import com.sublinks.sublinksapi.api.sublinks.v1.post.models.IndexPost; -import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.post.models.UpdatePost; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.RemovePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.services.SublinksPostService; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; @@ -13,9 +10,11 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import lombok.AllArgsConstructor; -import org.springframework.web.bind.annotation.*; -import java.util.List; -import java.util.Optional; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping("api/v1/post/{key}/moderation") @@ -25,4 +24,18 @@ public class SublinksPostModerationController extends AbstractSublinksApiControl private final SublinksPostService sublinksPostService; + @Operation(summary = "Remove a post") + @PostMapping + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public void remove(@PathVariable final String key, @RequestBody final RemovePost removePostForm, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + sublinksPostService.remove(key, removePostForm, person); + } + + } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/FavoritePost.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/FavoritePost.java new file mode 100644 index 00000000..3f21286a --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/FavoritePost.java @@ -0,0 +1,15 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation; + +import io.swagger.v3.oas.annotations.media.Schema; + +public record FavoritePost( + @Schema(description = "If the post is favourited by you", + defaultValue = "true", + example = "true") Boolean favorite) { + + @Override + public Boolean favorite() { + + return favorite == null || favorite; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/PInPost.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/PInPost.java deleted file mode 100644 index 0415d2ce..00000000 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/PInPost.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation; - -public record PInPost( - String reason, - Boolean remove) { - - @Override - public Boolean remove() { - - return remove == null || remove; - } -} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/PinPost.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/PinPost.java new file mode 100644 index 00000000..bf3fd741 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/PinPost.java @@ -0,0 +1,5 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation; + +public record PinPost(Boolean pin) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index eef6f252..291640b4 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -1,14 +1,14 @@ package com.sublinks.sublinksapi.api.sublinks.v1.post.services; -import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.AggregateCommentResponse; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.AggregatePostResponse; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.CreatePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.DeletePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.IndexPost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.UpdatePost; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.FavoritePost; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.PinPost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.RemovePost; -import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommentTypes; import com.sublinks.sublinksapi.authorization.enums.RolePermissionPostTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.community.entities.Community; @@ -18,9 +18,12 @@ import com.sublinks.sublinksapi.language.repositories.LanguageRepository; import com.sublinks.sublinksapi.person.entities.Person; import com.sublinks.sublinksapi.person.enums.LinkPersonCommunityType; +import com.sublinks.sublinksapi.person.enums.LinkPersonPostType; import com.sublinks.sublinksapi.person.enums.ListingType; import com.sublinks.sublinksapi.person.enums.SortType; +import com.sublinks.sublinksapi.person.repositories.LinkPersonPostRepository; import com.sublinks.sublinksapi.person.services.LinkPersonCommunityService; +import com.sublinks.sublinksapi.person.services.LinkPersonPostService; import com.sublinks.sublinksapi.person.services.PersonService; import com.sublinks.sublinksapi.post.entities.Post; import com.sublinks.sublinksapi.post.entities.Post.PostBuilder; @@ -61,6 +64,8 @@ public class SublinksPostService { private final PostReportService postReportService; private final PersonService personService; private final PostAggregateRepository postAggregateRepository; + private final LinkPersonPostService linkPersonPostService; + private final LinkPersonPostRepository linkPersonPostRepository; /** * Retrieves a list of PostResponse objects based on the provided search criteria. @@ -383,11 +388,11 @@ public PostResponse update(final String postKey, final UpdatePost updatePostForm /** * Removes a post. * - * @param postKey The key of the post to remove. + * @param postKey The key of the post to pin. * @param removePostForm The RemovePost object containing additional parameters for the removal. * @param person The Person object representing the user performing the removal. * @return The PostResponse object for the removed post. - * @throws ResponseStatusException If the post is not found or the user is not permitted to remove + * @throws ResponseStatusException If the post is not found or the user is not permitted to pin * the post. */ public PostResponse remove(final String postKey, final RemovePost removePostForm, @@ -444,13 +449,103 @@ public PostResponse delete(final String postKey, final DeletePost deletePostForm return conversionService.convert(post, PostResponse.class); } + /** + * Retrieves the aggregated post information for the given post key and person. + * + * @param postKey The key of the post to aggregate. + * @param person The Person object representing the user. + * @return The aggregated post information as an AggregatePostResponse object. + * @throws ResponseStatusException If the post is not found or the user does not have permission + * to access the post. + */ public AggregatePostResponse aggregate(String postKey, Person person) { rolePermissionService.isPermitted(person, RolePermissionPostTypes.READ_POST_AGGREGATE, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "aggregate_post_permission_denied")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "aggregate_post_permission_denied")); return postAggregateRepository.findByPost_TitleSlug(postKey) .map(postAggregate -> conversionService.convert(postAggregate, AggregatePostResponse.class)) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found")); } + + + public PostResponse favorite(final String postKey, final FavoritePost favoritePostForm, + final Person person) + { + + rolePermissionService.isPermitted(person, RolePermissionPostTypes.FAVORITE_POST, + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "favorite_post_permission_denied")); + + final Post post = postRepository.findByTitleSlug(postKey) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found")); + + if (favoritePostForm.favorite()) { + if (linkPersonPostRepository.getLinkPersonPostByPostAndPersonAndLinkType(post, person, + LinkPersonPostType.follower) + .isEmpty()) { + linkPersonPostService.createLink(person, post, LinkPersonPostType.follower); + } + } else { + linkPersonPostService.removeLink(person, post, LinkPersonPostType.follower); + } + + return conversionService.convert(post, PostResponse.class); + } + + /** + * Pins a post. + * + * @param postKey The key of the post to pin. + * @param pinPostForm The PinPost object containing the pin information. + * @param person The Person object representing the user pinning the post. + * @return The PostResponse object for the pinned post. + * @throws ResponseStatusException If the post is not found or the user does not have permission + * to pin the post. + */ + public PostResponse pin(final String postKey, final PinPost pinPostForm, final Person person) { + + final Post post = postRepository.findByTitleSlug(postKey) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found")); + + if (!rolePermissionService.isPermitted(person, RolePermissionPostTypes.ADMIN_PIN_POST)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "pin_post_permission_denied"); + } + + post.setFeatured(pinPostForm.pin()); + postService.updatePost(post); + + return conversionService.convert(post, PostResponse.class); + } + + /** + * Pins a post in a community. + * + * @param postKey The key of the post to pin. + * @param pinPostForm The PinPost object containing the pin information. + * @param person The Person object representing the user pinning the post. + * @return The PostResponse object for the pinned post. + * @throws ResponseStatusException If the post is not found or the user does not have permission + * to pin the post. + */ + public PostResponse pinCommunity(final String postKey, final PinPost pinPostForm, + final Person person) + { + + final Post post = postRepository.findByTitleSlug(postKey) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found")); + + if (!rolePermissionService.isPermitted(person, RolePermissionPostTypes.ADMIN_PIN_POST) && !( + rolePermissionService.isPermitted(person, RolePermissionPostTypes.MODERATOR_PIN_POST) + && !linkPersonCommunityService.hasAnyLink(person, post.getCommunity(), + List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)))) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, + "instance_pin_post_permission_denied"); + } + + post.setFeaturedInCommunity(pinPostForm.pin()); + postService.updatePost(post); + + return conversionService.convert(post, PostResponse.class); + } } \ No newline at end of file diff --git a/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonPostService.java b/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonPostService.java index 6ad212eb..1820ff65 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonPostService.java @@ -44,7 +44,9 @@ public void createLink(Person person, Post post, LinkPersonPostType type) { person.getLinkPersonPost() .add(newLink); linkPersonPostRepository.save(newLink); - postLikeService.updateOrCreatePostLikeLike(post, person); + if (type == LinkPersonPostType.creator) { + postLikeService.updateOrCreatePostLikeLike(post, person); + } linkPersonPostCreatedPublisher.publish(newLink); } From 4cf90cbffb499847d3ff86e27878a3cf4d5b54c4 Mon Sep 17 00:00:00 2001 From: rooki Date: Sun, 7 Jul 2024 17:14:06 +0200 Subject: [PATCH 072/115] Add purge community operation and update delete action summary A new operation to purge a community was added to the Sublinks Community Moderation Controller. This operation, however, is still not implemented and will be completed in the future. The summary text for the delete operation in the Sublinks Community Controller was changed from 'Purge an community' to 'Delete a community'. Signed-off-by: rooki --- .../SublinksCommunityController.java | 2 +- ...SublinksCommunityModerationController.java | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index e20faef3..033efe45 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -95,7 +95,7 @@ public CommunityResponse update(@PathVariable final String key, return sublinksCommunityService.updateCommunity(key, updateCommunityForm, person); } - @Operation(summary = "Purge an community") + @Operation(summary = "Delete a community") @DeleteMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java index 44d4d464..ee920f47 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -1,6 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.common.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityBanPerson; @@ -196,4 +197,30 @@ public List banned(@PathVariable final String key) { .toList(); } + @Operation(summary = "Purge a community") + @PostMapping("/purge") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public RequestResponse purge(@PathVariable final String key, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + final Community community = communityRepository.findCommunityByTitleSlug(key) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); + + rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.PURGE_COMMUNITY, () -> { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_purge_community"); + }); + + // @todo: Implement purge + + return RequestResponse.builder() + .success(false) + .error("not_implemented") + .build(); + } + } From 9afff742919f32c2e53d2e60ae820036c8817e65 Mon Sep 17 00:00:00 2001 From: rooki Date: Tue, 9 Jul 2024 08:32:08 +0200 Subject: [PATCH 073/115] Add post pinning, community pinning, and post purging methods New functionalities added to SublinksPostModerationController: pinning a post, pinning a post in a community, and purging a post. The implementation for purging a post is not complete yet, but will be in future iterations. Additionally, the description for the Post Moderation API is updated. Existing functionality in the SublinksPostService is adjusted to allow the "featured" status of a post to be toggled during the pinning process. Signed-off-by: rooki --- .../SublinksPostModerationController.java | 50 ++++++++++++++++++- .../v1/post/services/SublinksPostService.java | 2 +- 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java index 9e8f4d82..47496c0f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java @@ -1,7 +1,10 @@ package com.sublinks.sublinksapi.api.sublinks.v1.post.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.common.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.PinPost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.RemovePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.services.SublinksPostService; import com.sublinks.sublinksapi.person.entities.Person; @@ -18,7 +21,7 @@ @RestController @RequestMapping("api/v1/post/{key}/moderation") -@Tag(name = "Post", description = "Post API") +@Tag(name = "Post Moderation", description = "Post Moderation API") @AllArgsConstructor public class SublinksPostModerationController extends AbstractSublinksApiController { @@ -37,5 +40,50 @@ public void remove(@PathVariable final String key, @RequestBody final RemovePost sublinksPostService.remove(key, removePostForm, person); } + @Operation(summary = "Pin a post") + @PostMapping("/pin") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PostResponse pin(@PathVariable final String key, @RequestBody final PinPost pinPostForm, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksPostService.pin(key, pinPostForm, person); + } + + @Operation(summary = "Pin a post in a community") + @PostMapping("/pin/community") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PostResponse pinInCommunity(@PathVariable final String key, + @RequestBody final PinPost pinPostForm, final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksPostService.pinCommunity(key, pinPostForm, person); + } + + + @Operation(summary = "Purge a post") + @PostMapping("/purge") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public RequestResponse delete(@PathVariable final String key, + @RequestBody final RemovePost removePostForm, final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + // @todo: implement + + return RequestResponse.builder() + .success(false) + .error("not_implemented") + .build(); + + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index 291640b4..4645a2e7 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -512,7 +512,7 @@ public PostResponse pin(final String postKey, final PinPost pinPostForm, final P throw new ResponseStatusException(HttpStatus.FORBIDDEN, "pin_post_permission_denied"); } - post.setFeatured(pinPostForm.pin()); + post.setFeatured(pinPostForm.pin() != null ? pinPostForm.pin() : !post.isFeatured()); postService.updatePost(post); return conversionService.convert(post, PostResponse.class); From 015d23e0dd642c763223f15c0a3cd151c3d64c9e Mon Sep 17 00:00:00 2001 From: rooki Date: Mon, 15 Jul 2024 17:10:35 +0200 Subject: [PATCH 074/115] Update permissions and search functionalities in API Enhancements were made to various API functionalities, including the addition of Instance Search and revised Private Message features. An array of new dependencies and properties were defined for instances and private message search criteria. SearchScopes now encompass posts, comments, communities, and instances. The changes aim to improve the user's search experience across categories and streamline the process of private message handling. Signed-off-by: rooki --- .../controllers/PrivateMessageController.java | 122 ++++--- .../models/GetPrivateMessages.java | 2 +- .../SublinksCommentController.java | 15 +- .../v1/comment/models/DeleteComment.java | 22 ++ .../v1/comment/models/IndexComment.java | 6 +- .../services/SublinksCommentService.java | 17 +- ...SublinksCommunityModerationController.java | 24 +- .../mappers/SublinksCommunityTypeMapper.java | 2 +- .../CommunityBanPerson.java | 2 +- .../CommunityModeratorResponse.java | 2 +- .../models/moderation/PurgeCommunity.java | 6 + .../RemoveCommunity.java | 2 +- .../services/SublinksCommunityService.java | 4 +- .../SublinksPersonModerationController.java | 31 +- .../sublinks/v1/post/models/IndexPost.java | 2 +- .../SublinksPrivatemessageController.java | 88 +++-- ...inksPrivatemessageModeratorController.java | 46 +++ .../models/CreatePrivateMessage.java | 7 + .../models/DeletePrivateMessage.java | 6 + .../models/IndexPrivateMessages.java | 23 ++ .../models/MarkAsReadPrivateMessage.java | 6 + .../models/UpdatePrivateMessage.java | 7 + .../moderation/PurgePrivateMessage.java | 6 + .../SublinksPrivateMessageService.java | 332 ++++++++++++++++++ .../sublinks/v1/search/enums/SearchScope.java | 10 + .../api/sublinks/v1/search/models/Search.java | 3 + .../v1/search/models/SearchResponse.java | 6 +- .../services/SublinksSearchService.java | 32 +- .../RolePermissionPrivateMessageTypes.java | 4 +- .../services/InitialRoleSetupService.java | 1 + .../models/PrivateMessageSearchCriteria.java | 4 +- .../services/PrivateMessageService.java | 39 +- 32 files changed, 776 insertions(+), 103 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/DeleteComment.java rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/{Moderation => moderation}/CommunityBanPerson.java (90%) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/{Moderation => moderation}/CommunityModeratorResponse.java (96%) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/moderation/PurgeCommunity.java rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/{Moderation => moderation}/RemoveCommunity.java (90%) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageModeratorController.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/CreatePrivateMessage.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/DeletePrivateMessage.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/IndexPrivateMessages.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/MarkAsReadPrivateMessage.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/UpdatePrivateMessage.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/moderation/PurgePrivateMessage.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/services/SublinksPrivateMessageService.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/enums/SearchScope.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/privatemessage/controllers/PrivateMessageController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/privatemessage/controllers/PrivateMessageController.java index e2878324..e851c3d7 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/privatemessage/controllers/PrivateMessageController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/privatemessage/controllers/PrivateMessageController.java @@ -70,11 +70,14 @@ public class PrivateMessageController extends AbstractLemmyApiController { private final RolePermissionService rolePermissionService; @Operation(summary = "Get / fetch private messages.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PrivateMessagesResponse.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = PrivateMessagesResponse.class))})}) @GetMapping("list") PrivateMessagesResponse list(@Valid final GetPrivateMessages getPrivateMessagesForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person sender = getPersonOrThrowUnauthorized(principal); @@ -95,7 +98,8 @@ PrivateMessagesResponse list(@Valid final GetPrivateMessages getPrivateMessagesF .perPage(perPage) .privateMessageSortType(sortType) .person(sender) - .unreadOnly(getPrivateMessagesForm.unread_only().orElse(false)) + .unreadOnly( + getPrivateMessagesForm.unread_only() != null && getPrivateMessagesForm.unread_only()) .build(); final List privateMessages = privateMessageRepository.allPrivateMessagesBySearchCriteria( @@ -104,18 +108,25 @@ PrivateMessagesResponse list(@Valid final GetPrivateMessages getPrivateMessagesF privateMessages.forEach(privateMessage -> privateMessageViews.add( lemmyPrivateMessageService.createPrivateMessageView(privateMessage))); - return PrivateMessagesResponse.builder().private_messages(privateMessageViews).build(); + return PrivateMessagesResponse.builder() + .private_messages(privateMessageViews) + .build(); } @Operation(summary = "Create a private message.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PrivateMessageResponse.class))}), - @ApiResponse(responseCode = "400", description = "Recipient Not Found", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ApiError.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = PrivateMessageResponse.class))}), @ApiResponse( + responseCode = "400", + description = "Recipient Not Found", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ApiError.class))})}) @PostMapping PrivateMessageResponse create( @Valid @RequestBody final CreatePrivateMessage createPrivateMessageForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person sender = getPersonOrThrowUnauthorized(principal); @@ -139,17 +150,24 @@ PrivateMessageResponse create( final PrivateMessageView privateMessageView = lemmyPrivateMessageService.createPrivateMessageView( privateMessage); - return PrivateMessageResponse.builder().private_message_view(privateMessageView).build(); + return PrivateMessageResponse.builder() + .private_message_view(privateMessageView) + .build(); } @Operation(summary = "Edit a private message.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PrivateMessageResponse.class))}), - @ApiResponse(responseCode = "400", description = "Private Message Not Found", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ApiError.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = PrivateMessageResponse.class))}), @ApiResponse( + responseCode = "400", + description = "Private Message Not Found", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ApiError.class))})}) @PutMapping PrivateMessageResponse update(@Valid @RequestBody final EditPrivateMessage editPrivateMessageForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -168,18 +186,25 @@ PrivateMessageResponse update(@Valid @RequestBody final EditPrivateMessage editP final PrivateMessageView privateMessageView = lemmyPrivateMessageService.createPrivateMessageView( privateMessage); - return PrivateMessageResponse.builder().private_message_view(privateMessageView).build(); + return PrivateMessageResponse.builder() + .private_message_view(privateMessageView) + .build(); } @Operation(summary = "Delete a private message.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PrivateMessageResponse.class))}), - @ApiResponse(responseCode = "400", description = "Private Message Not Found", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ApiError.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = PrivateMessageResponse.class))}), @ApiResponse( + responseCode = "400", + description = "Private Message Not Found", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ApiError.class))})}) @PostMapping("delete") PrivateMessageResponse delete( @Valid @RequestBody final DeletePrivateMessage deletePrivateMessageForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -196,18 +221,25 @@ PrivateMessageResponse delete( final PrivateMessageView privateMessageView = lemmyPrivateMessageService.createPrivateMessageView( privateMessage); - return PrivateMessageResponse.builder().private_message_view(privateMessageView).build(); + return PrivateMessageResponse.builder() + .private_message_view(privateMessageView) + .build(); } @Operation(summary = "Mark a private message as read.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PrivateMessageResponse.class))}), - @ApiResponse(responseCode = "400", description = "Private Message Not Found", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ApiError.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = PrivateMessageResponse.class))}), @ApiResponse( + responseCode = "400", + description = "Private Message Not Found", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ApiError.class))})}) @PostMapping("mark_as_read") PrivateMessageResponse markAsRead( @Valid @RequestBody MarkPrivateMessageAsRead markPrivateMessageAsReadForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -226,16 +258,21 @@ PrivateMessageResponse markAsRead( final PrivateMessageView privateMessageView = lemmyPrivateMessageService.createPrivateMessageView( privateMessage); - return PrivateMessageResponse.builder().private_message_view(privateMessageView).build(); + return PrivateMessageResponse.builder() + .private_message_view(privateMessageView) + .build(); } @Operation(summary = "Create a report for a private message.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PrivateMessageReportResponse.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = PrivateMessageReportResponse.class))})}) @PostMapping("report") PrivateMessageReportResponse report( @Valid @RequestBody final CreatePrivateMessageReport privateMessageReportForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -265,17 +302,19 @@ PrivateMessageReportResponse report( } @Operation(summary = "Resolve a report for a private message.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = PrivateMessageReportResponse.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = PrivateMessageReportResponse.class))})}) @PutMapping("report/resolve") PrivateMessageReportResponse reportResolve( @Valid @RequestBody ResolvePrivateMessageReport privateMessageReportForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); - rolePermissionService.isPermitted(person, - RolePermissionInstanceTypes.REPORT_INSTANCE_RESOLVE, + rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.REPORT_INSTANCE_RESOLVE, () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); final PrivateMessageReport privateMessageReport = privateMessageReportRepository.findById( @@ -295,12 +334,15 @@ PrivateMessageReportResponse reportResolve( } @Operation(summary = "List private message reports.") - @ApiResponses(value = {@ApiResponse(responseCode = "200", description = "OK", content = { - @Content(mediaType = MediaType.APPLICATION_JSON_VALUE, schema = @Schema(implementation = ListPrivateMessageReportsResponse.class))})}) + @ApiResponses(value = {@ApiResponse(responseCode = "200", + description = "OK", + content = {@Content(mediaType = MediaType.APPLICATION_JSON_VALUE, + schema = @Schema(implementation = ListPrivateMessageReportsResponse.class))})}) @GetMapping("report/list") ListPrivateMessageReportsResponse reportList( @Valid final ListPrivateMessageReports listPrivateMessageReportsForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/privatemessage/models/GetPrivateMessages.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/privatemessage/models/GetPrivateMessages.java index 36afc487..e1e9a839 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/privatemessage/models/GetPrivateMessages.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/privatemessage/models/GetPrivateMessages.java @@ -9,7 +9,7 @@ public record GetPrivateMessages( Integer page, Integer limit, Long creator_id, - Optional unread_only + Boolean unread_only ) { } \ No newline at end of file diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java index 2deaba54..e0cf1123 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java @@ -3,11 +3,12 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CreateComment; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.DeleteComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.IndexComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.UpdateComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; -import com.sublinks.sublinksapi.api.sublinks.v1.common.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -31,6 +32,7 @@ public class SublinksCommentController extends AbstractSublinksApiController { private final SublinksCommentService sublinksCommentService; + private final SublinksCommunityService sublinksCommunityService; @Operation(summary = "Get a list of comments") @@ -91,11 +93,12 @@ public CommentResponse update(@PathVariable String key, final UpdateComment crea @DeleteMapping("/{id}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public RequestResponse delete(@PathVariable String id) { - // TODO: implement + public CommentResponse delete(@PathVariable String id, final DeleteComment deleteCommentForm, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - return RequestResponse.builder() - .success(true) - .build(); + return sublinksCommentService.delete(id, deleteCommentForm, person); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/DeleteComment.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/DeleteComment.java new file mode 100644 index 00000000..e882640d --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/DeleteComment.java @@ -0,0 +1,22 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.comment.models; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import lombok.Builder; + +@Builder +public record DeleteComment( + @Schema(requiredMode = RequiredMode.REQUIRED, + description = "The reason for deleting the comment", + example = "Spam") String reason, + @Schema(requiredMode = RequiredMode.NOT_REQUIRED, + description = "Whether to remove the comment", + example = "true", + defaultValue = "true") Boolean remove) { + + @Override + public Boolean remove() { + + return remove == null || remove; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java index 02f52e53..5a257126 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/IndexComment.java @@ -15,9 +15,13 @@ public record IndexComment( @Schema(description = "Community key", requiredMode = RequiredMode.NOT_REQUIRED) String communityKey, @Schema(description = "Post key", requiredMode = RequiredMode.NOT_REQUIRED) String postKey, - @Schema(description = "Parent Comment key", requiredMode = RequiredMode.NOT_REQUIRED) String parentCommentKey, + @Schema(description = "Parent Comment key", + requiredMode = RequiredMode.NOT_REQUIRED) String parentCommentKey, @Schema(description = "Show NSFW", requiredMode = RequiredMode.NOT_REQUIRED) Boolean showNsfw, @Schema(description = "Saved only", requiredMode = RequiredMode.NOT_REQUIRED) Boolean savedOnly, + @Schema(description = "Max Depth", + requiredMode = RequiredMode.NOT_REQUIRED, + defaultValue = "3") Integer maxDepth, @Schema(description = "Per page", requiredMode = RequiredMode.NOT_REQUIRED) Integer perPage, @Schema(description = "Page", requiredMode = RequiredMode.NOT_REQUIRED) Integer page) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index b616d36b..d76025b5 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -3,8 +3,8 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.AggregateCommentResponse; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CreateComment; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.DeleteComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.IndexComment; -import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.PurgeComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.PinComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.RemoveComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.UpdateComment; @@ -118,6 +118,8 @@ public List index(final IndexComment indexCommentForm, final Pe .community(community.orElse(null)) .parent(parentComment.orElse(null)) .post(post.orElse(null)) + .maxDepth(Math.max( + Math.min(indexCommentForm.maxDepth() != null ? indexCommentForm.maxDepth() : 3, 5), 0)) .person(person); return commentRepository.allCommentsBySearchCriteria(commentSearchCriteria.build()) @@ -242,8 +244,8 @@ public CommentResponse updateComment(UpdateComment updateCommentForm, Person per * @param removeCommentForm The CommentRemove object representing the pin form data. * @param person The Person object representing the user performing the removal. * @return A CommentResponse object representing the removed comment. - * @throws ResponseStatusException If the user does not have permission to pin the comment or - * the comment is not found. + * @throws ResponseStatusException If the user does not have permission to pin the comment or the + * comment is not found. */ public CommentResponse remove(String key, RemoveComment removeCommentForm, Person person) { @@ -264,16 +266,16 @@ public CommentResponse remove(String key, RemoveComment removeCommentForm, Perso } /** - * Deletes a comment based on the provided key, CommentDelete form, and Person. + * Deletes a comment based on the provided key, delete comment form, and person. * * @param key The key of the comment to be deleted. - * @param purgeCommentForm The CommentDelete object representing the delete form data. + * @param deleteCommentForm The DeleteComment object representing the delete comment form data. * @param person The Person object representing the user performing the deletion. * @return A CommentResponse object representing the deleted comment. * @throws ResponseStatusException If the user does not have permission to delete the comment or * the comment is not found. */ - public void delete(String key, PurgeComment purgeCommentForm, Person person) { + public CommentResponse delete(String key, DeleteComment deleteCommentForm, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.DELETE_COMMENT, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_delete_not_permitted")); @@ -287,10 +289,11 @@ public void delete(String key, PurgeComment purgeCommentForm, Person person) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_delete_not_permitted"); } - comment.setDeleted(purgeCommentForm.remove()); + comment.setDeleted(deleteCommentForm.remove()); commentService.updateComment(comment); // @todo: modlog + return conversionService.convert(comment, CommentResponse.class); } /** diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java index ee920f47..f65679f6 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -4,9 +4,10 @@ import com.sublinks.sublinksapi.api.sublinks.v1.common.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityBanPerson; -import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityModeratorResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.RemoveCommunity; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.moderation.CommunityBanPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.moderation.CommunityModeratorResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.moderation.PurgeCommunity; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.moderation.RemoveCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommunityTypes; @@ -61,6 +62,23 @@ public CommunityResponse remove(@PathVariable final String key, return sublinksCommunityService.remove(key, removeCommunityForm, person); } + @Operation(summary = "Purge a community") + @PostMapping("/purge") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public RequestResponse purge(@PathVariable final String key, + @RequestBody @Valid PurgeCommunity purgeCommunityForm, SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + // @todo: Implement purge + + return RequestResponse.builder() + .success(true) + .build(); + } + @Operation(summary = "Get moderators of the community") @GetMapping("/moderators") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityTypeMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityTypeMapper.java index 8cb920ff..fa109d2b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityTypeMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/mappers/SublinksCommunityTypeMapper.java @@ -1,7 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.mappers; import com.sublinks.sublinksapi.api.sublinks.v1.community.enums.SublinksPersonCommunityType; -import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityModeratorResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.moderation.CommunityModeratorResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.mappers.SublinksPersonMapper; import com.sublinks.sublinksapi.person.entities.LinkPersonCommunity; import org.mapstruct.Mapper; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/moderation/CommunityBanPerson.java similarity index 90% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/moderation/CommunityBanPerson.java index ea95bb4b..97009ab3 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityBanPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/moderation/CommunityBanPerson.java @@ -1,4 +1,4 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; +package com.sublinks.sublinksapi.api.sublinks.v1.community.models.moderation; public record CommunityBanPerson( String reason, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityModeratorResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/moderation/CommunityModeratorResponse.java similarity index 96% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityModeratorResponse.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/moderation/CommunityModeratorResponse.java index c2cdab0f..05f633d0 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/CommunityModeratorResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/moderation/CommunityModeratorResponse.java @@ -1,4 +1,4 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; +package com.sublinks.sublinksapi.api.sublinks.v1.community.models.moderation; import com.sublinks.sublinksapi.api.sublinks.v1.community.enums.SublinksPersonCommunityType; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/moderation/PurgeCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/moderation/PurgeCommunity.java new file mode 100644 index 00000000..31eebe3c --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/moderation/PurgeCommunity.java @@ -0,0 +1,6 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.community.models.moderation; + +public record PurgeCommunity( + String reason) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/RemoveCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/moderation/RemoveCommunity.java similarity index 90% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/RemoveCommunity.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/moderation/RemoveCommunity.java index a4c6e272..41392d0d 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/Moderation/RemoveCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/moderation/RemoveCommunity.java @@ -1,4 +1,4 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation; +package com.sublinks.sublinksapi.api.sublinks.v1.community.models.moderation; public record RemoveCommunity( String reason, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index d4107007..8381f9b4 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -8,9 +8,9 @@ import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CreateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.DeleteCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.IndexCommunity; -import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.CommunityBanPerson; -import com.sublinks.sublinksapi.api.sublinks.v1.community.models.Moderation.RemoveCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.moderation.CommunityBanPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.community.models.moderation.RemoveCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.utils.ActorIdUtils; import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommunityTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java index 4ecfc52f..dfddaa1c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java @@ -1,10 +1,14 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.common.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.moderation.BanPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.services.SublinksPrivateMessageService; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionPrivateMessageTypes; +import com.sublinks.sublinksapi.authorization.services.AclService; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -26,6 +30,8 @@ public class SublinksPersonModerationController extends AbstractSublinksApiController { private final SublinksPersonService sublinksPersonService; + private final AclService aclService; + private final SublinksPrivateMessageService sublinksPrivateMessageService; @Operation(summary = "Ban a person") @GetMapping("/ban") @@ -45,7 +51,30 @@ public PersonResponse ban(@RequestBody @Valid BanPerson banPersonForm, @DeleteMapping("/purge") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void delete(@PathVariable String key) { + public RequestResponse purge(@PathVariable String key) { // TODO: implement + + return RequestResponse.builder() + .success(true) + .build(); + } + + @Operation(summary = "Delete/Purge an person ( as an admin )") + @DeleteMapping("/purge/privatemessages") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public RequestResponse purgeAll(@PathVariable String key, final SublinksJwtPerson jwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(jwtPerson); + + aclService.canPerson(person) + .performTheAction(RolePermissionPrivateMessageTypes.PURGE_PRIVATE_MESSAGES); + + sublinksPrivateMessageService.purgeAllPrivateMessages(person); + + return RequestResponse.builder() + .success(true) + .build(); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/IndexPost.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/IndexPost.java index d947296b..ba924b84 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/IndexPost.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/IndexPost.java @@ -30,6 +30,6 @@ public Integer perPage() { @Override public Integer page() { - return page != null ? page : 1; + return page != null ? page : 0; } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java index e4932aec..20983a28 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java @@ -1,10 +1,20 @@ package com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.controllers; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.CreatePrivateMessage; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.DeletePrivateMessage; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.IndexPrivateMessages; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.PrivateMessageResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.UpdatePrivateMessage; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.services.SublinksPrivateMessageService; +import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; +import java.util.Optional; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -17,48 +27,80 @@ @Tag(name = "Privatemessage", description = "Privatemessage API") public class SublinksPrivatemessageController extends AbstractSublinksApiController { + private final SublinksPrivateMessageService sublinksPrivateMessageService; + + public SublinksPrivatemessageController( + SublinksPrivateMessageService sublinksPrivateMessageService) + { + + super(); + this.sublinksPrivateMessageService = sublinksPrivateMessageService; + } + @Operation(summary = "Get a list of privatemessages") @GetMapping @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void index() { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List index(final IndexPrivateMessages indexPrivateMessagesForm, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Optional person = getOptionalPerson(sublinksJwtPerson); + + return sublinksPrivateMessageService.index(indexPrivateMessagesForm, person.orElse(null)); } @Operation(summary = "Get a specific privatemessage") - @GetMapping("/{id}") + @GetMapping("/{key}") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void show(@PathVariable String id) { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PrivateMessageResponse show(@PathVariable String key, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Optional person = getOptionalPerson(sublinksJwtPerson); + + return sublinksPrivateMessageService.show(key, person.orElse(null)); } @Operation(summary = "Create a new privatemessage") @PostMapping @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void create() { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PrivateMessageResponse create(final CreatePrivateMessage createPrivateMessageForm, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowBadRequest(sublinksJwtPerson); + + return sublinksPrivateMessageService.create(createPrivateMessageForm, person); } @Operation(summary = "Update an privatemessage") - @PostMapping("/{id}") + @PostMapping("/{key}") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void update(@PathVariable String id) { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PrivateMessageResponse update(@PathVariable String key, + final UpdatePrivateMessage updatePrivateMessageForm, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowBadRequest(sublinksJwtPerson); + + return sublinksPrivateMessageService.update(updatePrivateMessageForm, person); } @Operation(summary = "Delete an privatemessage") - @DeleteMapping("/{id}") + @DeleteMapping("/{key}") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void delete(@PathVariable String id) { - // TODO: implement + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PrivateMessageResponse delete(@PathVariable String key, + final DeletePrivateMessage deletePrivateMessageForm, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowBadRequest(sublinksJwtPerson); + + return sublinksPrivateMessageService.delete(key, deletePrivateMessageForm, person); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageModeratorController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageModeratorController.java new file mode 100644 index 00000000..b8a475db --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageModeratorController.java @@ -0,0 +1,46 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.controllers; + +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; +import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.DeletePrivateMessage; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.PrivateMessageResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.services.SublinksPrivateMessageService; +import com.sublinks.sublinksapi.person.entities.Person; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("api/v1/privatemessage/{key}/") +@Tag(name = "Privatemessage", description = "Privatemessage API") +public class SublinksPrivatemessageModeratorController extends AbstractSublinksApiController { + + private final SublinksPrivateMessageService sublinksPrivateMessageService; + + public SublinksPrivatemessageModeratorController( + SublinksPrivateMessageService sublinksPrivateMessageService) + { + + super(); + this.sublinksPrivateMessageService = sublinksPrivateMessageService; + } + + @Operation(summary = "Purge an privatemessage") + @DeleteMapping + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PrivateMessageResponse purge(@PathVariable String key, + final DeletePrivateMessage deletePrivateMessageForm, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowBadRequest(sublinksJwtPerson); + + return sublinksPrivateMessageService.delete(key, deletePrivateMessageForm, person); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/CreatePrivateMessage.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/CreatePrivateMessage.java new file mode 100644 index 00000000..7eceb56c --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/CreatePrivateMessage.java @@ -0,0 +1,7 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models; + +public record CreatePrivateMessage( + String recipientKey, + String message) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/DeletePrivateMessage.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/DeletePrivateMessage.java new file mode 100644 index 00000000..f4300899 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/DeletePrivateMessage.java @@ -0,0 +1,6 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models; + +public record DeletePrivateMessage( + Boolean removed) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/IndexPrivateMessages.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/IndexPrivateMessages.java new file mode 100644 index 00000000..e2c60e92 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/IndexPrivateMessages.java @@ -0,0 +1,23 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models; + +import com.sublinks.sublinksapi.privatemessages.enums.PrivateMessageSortType; + +public record IndexPrivateMessages( + String search, + Boolean unreadOnly, + PrivateMessageSortType sort, + Boolean localOnly, + String senderKey, + Integer page, + Integer perPage) { + + public Integer page() { + + return page != null ? page : 1; + } + + public Integer perPage() { + + return perPage != null ? perPage : 20; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/MarkAsReadPrivateMessage.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/MarkAsReadPrivateMessage.java new file mode 100644 index 00000000..843c4625 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/MarkAsReadPrivateMessage.java @@ -0,0 +1,6 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models; + +public record MarkAsReadPrivateMessage( + Boolean read) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/UpdatePrivateMessage.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/UpdatePrivateMessage.java new file mode 100644 index 00000000..50cae7fe --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/UpdatePrivateMessage.java @@ -0,0 +1,7 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models; + +public record UpdatePrivateMessage( + String privateMessageKey, + String message) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/moderation/PurgePrivateMessage.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/moderation/PurgePrivateMessage.java new file mode 100644 index 00000000..cf75173a --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/models/moderation/PurgePrivateMessage.java @@ -0,0 +1,6 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.moderation; + +public record PurgePrivateMessage( + String reason) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/services/SublinksPrivateMessageService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/services/SublinksPrivateMessageService.java new file mode 100644 index 00000000..31623f5d --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/services/SublinksPrivateMessageService.java @@ -0,0 +1,332 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.services; + +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.CreatePrivateMessage; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.DeletePrivateMessage; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.IndexPrivateMessages; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.MarkAsReadPrivateMessage; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.PrivateMessageResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.UpdatePrivateMessage; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionPrivateMessageTypes; +import com.sublinks.sublinksapi.authorization.services.AclService; +import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.post.repositories.PostRepository; +import com.sublinks.sublinksapi.privatemessages.entities.PrivateMessage; +import com.sublinks.sublinksapi.privatemessages.models.PrivateMessageSearchCriteria; +import com.sublinks.sublinksapi.privatemessages.repositories.PrivateMessageRepository; +import com.sublinks.sublinksapi.privatemessages.services.PrivateMessageService; +import java.util.List; +import java.util.stream.Collectors; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.ConversionService; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +@AllArgsConstructor +@Service +public class SublinksPrivateMessageService { + + + private final PrivateMessageRepository privateMessageRepository; + private final ConversionService conversionService; + private final PostRepository postRepository; + private final AclService aclService; + private final PrivateMessageService privateMessageService; + + /** + * Retrieves a list of private messages based on the given search criteria. + * + * @param indexPrivateMessagesForm The form containing the search criteria for filtering the + * private messages. + * @param person The person accessing the private messages. + * @return A list of PrivateMessageResponse objects containing the details of the private + * messages. + * @throws ResponseStatusException If the person is not allowed to read private messages. + */ + public List index(final IndexPrivateMessages indexPrivateMessagesForm, + final Person person) + { + + aclService.canPerson(person) + .performTheAction(RolePermissionPrivateMessageTypes.READ_PRIVATE_MESSAGES) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_allowed_to_read_private_messages")); + + return privateMessageRepository.allPrivateMessagesBySearchCriteria( + PrivateMessageSearchCriteria.builder() + .search(indexPrivateMessagesForm.search()) + .person(person) + .unreadOnly(indexPrivateMessagesForm.unreadOnly() != null + && indexPrivateMessagesForm.unreadOnly()) + .privateMessageSortType(indexPrivateMessagesForm.sort()) + .page(indexPrivateMessagesForm.page()) + .perPage(indexPrivateMessagesForm.perPage()) + .build()) + .stream() + .map(privateMessage -> conversionService.convert(privateMessage, + PrivateMessageResponse.class)) + .collect(Collectors.toList()); + } + + /** + * Retrieves the details of a private message. + * + * @param id The ID of the private message. + * @param person The person accessing the private message. + * @return The PrivateMessageResponse object containing the details of the private message. + * @throws ResponseStatusException If the person is not allowed to read private messages or if the + * private message is not found. + */ + public PrivateMessageResponse show(final String id, final Person person) { + + aclService.canPerson(person) + .performTheAction(RolePermissionPrivateMessageTypes.READ_PRIVATE_MESSAGES) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_allowed_to_read_private_messages")); + + final PrivateMessage privateMessage = privateMessageRepository.findById(Long.parseLong(id)) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, + "Image could not be written")); + + if (!privateMessage.getRecipient() + .equals(person) && !privateMessage.getSender() + .equals(person)) { + return null; + } + + return conversionService.convert(privateMessage, PrivateMessageResponse.class); + } + + /** + * Creates a new private message. + * + * @param createPrivateMessageForm The form containing the information of the new private + * message. + * @param person The person creating the private message. + * @return The newly created private message as a PrivateMessageResponse object. + * @throws ResponseStatusException If the person is not allowed to send private messages. + */ + public PrivateMessageResponse create(final CreatePrivateMessage createPrivateMessageForm, + final Person person) + { + + aclService.canPerson(person) + .performTheAction(RolePermissionPrivateMessageTypes.CREATE_PRIVATE_MESSAGE) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_allowed_to_send_private_messages")); + + final PrivateMessage privateMessage = PrivateMessage.builder() + .recipient(person) + .sender(person) + .content(createPrivateMessageForm.message()) + .isLocal(true) + .build(); + + privateMessageService.createPrivateMessage(privateMessage); + + return conversionService.convert(privateMessageRepository.save(privateMessage), + PrivateMessageResponse.class); + } + + /** + * Updates a private message. + * + * @param updatePrivateMessageForm The form containing the updated private message information. + * @param person The person performing the update. + * @return The updated private message as a PrivateMessageResponse object. + * @throws ResponseStatusException If the person is not allowed to update the private message or + * if the private message is not found. + */ + public PrivateMessageResponse update(final UpdatePrivateMessage updatePrivateMessageForm, + final Person person) + { + + aclService.canPerson(person) + .performTheAction(RolePermissionPrivateMessageTypes.UPDATE_PRIVATE_MESSAGE) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_allowed_to_update_private_messages")); + + final PrivateMessage privateMessage = privateMessageRepository.findById( + Long.parseLong(updatePrivateMessageForm.privateMessageKey())) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, + "Image could not be written")); + + if (!privateMessage.getRecipient() + .equals(person) && !privateMessage.getSender() + .equals(person)) { + return null; + } + + privateMessage.setContent(updatePrivateMessageForm.message()); + privateMessageService.updatePrivateMessage(privateMessage); + + return conversionService.convert(privateMessageRepository.save(privateMessage), + PrivateMessageResponse.class); + } + + /** + * Deletes a private message. + * + * @param id The ID of the private message to be deleted. + * @param deletePrivateMessageForm The form containing the deletion status to update. + * @param person The person performing the deletion. + * @return The response of the operation as a PrivateMessageResponse object. + * @throws ResponseStatusException If the person is not allowed to delete the private message or + * if the private message is not found. + */ + public PrivateMessageResponse delete(final String id, + final DeletePrivateMessage deletePrivateMessageForm, final Person person) + { + + aclService.canPerson(person) + .performTheAction(RolePermissionPrivateMessageTypes.DELETE_PRIVATE_MESSAGE) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_allowed_to_delete_private_messages")); + + final PrivateMessage privateMessage = privateMessageRepository.findById(Long.parseLong(id)) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "private_message_not_found")); + + if (!privateMessage.getSender() + .equals(person)) { + return null; + } + + privateMessage.setDeleted(deletePrivateMessageForm.removed()); + privateMessageService.updatePrivateMessage(privateMessage); + + return conversionService.convert(privateMessage, PrivateMessageResponse.class); + } + + /** + * Marks all private messages of a given person as read. + * + * @param person The person whose private messages should be marked as read. + */ + public void markAllAsRead(final Person person) { + + aclService.canPerson(person) + .performTheAction(RolePermissionPrivateMessageTypes.MARK_PRIVATE_MESSAGE_AS_READ) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_allowed_to_mark_as_read")); + + List privateMessages = privateMessageRepository.findByRecipientAndReadIsFalse( + person); + privateMessages.forEach(privateMessage -> { + privateMessage.setRead(true); + privateMessageService.updatePrivateMessage(privateMessage); + }); + } + + /** + * Marks a private message as read. + * + * @param id The ID of the private message to mark as read. + * @param markAsReadPrivateMessageForm The form containing the read status to update. + * @param person The person performing the action. + * @return The updated private message as a PrivateMessageResponse object. + * @throws ResponseStatusException If the person is not allowed to mark the private message as + * read or if the private message is not found. + */ + public PrivateMessageResponse markAsRead(final String id, + final MarkAsReadPrivateMessage markAsReadPrivateMessageForm, final Person person) + { + + aclService.canPerson(person) + .performTheAction(RolePermissionPrivateMessageTypes.MARK_PRIVATE_MESSAGE_AS_READ) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_allowed_to_mark_as_read")); + + final PrivateMessage privateMessage = privateMessageRepository.findById(Long.parseLong(id)) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "private_message_not_found")); + + if (!privateMessage.getRecipient() + .equals(person)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_allowed_to_mark_as_read"); + } + + privateMessage.setRead(markAsReadPrivateMessageForm.read()); + privateMessageService.updatePrivateMessage(privateMessage); + + return conversionService.convert(privateMessage, PrivateMessageResponse.class); + } + + /** + * Purge a specific private message belonging to a person. + * + * @param person The person who owns the private message. + * @param id The ID of the private message to purge. + * @return The response after purging the private message or null if the person is not authorized. + * @throws ResponseStatusException If the person is not allowed to delete private messages or if + * the private message is not found. + */ + public PrivateMessageResponse purgePrivateMessage(final Person person, final String id) { + + aclService.canPerson(person) + .performTheAction(RolePermissionPrivateMessageTypes.PURGE_PRIVATE_MESSAGE) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_allowed_to_delete_private_messages")); + + final PrivateMessage privateMessage = privateMessageRepository.findById(Long.parseLong(id)) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "private_message_not_found")); + + if (!privateMessage.getRecipient() + .equals(person) && !privateMessage.getSender() + .equals(person)) { + return null; + } + + privateMessageService.deletePrivateMessage(privateMessage); + + return conversionService.convert(privateMessage, PrivateMessageResponse.class); + } + + /** + * Purges a list of private messages belonging to a specific person. + * + * @param ids The list of IDs of the private messages to purge. + * @param person The person who owns the private messages. + * @return A list of PrivateMessageResponse objects representing the purged private messages. + * @throws ResponseStatusException If the person is not allowed to delete private messages or if + * any of the private messages specified by the IDs are not found. + */ + public List purgePrivateMessages(List ids, final Person person) { + + aclService.canPerson(person) + .performTheAction(RolePermissionPrivateMessageTypes.PURGE_PRIVATE_MESSAGES) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_allowed_to_delete_private_messages")); + + return privateMessageRepository.findAllById(ids.stream() + .map(Long::parseLong) + .collect(Collectors.toList())) + .stream() + .map(privateMessage -> { + privateMessageService.deletePrivateMessage(privateMessage); + return conversionService.convert(privateMessage, PrivateMessageResponse.class); + }) + .collect(Collectors.toList()); + } + + /** + * Purge all private messages belonging to a specific person. + * + * @param person The person for whom to purge all private messages. + * @throws ResponseStatusException If the person is not allowed to delete private messages. + */ + public List purgeAllPrivateMessages(final Person person) { + + aclService.canPerson(person) + .performTheAction(RolePermissionPrivateMessageTypes.PURGE_PRIVATE_MESSAGE) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, + "not_allowed_to_delete_private_messages")); + + return privateMessageService.deleteAllPrivateMessagesByPerson(person, true) + .stream() + .map(privateMessage -> conversionService.convert(privateMessage, + PrivateMessageResponse.class)) + .collect(Collectors.toList()); + } +} \ No newline at end of file diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/enums/SearchScope.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/enums/SearchScope.java new file mode 100644 index 00000000..167cfb93 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/enums/SearchScope.java @@ -0,0 +1,10 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.search.enums; + +public enum SearchScope { + POSTS, + COMMENTS, + USERS, + COMMUNITIES, + INSTANCES, + PRIVATE_MESSAGES, +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/Search.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/Search.java index 65458e19..31493bdc 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/Search.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/Search.java @@ -2,13 +2,16 @@ import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; +import com.sublinks.sublinksapi.api.sublinks.v1.search.enums.SearchScope; import lombok.Builder; +import java.util.Set; @Builder public record Search( String search, SortType type, SublinksListingType listingType, + Set scopes, Boolean showNsfw, Boolean savedOnly, Integer perPage, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/SearchResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/SearchResponse.java index 573bbf37..40bf2430 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/SearchResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/models/SearchResponse.java @@ -2,8 +2,10 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.InstanceResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.PrivateMessageResponse; import java.util.List; import lombok.Builder; @@ -13,6 +15,8 @@ public record SearchResponse( List persons, List communities, List posts, - List comments) { + List comments, + List instances, + List messages) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java index 3692ef6a..37e9138f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java @@ -4,10 +4,13 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.IndexCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.IndexInstance; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.service.SublinksInstanceService; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.IndexPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.IndexPost; import com.sublinks.sublinksapi.api.sublinks.v1.post.services.SublinksPostService; +import com.sublinks.sublinksapi.api.sublinks.v1.search.enums.SearchScope; import com.sublinks.sublinksapi.api.sublinks.v1.search.models.Search; import com.sublinks.sublinksapi.api.sublinks.v1.search.models.SearchResponse; import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommentTypes; @@ -33,6 +36,7 @@ public class SublinksSearchService { private final SublinksCommentService sublinksCommentService; private final SublinksCommunityService sublinksCommunityService; private final SublinksPostService sublinksPostService; + private final SublinksInstanceService sublinksInstanceService; /** * Perform a search and return the search result. @@ -55,8 +59,9 @@ public SearchResponse list(final Search searchForm, final Optional perso final SearchResponse.SearchResponseBuilder searchResponseBuilder = SearchResponse.builder(); - if (rolePermissionService.isPermitted(person.orElse(null), - RolePermissionPostTypes.READ_POSTS)) { + if (rolePermissionService.isPermitted(person.orElse(null), RolePermissionPostTypes.READ_POSTS) + && searchForm.scopes() + .contains(SearchScope.POSTS)) { searchResponseBuilder.persons(sublinksPersonService.index(IndexPerson.builder() .search(search) @@ -67,8 +72,9 @@ public SearchResponse list(final Search searchForm, final Optional perso .build(), person.orElse(null))); } - if (rolePermissionService.isPermitted(person.orElse(null), - RolePermissionPostTypes.READ_POSTS)) { + if (rolePermissionService.isPermitted(person.orElse(null), RolePermissionPostTypes.READ_POSTS) + && searchForm.scopes() + .contains(SearchScope.POSTS)) { searchResponseBuilder.posts(sublinksPostService.index(IndexPost.builder() .search(search) @@ -82,7 +88,8 @@ public SearchResponse list(final Search searchForm, final Optional perso } if (rolePermissionService.isPermitted(person.orElse(null), - RolePermissionCommentTypes.READ_COMMENTS)) { + RolePermissionCommentTypes.READ_COMMENTS) && searchForm.scopes() + .contains(SearchScope.COMMENTS)) { searchResponseBuilder.comments(sublinksCommentService.index(IndexComment.builder() .search(search) @@ -94,7 +101,8 @@ public SearchResponse list(final Search searchForm, final Optional perso } if (rolePermissionService.isPermitted(person.orElse(null), - RolePermissionCommunityTypes.READ_COMMUNITIES)) { + RolePermissionCommunityTypes.READ_COMMUNITIES) && searchForm.scopes() + .contains(SearchScope.COMMUNITIES)) { searchResponseBuilder.communities(sublinksCommunityService.index(IndexCommunity.builder() .search(search) @@ -106,6 +114,18 @@ public SearchResponse list(final Search searchForm, final Optional perso .build(), person.orElse(null))); } + if (rolePermissionService.isPermitted(person.orElse(null), + RolePermissionInstanceTypes.INSTANCE_SEARCH) && searchForm.scopes() + .contains(SearchScope.INSTANCES)) { + searchResponseBuilder.instances(sublinksInstanceService.index(IndexInstance.builder() + .search(search) + .page(page) + .perPage(perPage) + .build())); + } + + + return searchResponseBuilder.build(); } } diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPrivateMessageTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPrivateMessageTypes.java index 7a5904a3..50f81715 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPrivateMessageTypes.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionPrivateMessageTypes.java @@ -3,12 +3,14 @@ public enum RolePermissionPrivateMessageTypes implements RolePermissionInterface { // Person permissions - READ_PRIVATE_MESSAGES("message", AuthorizeAction.READ), + READ_PRIVATE_MESSAGES("messages", AuthorizeAction.READ), + READ_PRIVATE_MESSAGE("message", AuthorizeAction.READ), MARK_PRIVATE_MESSAGE_AS_READ("message-read", AuthorizeAction.UPDATE), CREATE_PRIVATE_MESSAGE("message", AuthorizeAction.CREATE), UPDATE_PRIVATE_MESSAGE("message", AuthorizeAction.UPDATE), DELETE_PRIVATE_MESSAGE("message", AuthorizeAction.DELETE), PURGE_PRIVATE_MESSAGE("message", AuthorizeAction.PURGE), + PURGE_PRIVATE_MESSAGES("messages", AuthorizeAction.PURGE), // Report permissions REPORT_PRIVATE_MESSAGE("message-report", AuthorizeAction.CREATE); diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java index cb8f50bc..1b3d3dc1 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java @@ -67,6 +67,7 @@ private void savePermissions(Role role, Set rolePermiss */ private void applyCommonPermissions(Set rolePermissions) { + rolePermissions.add(RolePermissionPrivateMessageTypes.READ_PRIVATE_MESSAGE); rolePermissions.add(RolePermissionPrivateMessageTypes.READ_PRIVATE_MESSAGES); rolePermissions.add(RolePermissionPostTypes.READ_POST); rolePermissions.add(RolePermissionPostTypes.READ_POSTS); diff --git a/src/main/java/com/sublinks/sublinksapi/privatemessages/models/PrivateMessageSearchCriteria.java b/src/main/java/com/sublinks/sublinksapi/privatemessages/models/PrivateMessageSearchCriteria.java index 04d914e1..4c0201f4 100644 --- a/src/main/java/com/sublinks/sublinksapi/privatemessages/models/PrivateMessageSearchCriteria.java +++ b/src/main/java/com/sublinks/sublinksapi/privatemessages/models/PrivateMessageSearchCriteria.java @@ -6,11 +6,11 @@ @Builder public record PrivateMessageSearchCriteria( + String search, PrivateMessageSortType privateMessageSortType, int perPage, int page, boolean unreadOnly, - Person person -) { + Person person) { } diff --git a/src/main/java/com/sublinks/sublinksapi/privatemessages/services/PrivateMessageService.java b/src/main/java/com/sublinks/sublinksapi/privatemessages/services/PrivateMessageService.java index bdbaf0e1..fea091c5 100644 --- a/src/main/java/com/sublinks/sublinksapi/privatemessages/services/PrivateMessageService.java +++ b/src/main/java/com/sublinks/sublinksapi/privatemessages/services/PrivateMessageService.java @@ -34,9 +34,11 @@ public class PrivateMessageService { private final PersonMentionRepository personMentionRepository; public String generateActivityPubId( - final com.sublinks.sublinksapi.privatemessages.entities.PrivateMessage privateMessage) { + final com.sublinks.sublinksapi.privatemessages.entities.PrivateMessage privateMessage) + { - String domain = localInstanceContext.instance().getDomain(); + String domain = localInstanceContext.instance() + .getDomain(); return String.format("%s/private_message/%d", domain, privateMessage.getId()); } @@ -62,6 +64,7 @@ public void updatePrivateMessage(final PrivateMessage privateMessage) { public void deletePrivateMessage(final PrivateMessage privateMessage) { privateMessageRepository.delete(privateMessage); + privateMessageDeletedPublisher.publish(privateMessage); } @Transactional @@ -97,16 +100,44 @@ public MarkAllAsReadResponse markAllAsRead(final Person person) { .build(); } + @Transactional + public PrivateMessage deletePrivateMessage(final String id) { + + return this.deletePrivateMessage(id, false); + } + + @Transactional + public PrivateMessage deletePrivateMessage(final String id, final boolean byAdmin) + { + + final PrivateMessage privateMessage = privateMessageRepository.findById(Long.parseLong(id)) + .orElseThrow(); + privateMessage.setDeleted(true); + privateMessage.setRead(true); + privateMessage.setContent("*Permanently deleted by " + (byAdmin ? "admin" : "creator") + "*"); + this.updatePrivateMessage(privateMessage); + return privateMessage; + } + @Transactional public List deleteAllPrivateMessagesByPerson(final Person person) { + return this.deleteAllPrivateMessagesByPerson(person, false); + + } + + @Transactional + public List deleteAllPrivateMessagesByPerson(final Person person, + final boolean byAdmin) + { + final List privateMessages = privateMessageRepository.findAllBySender(person); privateMessages.forEach(privateMessage -> { privateMessage.setDeleted(true); privateMessage.setRead(true); - privateMessage.setContent("*Permanently deleted by creator*"); + privateMessage.setContent("*Permanently deleted by " + (byAdmin ? "admin" : "creator") + "*"); - privateMessageDeletedPublisher.publish(privateMessageRepository.save(privateMessage)); + this.updatePrivateMessage(privateMessage); }); return privateMessages; } From 230837a12350d285091e335a5e42984baac9f9b1 Mon Sep 17 00:00:00 2001 From: rooki Date: Mon, 15 Jul 2024 19:30:18 +0200 Subject: [PATCH 075/115] Remove "purge" functionality and cleanup controllers The "purge" functionality has been removed from the SublinksCommunityModerationController. Additionally, unneeded component and dependencies were removed from the SublinksCommunityController, and the constructor in SublinksPrivatemessageModeratorController was replaced with @AllArgsConstructor for brevity. Signed-off-by: rooki --- .../SublinksCommunityController.java | 6 ----- ...SublinksCommunityModerationController.java | 27 ------------------- ...inksPrivatemessageModeratorController.java | 10 ++----- 3 files changed, 2 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index 033efe45..7a921b80 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -8,8 +8,6 @@ import com.sublinks.sublinksapi.api.sublinks.v1.community.models.IndexCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.UpdateCommunity; import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; -import com.sublinks.sublinksapi.community.repositories.CommunityRepository; -import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -19,7 +17,6 @@ import java.util.List; import java.util.Optional; import lombok.AllArgsConstructor; -import org.springframework.core.convert.ConversionService; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -35,10 +32,7 @@ @Tag(name = "Community", description = "Community API") public class SublinksCommunityController extends AbstractSublinksApiController { - private final CommunityRepository communityRepository; private final SublinksCommunityService sublinksCommunityService; - private final ConversionService conversionService; - private final LocalInstanceContext localInstanceContext; @Operation(summary = "Get a list of communities") @GetMapping diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java index f65679f6..0a0c6f04 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -214,31 +214,4 @@ public List banned(@PathVariable final String key) { .map(person -> conversionService.convert(person, PersonResponse.class)) .toList(); } - - @Operation(summary = "Purge a community") - @PostMapping("/purge") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public RequestResponse purge(@PathVariable final String key, - final SublinksJwtPerson sublinksJwtPerson) - { - - final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - - final Community community = communityRepository.findCommunityByTitleSlug(key) - .orElseThrow( - () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - - rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.PURGE_COMMUNITY, () -> { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_purge_community"); - }); - - // @todo: Implement purge - - return RequestResponse.builder() - .success(false) - .error("not_implemented") - .build(); - } - } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageModeratorController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageModeratorController.java index b8a475db..1ceaf38f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageModeratorController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageModeratorController.java @@ -10,11 +10,13 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@AllArgsConstructor @RestController @RequestMapping("api/v1/privatemessage/{key}/") @Tag(name = "Privatemessage", description = "Privatemessage API") @@ -22,14 +24,6 @@ public class SublinksPrivatemessageModeratorController extends AbstractSublinksA private final SublinksPrivateMessageService sublinksPrivateMessageService; - public SublinksPrivatemessageModeratorController( - SublinksPrivateMessageService sublinksPrivateMessageService) - { - - super(); - this.sublinksPrivateMessageService = sublinksPrivateMessageService; - } - @Operation(summary = "Purge an privatemessage") @DeleteMapping @ApiResponses(value = { From 0559f32fb97ae290d91358fd88394e5740dc1ada Mon Sep 17 00:00:00 2001 From: rooki Date: Tue, 16 Jul 2024 16:29:47 +0200 Subject: [PATCH 076/115] Change request mapping in SublinksPostAggregateController The request mapping for the SublinksPostAggregateController class has been updated. Previously, it was incorrectly mapped to "api/v1/comment/{key}/aggregate". It has been corrected to "api/v1/post/{key}/aggregate". Signed-off-by: rooki --- .../v1/post/controllers/SublinksPostAggerateController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostAggerateController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostAggerateController.java index db3efc85..64cc5501 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostAggerateController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostAggerateController.java @@ -18,7 +18,7 @@ @RestController @AllArgsConstructor -@RequestMapping("api/v1/comment/{key}/aggregate") +@RequestMapping("api/v1/post/{key}/aggregate") @Tag(name = "Comment Aggregation", description = "Comment Aggregate API") public class SublinksPostAggerateController extends AbstractSublinksApiController { From c4e12ede180b78924d94b89d965d58dff09e9212 Mon Sep 17 00:00:00 2001 From: rooki Date: Fri, 2 Aug 2024 11:42:06 +0200 Subject: [PATCH 077/115] Enhance role management and authorization handling. Removed `Role` model and replaced it with new event-based role management. Introduced publishers and events for role creation, updates, and deletion, and updated controllers and mappers accordingly. Additionally, simplified authorization error messages for consistency. Signed-off-by: rooki --- .../CommunityModActionsController.java | 10 +- .../controllers/PostModActionsController.java | 8 +- .../v3/user/controllers/UserController.java | 20 ++-- .../services/SublinksAnnouncementService.java | 12 +- .../SublinksCommentController.java | 10 +- .../v1/comment/models/UpdateComment.java | 1 - .../services/SublinksCommentService.java | 38 +++--- ...SublinksCommunityModerationController.java | 8 +- .../services/SublinksCommunityService.java | 26 ++-- .../SublinksInstanceConfigController.java | 8 -- .../service/SublinksInstanceService.java | 4 +- .../SublinksPersonAggregationController.java | 2 +- .../SublinksPersonAggregationMapper.java | 4 +- .../person/mappers/SublinksPersonMapper.java | 5 +- .../mappers/SublinksPersonMetaDataMapper.java | 4 +- .../v1/person/models/PersonResponse.java | 4 +- .../services/SublinksPersonService.java | 30 ++--- .../v1/post/services/SublinksPostService.java | 22 ++-- .../SublinksPrivatemessageController.java | 10 +- .../SublinksPrivateMessageService.java | 22 ++-- .../controllers/SublinksRolesController.java | 39 +++--- ...eMapper.java => SublinksPersonMapper.java} | 8 +- .../mappers/SublinksPersonRoleMapper.java | 56 +++++++++ .../sublinks/v1/roles/models/CreateRole.java | 11 ++ .../sublinks/v1/roles/models/IndexRole.java | 11 ++ .../v1/roles/models/PersonRoleResponse.java | 22 ++++ .../api/sublinks/v1/roles/models/Role.java | 16 --- .../v1/roles/models/RoleResponse.java | 22 ++++ .../sublinks/v1/roles/models/UpdateRole.java | 11 ++ .../roles/services/SublinksRoleService.java | 113 ++++++++++++++++++ .../services/SublinksSearchService.java | 2 +- .../authorization/entities/Role.java | 2 +- .../enums/RolePermissionRoleTypes.java | 27 +++++ .../events/RoleCreatedEvent.java | 21 ++++ .../events/RoleCreatedPublisher.java | 23 ++++ .../events/RoleDeletedEvent.java | 18 +++ .../events/RoleDeletedPublisher.java | 23 ++++ .../events/RoleUpdatedEvent.java | 18 +++ .../events/RoleUpdatedPublisher.java | 23 ++++ .../repositories/RoleRepository.java | 14 +-- .../authorization/services/RoleService.java | 58 ++++++++- .../sublinksapi/common/enums/SortOrder.java | 6 + ...20231003__Create_initial_entity_tables.sql | 2 + 43 files changed, 604 insertions(+), 190 deletions(-) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/{SublinksRoleMapper.java => SublinksPersonMapper.java} (84%) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPersonRoleMapper.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/CreateRole.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/IndexRole.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/PersonRoleResponse.java delete mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/Role.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/RoleResponse.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/UpdateRole.java create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java create mode 100644 src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionRoleTypes.java create mode 100644 src/main/java/com/sublinks/sublinksapi/authorization/events/RoleCreatedEvent.java create mode 100644 src/main/java/com/sublinks/sublinksapi/authorization/events/RoleCreatedPublisher.java create mode 100644 src/main/java/com/sublinks/sublinksapi/authorization/events/RoleDeletedEvent.java create mode 100644 src/main/java/com/sublinks/sublinksapi/authorization/events/RoleDeletedPublisher.java create mode 100644 src/main/java/com/sublinks/sublinksapi/authorization/events/RoleUpdatedEvent.java create mode 100644 src/main/java/com/sublinks/sublinksapi/authorization/events/RoleUpdatedPublisher.java create mode 100644 src/main/java/com/sublinks/sublinksapi/common/enums/SortOrder.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java index d6861b08..d76c8d2e 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java @@ -176,7 +176,7 @@ CommunityResponse remove(@Valid @RequestBody final RemoveCommunity removeCommuni if (!linkPersonCommunityService.hasAnyLink(person, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_allowed"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } community.setRemoved(removeCommunityForm.removed()); @@ -221,7 +221,7 @@ GetCommunityResponse transfer(@Valid @RequestBody final TransferCommunity transf () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); if (!linkPersonCommunityService.hasLinkOrAdmin(person, community, LinkPersonCommunityType.owner)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_allowed"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } final Person newOwner = personRepository.findById((long) transferCommunityForm.person_id()) @@ -273,7 +273,7 @@ BanFromCommunityResponse banUser(@Valid @RequestBody final BanFromCommunity banP final Person person = getPersonOrThrowUnauthorized(principal); rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.MODERATOR_BAN_USER, - () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "not_allowed")); + () -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); final Community community = communityRepository.findById((long) banPersonForm.community_id()) .orElseThrow( @@ -281,7 +281,7 @@ BanFromCommunityResponse banUser(@Valid @RequestBody final BanFromCommunity banP if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_allowed"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } final Person personToBan = personRepository.findById((long) banPersonForm.person_id()) @@ -360,7 +360,7 @@ AddModToCommunityResponse addMod(@Valid @RequestBody AddModToCommunity addModToC LinkPersonCommunityType.owner); if (!isAllowed) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_allowed"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } final Person personToAdd = personRepository.findById((long) addModToCommunityForm.person_id()) 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 7fe6aa83..f79ab7a8 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 @@ -104,7 +104,7 @@ PostResponse remove(@Valid @RequestBody final ModRemovePost modRemovePostForm, post.getCommunity(), LinkPersonCommunityType.owner); if (!moderatesCommunity) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_a_moderator"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } } @@ -160,7 +160,7 @@ PostResponse lock(@Valid @RequestBody final ModLockPost modLockPostForm, JwtPers post.getCommunity(), LinkPersonCommunityType.owner); if (!moderatesCommunity) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_a_moderator"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } } @@ -209,7 +209,7 @@ PostResponse feature(@Valid @RequestBody FeaturePost featurePostForm, JwtPerson post.getCommunity(), LinkPersonCommunityType.owner); if (!moderatesCommunity) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_a_moderator"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } } switch (featurePostForm.feature_type()) { @@ -218,7 +218,7 @@ PostResponse feature(@Valid @RequestBody FeaturePost featurePostForm, JwtPerson break; case Local: if (!isAdmin) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_a_admin"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } post.setFeatured(featurePostForm.featured()); break; diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/user/controllers/UserController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/user/controllers/UserController.java index f6e9bba0..5a115056 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/user/controllers/UserController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/user/controllers/UserController.java @@ -127,7 +127,7 @@ GetPersonDetailsResponse show(@Valid final GetPersonDetails getPersonDetailsForm } rolePermissionService.isPermitted(person, RolePermissionPersonTypes.READ_USER, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "no_permission")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); return GetPersonDetailsResponse.builder() .person_view(lemmyPersonService.getPersonView(person)) @@ -150,7 +150,7 @@ GetPersonMentionsResponse mention(@Valid final GetPersonMentions getPersonMentio final Person person = getPersonOrThrowUnauthorized(principal); rolePermissionService.isPermitted(person, RolePermissionPersonTypes.READ_MENTION_USER, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "no_permission")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final int page = PaginationControllerUtils.getAbsoluteMinNumber(getPersonMentionsForm.page(), 1); @@ -191,7 +191,7 @@ PersonMentionResponse mentionMarkAsRead( final Person person = getPersonOrThrowUnauthorized(principal); rolePermissionService.isPermitted(person, RolePermissionPersonTypes.MARK_MENTION_AS_READ, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "no_permission")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final PersonMention personMention = personMentionRepository.findById( (long) markPersonMentionAsReadForm.person_mention_id()) @@ -221,7 +221,7 @@ GetRepliesResponse replies(@Valid final GetReplies getReplies, JwtPerson princip final Person person = getPersonOrThrowUnauthorized(principal); rolePermissionService.isPermitted(person, RolePermissionPersonTypes.READ_REPLIES, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "no_permission")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final int page = PaginationControllerUtils.getAbsoluteMinNumber(getReplies.page(), 1); final int perPage = PaginationControllerUtils.getAbsoluteMinNumber(getReplies.limit(), 20); @@ -256,7 +256,7 @@ BannedPersonsResponse bannedList(final JwtPerson principal) { final Person person = getPersonOrThrowUnauthorized(principal); rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_BAN_READ, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "no_permission")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final Collection bannedPersons = roleService.getBannedUsers() .stream() @@ -281,7 +281,7 @@ GetRepliesResponse markAllAsRead(final JwtPerson principal) { final Person person = getPersonOrThrowUnauthorized(principal); rolePermissionService.isPermitted(person, RolePermissionPersonTypes.READ_REPLIES, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "no_permission")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final MarkAllAsReadResponse readReplies = privateMessageService.markAllAsRead(person); @@ -310,7 +310,7 @@ public LoginResponse saveUserSettings(@Valid @RequestBody SaveUserSettings saveU rolePermissionService.isPermitted(person, RolePermissionPersonTypes.UPDATE_USER_SETTINGS, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "no_permission_to_update_user_settings")); + "unauthorized")); // @todo expand form validation to check for email formatting, etc. if (saveUserSettingsForm.show_nsfw() != null) { @@ -437,7 +437,7 @@ GetUnreadCountResponse unreadCount(@Valid final GetUnreadCount getUnreadCountFor rolePermissionService.isPermitted(person, Set.of(RolePermissionPersonTypes.READ_MENTION_USER, RolePermissionPersonTypes.READ_REPLIES, RolePermissionPrivateMessageTypes.READ_PRIVATE_MESSAGES), - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "no_permission")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); GetUnreadCountResponse.GetUnreadCountResponseBuilder builder = GetUnreadCountResponse.builder(); @@ -475,7 +475,7 @@ SuccessResponse import_settings(@Valid final ImportSettings importSettingsForm, rolePermissionService.isPermitted(person, RolePermissionPersonTypes.UPDATE_USER_SETTINGS, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "no_permission_to_update_user_settings")); + "unauthorized")); final SuccessResponse.SuccessResponseBuilder builder = SuccessResponse.builder(); @@ -558,7 +558,7 @@ ExportSettingsResponse export_settings(final JwtPerson principal) { final Person person = getPersonOrThrowUnauthorized(principal); rolePermissionService.isPermitted(person, RolePermissionPersonTypes.USER_EXPORT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "no_permission")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); ExportSettingsResponse.ExportSettingsResponseBuilder builder = ExportSettingsResponse.builder(); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java index b64e3533..b8b2f41a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java @@ -48,7 +48,7 @@ public List index(final IndexAnnouncement indexAnnouncemen rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_READ_ANNOUNCEMENTS, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "forbidden_to_read_announcements")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final List announcements = this.announcementRepository.findAll( PageRequest.of(indexAnnouncementForm.page() - 1, indexAnnouncementForm.perPage(), Sort.by( @@ -77,7 +77,7 @@ public AnnouncementResponse show(final Long key, final Person person) { rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_READ_ANNOUNCEMENT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "forbidden_to_read_announcement")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); try { final Announcement announcement = this.announcementRepository.getReferenceById(key); @@ -103,7 +103,7 @@ public AnnouncementResponse create(final CreateAnnouncement createAnnouncementFo rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_CREATE_ANNOUNCEMENT, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "forbidden_to_create_announcement")); + "unauthorized")); final Announcement announcement = Announcement.builder() .content(createAnnouncementForm.content()) @@ -133,7 +133,7 @@ public AnnouncementResponse update(final Long id, final UpdateAnnouncement updat rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_UPDATE_ANNOUNCEMENT, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "forbidden_to_update_announcement")); + "unauthorized")); final Announcement announcement = this.announcementRepository.getReferenceById(id); @@ -162,7 +162,7 @@ public AnnouncementResponse remove(final Long id, final RemoveAnnouncement remov rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_DELETE_ANNOUNCEMENT, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "forbidden_to_delete_announcement")); + "unauthorized")); final Announcement announcement = this.announcementRepository.getReferenceById(id); @@ -185,7 +185,7 @@ public AnnouncementResponse delete(final Long id, final Person person) { rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_DELETE_ANNOUNCEMENT, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "forbidden_to_delete_announcement")); + "unauthorized")); final Announcement announcement = this.announcementRepository.getReferenceById(id); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java index e0cf1123..aa6ee966 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java @@ -8,7 +8,6 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.UpdateComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; -import com.sublinks.sublinksapi.api.sublinks.v1.community.services.SublinksCommunityService; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -32,7 +31,6 @@ public class SublinksCommentController extends AbstractSublinksApiController { private final SublinksCommentService sublinksCommentService; - private final SublinksCommunityService sublinksCommunityService; @Operation(summary = "Get a list of comments") @@ -86,19 +84,19 @@ public CommentResponse update(@PathVariable String key, final UpdateComment crea final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - return sublinksCommentService.updateComment(createCommentForm, person); + return sublinksCommentService.updateComment(key, createCommentForm, person); } @Operation(summary = "Delete an comment") - @DeleteMapping("/{id}") + @DeleteMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommentResponse delete(@PathVariable String id, final DeleteComment deleteCommentForm, + public CommentResponse delete(@PathVariable String key, final DeleteComment deleteCommentForm, final SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - return sublinksCommentService.delete(id, deleteCommentForm, person); + return sublinksCommentService.delete(key, deleteCommentForm, person); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/UpdateComment.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/UpdateComment.java index a8a72ebb..9c7da67f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/UpdateComment.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/UpdateComment.java @@ -6,7 +6,6 @@ @Builder public record UpdateComment( - String commentKey, String body, @Schema(description = "The language key of the comment", defaultValue = "und", diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index d76025b5..3f9fd4ed 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -69,7 +69,7 @@ public class SublinksCommentService { public List index(final IndexComment indexCommentForm, final Person person) { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.READ_COMMENTS, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_update_not_permitted")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); Optional parentComment = Optional.empty(); if (indexCommentForm.postKey() != null) { @@ -140,7 +140,7 @@ public List index(final IndexComment indexCommentForm, final Pe public CommentResponse show(String key, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.READ_COMMENT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_view_not_permitted")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); return commentRepository.findByPath(key) .map(comment -> conversionService.convert(comment, CommentResponse.class)) @@ -161,7 +161,7 @@ public CommentResponse show(String key, Person person) { public CommentResponse createComment(CreateComment createComment, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.CREATE_COMMENT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_create_not_permitted")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final Post parentPost = postRepository.findByTitleSlugAndRemovedStateIs(createComment.postKey(), RemovedState.NOT_REMOVED) @@ -193,27 +193,29 @@ public CommentResponse createComment(CreateComment createComment, Person person) } /** - * Updates a comment based on the provided form and person. + * Updates a comment based on the provided comment key, UpdateComment form, and user. * - * @param updateCommentForm The UpdateComment object representing the updated data for the - * comment. - * @param person The Person object representing the person performing the update. + * @param commentKey The key of the comment to be updated. + * @param updateCommentForm The UpdateComment object representing the updated comment data. + * @param person The Person object representing the user performing the update. * @return A CommentResponse object representing the updated comment. - * @throws ResponseStatusException If the user does not have permission to update the comment, the + * @throws ResponseStatusException If the user is not authorized to update the comment, the * comment is not found, or the language is not found. */ - public CommentResponse updateComment(UpdateComment updateCommentForm, Person person) { + public CommentResponse updateComment(final String commentKey, UpdateComment updateCommentForm, + Person person) + { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.UPDATE_COMMENT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_update_not_permitted")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); - Comment comment = commentRepository.findByPath(updateCommentForm.commentKey()) + Comment comment = commentRepository.findByPath(commentKey) .orElseThrow( () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); if (!Objects.equals(comment.getPerson() .getId(), person.getId())) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_update_not_permitted"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } if (updateCommentForm.featured() != null) { @@ -250,7 +252,7 @@ public CommentResponse updateComment(UpdateComment updateCommentForm, Person per public CommentResponse remove(String key, RemoveComment removeCommentForm, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.REMOVE_COMMENT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_remove_not_permitted")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); Comment comment = commentRepository.findByPath(key) .orElseThrow( @@ -278,7 +280,7 @@ public CommentResponse remove(String key, RemoveComment removeCommentForm, Perso public CommentResponse delete(String key, DeleteComment deleteCommentForm, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.DELETE_COMMENT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_delete_not_permitted")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); Comment comment = commentRepository.findByPath(key) .orElseThrow( @@ -286,7 +288,7 @@ public CommentResponse delete(String key, DeleteComment deleteCommentForm, Perso if (!Objects.equals(comment.getPerson() .getId(), person.getId())) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_delete_not_permitted"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } comment.setDeleted(deleteCommentForm.remove()); @@ -310,14 +312,14 @@ public CommentResponse delete(String key, DeleteComment deleteCommentForm, Perso public CommentResponse pin(String key, PinComment pinCommentForm, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.MODERATOR_PIN_COMMENT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_highlight_not_permitted")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); Comment comment = commentRepository.findByPath(key) .orElseThrow( () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); if (linkPersonCommunityService.hasAnyLinkOrAdmin(person, comment.getCommunity(), List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_highlight_not_permitted"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } comment.setFeatured( @@ -339,7 +341,7 @@ public CommentResponse pin(String key, PinComment pinCommentForm, Person person) public AggregateCommentResponse aggregate(String commentKey, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.READ_COMMENT_AGGREGATE, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "comment_view_not_permitted")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); return commentAggregateRepository.findByComment_Path(commentKey) .map(commentAggregate -> conversionService.convert(commentAggregate, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java index 0a0c6f04..d40387b4 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -93,7 +93,7 @@ public List show(@PathVariable final String key, rolePermissionService.isPermitted(person.orElse(null), RolePermissionCommunityTypes.READ_COMMUNITY_MODERATORS, () -> { throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_read_moderators"); + "unauthorized"); }); return sublinksCommunityService.getCommunityModerators(key, person.orElse(null)) @@ -123,7 +123,7 @@ public List add(@PathVariable final String key, RolePermissionCommunityTypes.MODERATOR_ADD_MODERATOR)) && !rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.ADMIN_ADD_COMMUNITY_MODERATOR)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_add_moderator"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } final Person newModerator = personRepository.findOneByNameIgnoreCase(personKey) @@ -160,7 +160,7 @@ public List remove(@PathVariable final String key, if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_remove_moderator"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } linkPersonCommunityService.removeLink(person, community, LinkPersonCommunityType.moderator); @@ -188,7 +188,7 @@ public PersonResponse ban(@PathVariable final String key, @PathVariable final St if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_ban_user"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } final Person bannedPerson = sublinksCommunityService.banPerson(key, personKey, person, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index 8381f9b4..fbef7f03 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -47,8 +47,7 @@ public class SublinksCommunityService { public CommunityResponse show(String key, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.READ_COMMUNITY, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_read_community")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final Community community = communityRepository.findCommunityByTitleSlug(key) .orElseThrow( @@ -67,8 +66,7 @@ public CommunityResponse show(String key, Person person) { public List index(IndexCommunity indexCommunityForm, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.READ_COMMUNITY, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_read_communities")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType sortType = indexCommunityForm.sortType(); @@ -137,7 +135,7 @@ public CommunityResponse createCommunity(CreateCommunity createCommunity, Person } if (rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.CREATE_COMMUNITY)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_create_community"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } Community community = Community.builder() @@ -167,8 +165,8 @@ public CommunityResponse createCommunity(CreateCommunity createCommunity, Person * @throws ResponseStatusException If the community is not found, or the person is not authorized * to perform the update. */ - public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommunityForm, - Person person) + public CommunityResponse updateCommunity(String key, final UpdateCommunity updateCommunityForm, + final Person person) { String domain = ActorIdUtils.getActorDomain(key); @@ -185,7 +183,7 @@ public CommunityResponse updateCommunity(String key, UpdateCommunity updateCommu && rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.UPDATE_COMMUNITY)) && !rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.ADMIN_UPDATE_COMMUNITY)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_update_community"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } if (updateCommunityForm.description() != null) { @@ -220,8 +218,7 @@ public List getCommunityModerators(String key, Person perso rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.READ_COMMUNITY_MODERATORS, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_read_community_moderators")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final Community community = communityRepository.findCommunityByTitleSlug(key) .orElseThrow( @@ -265,7 +262,7 @@ public Person banPerson(String key, String personKey, Person person, && rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.MODERATOR_BAN_USER)) && !rolePermissionService.isPermitted( person, RolePermissionCommunityTypes.ADMIN_BAN_USER)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_ban_person"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } if (!communityBanPersonForm.ban()) { @@ -301,7 +298,7 @@ public CommunityResponse remove(String key, RemoveCommunity removeComment, Perso if (!rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.ADMIN_REMOVE_COMMUNITY)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_remove_community"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } community.setRemoved(removeComment.remove() != null ? removeComment.remove() : true); @@ -332,7 +329,7 @@ public CommunityResponse delete(String key, DeleteCommunity deleteCommunityForm, if (!(linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, List.of(LinkPersonCommunityType.owner)) && rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.MODERATOR_REMOVE_COMMUNITY))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_remove_community"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } community.setDeleted( @@ -357,8 +354,7 @@ public CommunityAggregateResponse showAggregate(String communityKey, Person pers rolePermissionService.isPermitted(person, RolePermissionCommunityTypes.READ_COMMUNITY_AGGREGATION, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_read_community_aggregation")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final Community community = communityRepository.findCommunityByTitleSlug(communityKey) .orElseThrow( diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceConfigController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceConfigController.java index 2730e130..881c57b1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceConfigController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceConfigController.java @@ -5,9 +5,6 @@ import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.InstanceConfigResponse; import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.UpdateInstanceConfig; import com.sublinks.sublinksapi.api.sublinks.v1.instance.service.SublinksInstanceService; -import com.sublinks.sublinksapi.instance.repositories.InstanceRepository; -import com.sublinks.sublinksapi.instance.services.InstanceConfigService; -import com.sublinks.sublinksapi.instance.services.InstanceService; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -15,7 +12,6 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.AllArgsConstructor; -import org.springframework.core.convert.ConversionService; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -29,11 +25,7 @@ @Tag(name = "Instance Config", description = "Instance Config API") public class SublinksInstanceConfigController extends AbstractSublinksApiController { - private final InstanceConfigService instanceConfigService; - private final InstanceService instanceService; - private final InstanceRepository instanceRepository; private final SublinksInstanceService sublinksInstanceService; - private final ConversionService conversionService; @Operation(summary = "Get a specific instance config") @GetMapping diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/service/SublinksInstanceService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/service/SublinksInstanceService.java index a90ca183..51e3c560 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/service/SublinksInstanceService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/service/SublinksInstanceService.java @@ -69,7 +69,7 @@ public InstanceConfigResponse showConfig(final String key, final Person person) rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_READ_ANNOUNCEMENT, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_read_instance_config")); + "unauthorized")); return conversionService.convert(instanceConfigRepository.findByInstance_Domain(key) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "instance_not_found")), @@ -82,7 +82,7 @@ public InstanceConfigResponse updateConfig(final String key, rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_UPDATE_SETTINGS, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_update_instance_config")); + "unauthorized")); final InstanceConfig config = instanceConfigRepository.findByInstance_Domain(key) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "instance_not_found")); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java index 99e4b5ea..432b0352 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java @@ -48,7 +48,7 @@ public PersonAggregateResponse aggregate(@PathVariable final String key, rolePermissionService.isPermitted(person.orElse(null), RolePermissionPersonTypes.READ_PERSON_AGGREGATION, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_read_community_aggregation")); + "unauthorized")); return conversionService.convert(sublinksPersonService.showAggregate(key, person.orElse(null)), PersonAggregateResponse.class); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonAggregationMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonAggregationMapper.java index 510b2918..2660d858 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonAggregationMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonAggregationMapper.java @@ -1,7 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.mappers; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonAggregateResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksRoleMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksPersonRoleMapper; import com.sublinks.sublinksapi.api.sublinks.v1.utils.PersonKeyUtils; import com.sublinks.sublinksapi.person.entities.Person; import com.sublinks.sublinksapi.person.entities.PersonAggregate; @@ -14,7 +14,7 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksRoleMapper.class}) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksPersonRoleMapper.class}) @NoArgsConstructor public abstract class SublinksPersonAggregationMapper implements Converter { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java index 5e1737bb..8eeec611 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java @@ -1,12 +1,11 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.mappers; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksRoleMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksPersonRoleMapper; import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; import com.sublinks.sublinksapi.api.sublinks.v1.utils.PersonKeyUtils; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.person.entities.Person; -import com.sublinks.sublinksapi.utils.UrlUtil; import java.text.SimpleDateFormat; import org.mapstruct.Mapper; import org.mapstruct.Mapping; @@ -16,7 +15,7 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksRoleMapper.class}) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksPersonRoleMapper.class}) public abstract class SublinksPersonMapper implements Converter { @Autowired diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMetaDataMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMetaDataMapper.java index dfeca37b..1722f85e 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMetaDataMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMetaDataMapper.java @@ -1,7 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.mappers; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonSessionData; -import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksRoleMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksPersonRoleMapper; import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; import com.sublinks.sublinksapi.person.entities.PersonMetaData; import org.mapstruct.Mapper; @@ -10,7 +10,7 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksRoleMapper.class}) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksPersonRoleMapper.class}) public abstract class SublinksPersonMetaDataMapper implements Converter { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java index 90fcea05..7ccc9d4d 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.models; -import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.Role; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.PersonRoleResponse; import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import lombok.Builder; @@ -21,7 +21,7 @@ public record PersonResponse( requiredMode = RequiredMode.NOT_REQUIRED) String banExpiresAt, Boolean isDeleted, Boolean isBotAccount, - Role role, + PersonRoleResponse personRoleResponse, String createdAt, String updatedAt) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index 32a93ffd..47f16bb8 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -123,7 +123,7 @@ public PersonIdentity getPersonIdentifiersFromKey(String key) { public List index(final IndexPerson indexPerson, final Person person) { rolePermissionService.isPermitted(person, RolePermissionPersonTypes.READ_USERS, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_read_persons")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); if (indexPerson.search() == null) { if (indexPerson.listingType() == SublinksListingType.Local) { @@ -170,7 +170,7 @@ public List index(final IndexPerson indexPerson, final Person pe public PersonResponse show(final String key, final Person person) { rolePermissionService.isPermitted(person, RolePermissionPersonTypes.READ_USER, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_read_person")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final PersonIdentity ids = getPersonIdentifiersFromKey(key); @@ -299,10 +299,10 @@ public LoginResponse login(final LoginPerson loginPersonForm, final String ip, .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); rolePermissionService.isPermitted(person, RolePermissionPersonTypes.USER_LOGIN, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_login")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); if (!rolePermissionService.isPermitted(person, RolePermissionPersonTypes.USER_LOGIN)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_login"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } if (person.isDeleted()) { @@ -357,7 +357,7 @@ public LoginResponse login(final LoginPerson loginPersonForm, final String ip, public PersonResponse updatePerson(Person person, UpdatePerson updatePersonForm) { rolePermissionService.isPermitted(person, RolePermissionPersonTypes.UPDATE_USER_SETTINGS, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_update_person")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); if (person.isDeleted()) { throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "person_deleted"); @@ -413,7 +413,7 @@ public List indexBannedPersons(final IndexBannedPerson indexBannedPerson { rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_BAN_READ, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_ban_person")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); Optional bannedRole = roleService.getBannedRole(); if (bannedRole.isEmpty()) { @@ -440,7 +440,7 @@ public PersonResponse banPerson(final BanPerson banPersonForm, final Person pers PersonIdentity ids = getPersonIdentifiersFromKey(banPersonForm.key()); rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_BAN_USER, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_ban_person")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); Person bannedPerson = personRepository.findOneByNameAndInstance_Domain(ids.name(), ids.domain()) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); @@ -471,7 +471,7 @@ public PersonAggregateResponse showAggregate(final String key, final Person pers rolePermissionService.isPermitted(person, RolePermissionPersonTypes.READ_PERSON_AGGREGATION, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_read_community_aggregation")); + "unauthorized")); final PersonIdentity personIdentity = getPersonIdentifiersFromKey(key); @@ -503,7 +503,7 @@ public PersonResponse deletePerson(final String key, final DeletePerson deletePe { rolePermissionService.isPermitted(person, RolePermissionPersonTypes.DELETE_USER, () -> { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_authorized_to_delete_person"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); }); final PersonIdentity ids = getPersonIdentifiersFromKey(key); @@ -554,7 +554,7 @@ public PersonSessionDataResponse getMetaData(final String targetKey, final Perso && !rolePermissionService.isPermitted(person, RolePermissionPersonTypes.READ_USER_METADATAS)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_read_person_metadata"); + "unauthorized"); } final List personMetaData = userDataRepository.findAllByPerson(target); @@ -592,7 +592,7 @@ public PersonSessionDataResponse getOneMetaData(final String targetsessionKey, .equals(person)) && !rolePermissionService.isPermitted(person, RolePermissionPersonTypes.READ_USER_METADATAS)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_read_person_metadatas"); + "unauthorized"); } return PersonSessionDataResponse.builder() @@ -626,7 +626,7 @@ public void invalidateUserData(String targetUserData, final Person person) { .equals(person)) && !rolePermissionService.isPermitted(person, RolePermissionPersonTypes.INVALIDATE_USER_METADATA)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_invalidate_person_metadata"); + "unauthorized"); } userDataService.invalidate(personMetaData); @@ -655,7 +655,7 @@ public void invalidateAllUserData(final String targetPersonKey, final Person per && !rolePermissionService.isPermitted(person, RolePermissionPersonTypes.INVALIDATE_USER_METADATA)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_invalidate_person_metadata"); + "unauthorized"); } userDataService.invalidateAllUserData(target); @@ -680,7 +680,7 @@ public void deleteUserData(final String targetUserDataKey, final Person person) .equals(person) && !rolePermissionService.isPermitted(person, RolePermissionPersonTypes.DELETE_USER_METADATA)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_delete_person_metadata"); + "unauthorized"); } userDataRepository.delete(personMetaData); @@ -707,7 +707,7 @@ public void deleteAllUserData(final String targetPersonKey, final Person person) && !rolePermissionService.isPermitted(person, RolePermissionPersonTypes.DELETE_USER_METADATA)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_authorized_to_invalidate_person_metadata"); + "unauthorized"); } userDataRepository.deleteAll(userDataRepository.findAllByPerson(target)); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index 4645a2e7..ed76506b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -179,7 +179,7 @@ public PostResponse create(final CreatePost createPostForm, final Person person) if (createPostForm.featuredLocal()) { if (!rolePermissionService.isPermitted(person, RolePermissionPostTypes.ADMIN_PIN_POST)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "feature_post_permission_denied"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } } if (createPostForm.featuredCommunity()) { @@ -187,7 +187,7 @@ public PostResponse create(final CreatePost createPostForm, final Person person) && linkPersonCommunityService.hasAnyLink(person, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) && !rolePermissionService.isPermitted(person, RolePermissionPostTypes.ADMIN_PIN_POST)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "feature_post_permission_denied"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } } @@ -306,7 +306,7 @@ public PostResponse update(final String postKey, final UpdatePost updatePostForm if (updatePostForm.featuredLocal() != null) { if (!rolePermissionService.isPermitted(person, RolePermissionPostTypes.ADMIN_PIN_POST)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "feature_post_permission_denied"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } post.setFeatured(updatePostForm.featuredLocal()); @@ -316,7 +316,7 @@ public PostResponse update(final String postKey, final UpdatePost updatePostForm && linkPersonCommunityService.hasAnyLink(person, post.getCommunity(), List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) && !rolePermissionService.isPermitted(person, RolePermissionPostTypes.ADMIN_PIN_POST)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "feature_post_permission_denied"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } post.setFeatured(updatePostForm.featuredCommunity()); @@ -406,7 +406,7 @@ public PostResponse remove(final String postKey, final RemovePost removePostForm rolePermissionService.isPermitted(person, RolePermissionPostTypes.MODERATOR_REMOVE_POST) && linkPersonCommunityService.hasAnyLink(person, post.getCommunity(), List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "remove_post_permission_denied"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } post.setRemovedState(removePostForm.remove() ? RemovedState.REMOVED : RemovedState.NOT_REMOVED); @@ -438,7 +438,7 @@ public PostResponse delete(final String postKey, final DeletePost deletePostForm && postService.getPostCreator(post) .getId() .equals(person.getId()) && !post.isRemoved())) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "delete_post_permission_denied"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } post.setDeleted(deletePostForm.remove()); @@ -461,8 +461,7 @@ public PostResponse delete(final String postKey, final DeletePost deletePostForm public AggregatePostResponse aggregate(String postKey, Person person) { rolePermissionService.isPermitted(person, RolePermissionPostTypes.READ_POST_AGGREGATE, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "aggregate_post_permission_denied")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); return postAggregateRepository.findByPost_TitleSlug(postKey) .map(postAggregate -> conversionService.convert(postAggregate, AggregatePostResponse.class)) @@ -475,7 +474,7 @@ public PostResponse favorite(final String postKey, final FavoritePost favoritePo { rolePermissionService.isPermitted(person, RolePermissionPostTypes.FAVORITE_POST, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "favorite_post_permission_denied")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final Post post = postRepository.findByTitleSlug(postKey) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found")); @@ -509,7 +508,7 @@ public PostResponse pin(final String postKey, final PinPost pinPostForm, final P .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found")); if (!rolePermissionService.isPermitted(person, RolePermissionPostTypes.ADMIN_PIN_POST)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "pin_post_permission_denied"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } post.setFeatured(pinPostForm.pin() != null ? pinPostForm.pin() : !post.isFeatured()); @@ -539,8 +538,7 @@ public PostResponse pinCommunity(final String postKey, final PinPost pinPostForm rolePermissionService.isPermitted(person, RolePermissionPostTypes.MODERATOR_PIN_POST) && !linkPersonCommunityService.hasAnyLink(person, post.getCommunity(), List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner)))) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "instance_pin_post_permission_denied"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } post.setFeaturedInCommunity(pinPostForm.pin()); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java index 20983a28..cbe7e4d1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java @@ -15,6 +15,7 @@ import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; import java.util.Optional; +import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -22,6 +23,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@AllArgsConstructor @RestController @RequestMapping("api/v1/privatemessage") @Tag(name = "Privatemessage", description = "Privatemessage API") @@ -29,14 +31,6 @@ public class SublinksPrivatemessageController extends AbstractSublinksApiControl private final SublinksPrivateMessageService sublinksPrivateMessageService; - public SublinksPrivatemessageController( - SublinksPrivateMessageService sublinksPrivateMessageService) - { - - super(); - this.sublinksPrivateMessageService = sublinksPrivateMessageService; - } - @Operation(summary = "Get a list of privatemessages") @GetMapping @ApiResponses(value = { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/services/SublinksPrivateMessageService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/services/SublinksPrivateMessageService.java index 31623f5d..0078a0f5 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/services/SublinksPrivateMessageService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/services/SublinksPrivateMessageService.java @@ -50,7 +50,7 @@ public List index(final IndexPrivateMessages indexPrivat aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.READ_PRIVATE_MESSAGES) .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_allowed_to_read_private_messages")); + "unauthorized")); return privateMessageRepository.allPrivateMessagesBySearchCriteria( PrivateMessageSearchCriteria.builder() @@ -82,7 +82,7 @@ public PrivateMessageResponse show(final String id, final Person person) { aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.READ_PRIVATE_MESSAGES) .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_allowed_to_read_private_messages")); + "unauthorized")); final PrivateMessage privateMessage = privateMessageRepository.findById(Long.parseLong(id)) .orElseThrow(() -> new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, @@ -113,7 +113,7 @@ public PrivateMessageResponse create(final CreatePrivateMessage createPrivateMes aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.CREATE_PRIVATE_MESSAGE) .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_allowed_to_send_private_messages")); + "unauthorized")); final PrivateMessage privateMessage = PrivateMessage.builder() .recipient(person) @@ -144,7 +144,7 @@ public PrivateMessageResponse update(final UpdatePrivateMessage updatePrivateMes aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.UPDATE_PRIVATE_MESSAGE) .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_allowed_to_update_private_messages")); + "unauthorized")); final PrivateMessage privateMessage = privateMessageRepository.findById( Long.parseLong(updatePrivateMessageForm.privateMessageKey())) @@ -181,7 +181,7 @@ public PrivateMessageResponse delete(final String id, aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.DELETE_PRIVATE_MESSAGE) .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_allowed_to_delete_private_messages")); + "unauthorized")); final PrivateMessage privateMessage = privateMessageRepository.findById(Long.parseLong(id)) .orElseThrow( @@ -208,7 +208,7 @@ public void markAllAsRead(final Person person) { aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.MARK_PRIVATE_MESSAGE_AS_READ) .orElseThrow( - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_allowed_to_mark_as_read")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); List privateMessages = privateMessageRepository.findByRecipientAndReadIsFalse( person); @@ -235,7 +235,7 @@ public PrivateMessageResponse markAsRead(final String id, aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.MARK_PRIVATE_MESSAGE_AS_READ) .orElseThrow( - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "not_allowed_to_mark_as_read")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final PrivateMessage privateMessage = privateMessageRepository.findById(Long.parseLong(id)) .orElseThrow( @@ -243,7 +243,7 @@ public PrivateMessageResponse markAsRead(final String id, if (!privateMessage.getRecipient() .equals(person)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, "not_allowed_to_mark_as_read"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } privateMessage.setRead(markAsReadPrivateMessageForm.read()); @@ -266,7 +266,7 @@ public PrivateMessageResponse purgePrivateMessage(final Person person, final Str aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.PURGE_PRIVATE_MESSAGE) .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_allowed_to_delete_private_messages")); + "unauthorized")); final PrivateMessage privateMessage = privateMessageRepository.findById(Long.parseLong(id)) .orElseThrow( @@ -297,7 +297,7 @@ public List purgePrivateMessages(List ids, final aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.PURGE_PRIVATE_MESSAGES) .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_allowed_to_delete_private_messages")); + "unauthorized")); return privateMessageRepository.findAllById(ids.stream() .map(Long::parseLong) @@ -321,7 +321,7 @@ public List purgeAllPrivateMessages(final Person person) aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.PURGE_PRIVATE_MESSAGE) .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "not_allowed_to_delete_private_messages")); + "unauthorized")); return privateMessageService.deleteAllPrivateMessagesByPerson(person, true) .stream() diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java index 5a785333..c23e4ad6 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java @@ -1,10 +1,12 @@ package com.sublinks.sublinksapi.api.sublinks.v1.roles.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.PersonRoleResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import java.util.List; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -20,45 +22,40 @@ public class SublinksRolesController extends AbstractSublinksApiController { @Operation(summary = "Get a list of roles") @GetMapping @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void index() { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public List index() { // TODO: implement } @Operation(summary = "Get a specific role") - @GetMapping("/{id}") + @GetMapping("/{key}") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void show(@PathVariable String id) { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PersonRoleResponse show(@PathVariable String key) { // TODO: implement } - @Operation(summary = "Create a new post") + @Operation(summary = "Create a new Role") @PostMapping @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void create() { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PersonRoleResponse create() { // TODO: implement } - @Operation(summary = "Update an post") - @PostMapping("/{id}") + @Operation(summary = "Update an Role") + @PostMapping("/{key}") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void update(@PathVariable String id) { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PersonRoleResponse update(@PathVariable String key) { // TODO: implement } - @Operation(summary = "Delete an post") - @DeleteMapping("/{id}") + @Operation(summary = "Delete an Role") + @DeleteMapping("/{key}") @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true) - }) - public void delete(@PathVariable String id) { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public void delete(@PathVariable String key) { // TODO: implement } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPersonMapper.java similarity index 84% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPersonMapper.java index 9851485e..5e220963 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPersonMapper.java @@ -1,6 +1,8 @@ package com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers; import com.sublinks.sublinksapi.api.lemmy.v3.utils.DateUtils; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.PersonRoleResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.RoleResponse; import com.sublinks.sublinksapi.authorization.entities.Role; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import java.util.Date; @@ -13,8 +15,8 @@ @Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {RolePermissionService.class}) -public abstract class SublinksRoleMapper implements - Converter { +public abstract class SublinksPersonMapper implements + Converter { @Override @Mapping(target = "key", source = "role.name") @@ -31,7 +33,7 @@ public abstract class SublinksRoleMapper implements @Mapping(target = "updatedAt", source = "role.updatedAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) - public abstract com.sublinks.sublinksapi.api.sublinks.v1.roles.models.Role convert( + public abstract RoleResponse convert( @Nullable Role role); @Named("is_expired") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPersonRoleMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPersonRoleMapper.java new file mode 100644 index 00000000..65d8737f --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPersonRoleMapper.java @@ -0,0 +1,56 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers; + +import com.sublinks.sublinksapi.api.lemmy.v3.utils.DateUtils; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.RoleResponse; +import com.sublinks.sublinksapi.authorization.entities.Role; +import com.sublinks.sublinksapi.authorization.entities.RolePermissions; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; +import java.util.Date; +import java.util.List; +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingConstants; +import org.mapstruct.Named; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, + uses = {RolePermissionService.class}) +public abstract class SublinksPersonRoleMapper implements Converter { + + @Override + @Mapping(target = "key", source = "role.name") + @Mapping(target = "name", source = "role.name") + @Mapping(target = "description", source = "role.description") + @Mapping(target = "permissions", source = "role", qualifiedByName = "permissions") + @Mapping(target = "isActive", source = "role.active") + @Mapping(target = "isExpired", source = "role", qualifiedByName = "is_expired") + @Mapping(target = "expiresAt", + source = "role.expiresAt", + dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "createdAt", + source = "role.createdAt", + dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + @Mapping(target = "updatedAt", + source = "role.updatedAt", + dateFormat = DateUtils.FRONT_END_DATE_FORMAT) + public abstract RoleResponse convert(@Nullable Role role); + + @Named("is_expired") + Boolean mapIsExpired(Role role) { + + if (role.getExpiresAt() == null) { + return false; + } + return new Date().after(role.getExpiresAt()); + } + + @Named("permissions") + List mapPermissions(Role role) { + + return role.getRolePermissions() + .stream() + .map(RolePermissions::getPermission) + .toList(); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/CreateRole.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/CreateRole.java new file mode 100644 index 00000000..3233d255 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/CreateRole.java @@ -0,0 +1,11 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.roles.models; + +import java.util.List; + +public record CreateRole( + String name, + String description, + Boolean active, + List permissions) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/IndexRole.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/IndexRole.java new file mode 100644 index 00000000..a7df5eb0 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/IndexRole.java @@ -0,0 +1,11 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.roles.models; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortOrder; + +public record IndexRole( + String search, + Integer page, + Integer limit, + SortOrder sort) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/PersonRoleResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/PersonRoleResponse.java new file mode 100644 index 00000000..35bb5f0f --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/PersonRoleResponse.java @@ -0,0 +1,22 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.roles.models; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import lombok.Builder; + +@Builder +public record PersonRoleResponse( + String key, + String name, + String description, + Boolean isActive, + Boolean isExpired, + @Schema( + requiredMode = RequiredMode.NOT_REQUIRED, + description = "The date and time the role expires at" + ) + String expiresAt, + String createdAt, + String updatedAt) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/Role.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/Role.java deleted file mode 100644 index 5d3db75c..00000000 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/Role.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.roles.models; - -import lombok.Builder; - -@Builder -public record Role( - String key, - String name, - String description, - Boolean isActive, - Boolean isExpired, - String expiresAt, - String createdAt, - String updatedAt) { - -} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/RoleResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/RoleResponse.java new file mode 100644 index 00000000..5bbd289b --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/RoleResponse.java @@ -0,0 +1,22 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.roles.models; + +import com.sublinks.sublinksapi.authorization.enums.RolePermissionInterface; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import java.util.Set; +import lombok.Builder; + +@Builder +public record RoleResponse( + String key, + String name, + String description, + Set permissions, + Boolean isActive, + Boolean isExpired, + @Schema(requiredMode = RequiredMode.NOT_REQUIRED, + description = "The date and time the role expires at") String expiresAt, + String createdAt, + String updatedAt) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/UpdateRole.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/UpdateRole.java new file mode 100644 index 00000000..16c014c3 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/UpdateRole.java @@ -0,0 +1,11 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.roles.models; + +import java.util.List; + +public record UpdateRole( + String name, + String description, + Boolean active, + List permissions) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java new file mode 100644 index 00000000..ec47621e --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java @@ -0,0 +1,113 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.roles.services; + +import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortOrder; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.CreateRole; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.IndexRole; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.RoleResponse; +import com.sublinks.sublinksapi.authorization.entities.Role; +import com.sublinks.sublinksapi.authorization.entities.RolePermissions; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionRoleTypes; +import com.sublinks.sublinksapi.authorization.repositories.RoleRepository; +import com.sublinks.sublinksapi.authorization.services.AclService; +import com.sublinks.sublinksapi.authorization.services.RoleService; +import com.sublinks.sublinksapi.person.entities.Person; +import java.util.List; +import java.util.stream.Collectors; +import lombok.AllArgsConstructor; +import org.springframework.core.convert.ConversionService; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; + +@AllArgsConstructor +@Service +public class SublinksRoleService { + + private final ConversionService conversionService; + private final AclService aclService; + private final RoleRepository roleRepository; + private final RoleService roleService; + + public List indexRole(final IndexRole indexRoleForm, final Person person) + { + + aclService.canPerson(person) + .performTheAction(RolePermissionRoleTypes.ROLE_READ) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + + return roleRepository.findAllByNameIsLikeIgnoreCase(indexRoleForm.search(), + PageRequest.of(indexRoleForm.page(), indexRoleForm.limit(), indexRoleForm.sort() + .equals(SortOrder.Asc) ? Sort.by("name") + .ascending() : Sort.by("name") + .descending())) + .stream() + .map(role -> conversionService.convert(role, RoleResponse.class)) + .toList(); + } + + public RoleResponse show(final String key, final Person person) + { + + aclService.canPerson(person) + .performTheAction(RolePermissionRoleTypes.ROLE_READ) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + + return roleRepository.findFirstByName(key) + .map(role -> conversionService.convert(role, RoleResponse.class)) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "role not found")); + } + + public RoleResponse create(final CreateRole createRoleForm, final Person person) + { + + aclService.canPerson(person) + .performTheAction(RolePermissionRoleTypes.ROLE_CREATE) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + + final Role role = Role.builder() + .name(createRoleForm.name()) + .description(createRoleForm.description()) + .isActive(createRoleForm.active()) + .rolePermissions(createRoleForm.permissions() + .stream() + .map(permission -> RolePermissions.builder() + .permission(permission) + .build()) + .collect(Collectors.toSet())) + .build(); + + return conversionService.convert(roleService.createRole(role), RoleResponse.class); + } + + public RoleResponse update(final String key, final UpdateRole createRoleForm, final Person person) + { + + aclService.canPerson(person) + .performTheAction(RolePermissionRoleTypes.ROLE_UPDATE) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + + final Role role = roleRepository.findFirstByName(key) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "role_not_found")); + + role.setName(createRoleForm.name()); + role.setDescription(createRoleForm.description()); + role.setActive(createRoleForm.active()); + + return conversionService.convert(roleService.updateRole(role), RoleResponse.class); + } + + public void delete(final String key, final Person person) + { + + aclService.canPerson(person) + .performTheAction(RolePermissionRoleTypes.ROLE_DELETE) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + + final Role role = roleRepository.findFirstByName(key) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "role_not_found")); + + roleService.deleteRole(role); + } +} \ No newline at end of file diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java index 37e9138f..783e551f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java @@ -49,7 +49,7 @@ public SearchResponse list(final Search searchForm, final Optional perso rolePermissionService.isPermitted(person.orElse(null), RolePermissionInstanceTypes.INSTANCE_SEARCH, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "search_not_permitted")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final String search = searchForm.search(); diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/entities/Role.java b/src/main/java/com/sublinks/sublinksapi/authorization/entities/Role.java index 8fb87ecd..82cafe72 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/entities/Role.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/entities/Role.java @@ -56,7 +56,7 @@ public class Role { @Column(updatable = false, nullable = false, name = "created_at") private Date createdAt; - + @UpdateTimestamp(source = SourceType.DB) @Column(updatable = false, name = "updated_at") private Date updatedAt; diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionRoleTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionRoleTypes.java new file mode 100644 index 00000000..42510d75 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/RolePermissionRoleTypes.java @@ -0,0 +1,27 @@ +package com.sublinks.sublinksapi.authorization.enums; + +public enum RolePermissionRoleTypes implements RolePermissionInterface { + + // Person permissions + ROLE_READ("role", AuthorizeAction.READ), + ROLES_READ("roles", AuthorizeAction.READ), + ROLE_CREATE("role", AuthorizeAction.CREATE), + ROLE_UPDATE("role", AuthorizeAction.UPDATE), + ROLE_DELETE("role", AuthorizeAction.DELETE); + + + public final String Entity; + public final AuthorizeAction Action; + + RolePermissionRoleTypes(String Entity, AuthorizeAction Action) { + + this.Entity = Entity; + this.Action = Action; + } + + @Override + public String toString() { + + return this.Entity + ":" + this.Action; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleCreatedEvent.java b/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleCreatedEvent.java new file mode 100644 index 00000000..13ce988b --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleCreatedEvent.java @@ -0,0 +1,21 @@ +package com.sublinks.sublinksapi.authorization.events; + +import com.sublinks.sublinksapi.authorization.entities.Role; +import com.sublinks.sublinksapi.person.entities.LinkPersonCommunity; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class RoleCreatedEvent extends ApplicationEvent { + + private final Role role; + + public RoleCreatedEvent( + final Object source, + final Role createdRole + ) { + + super(source); + this.role = createdRole; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleCreatedPublisher.java b/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleCreatedPublisher.java new file mode 100644 index 00000000..56903b39 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleCreatedPublisher.java @@ -0,0 +1,23 @@ +package com.sublinks.sublinksapi.authorization.events; + +import com.sublinks.sublinksapi.authorization.entities.Role; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; + +@Component +public class RoleCreatedPublisher { + + private final ApplicationEventPublisher applicationEventPublisher; + + public RoleCreatedPublisher(final ApplicationEventPublisher applicationEventPublisher) + { + + this.applicationEventPublisher = applicationEventPublisher; + } + + public void publish(final Role role) { + + RoleCreatedEvent roleCreatedEvent = new RoleCreatedEvent(this, role); + applicationEventPublisher.publishEvent(roleCreatedEvent); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleDeletedEvent.java b/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleDeletedEvent.java new file mode 100644 index 00000000..96eb0583 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleDeletedEvent.java @@ -0,0 +1,18 @@ +package com.sublinks.sublinksapi.authorization.events; + +import com.sublinks.sublinksapi.authorization.entities.Role; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class RoleDeletedEvent extends ApplicationEvent { + + private final Role role; + + public RoleDeletedEvent(final Object source, final Role updatedRole) + { + + super(source); + this.role = updatedRole; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleDeletedPublisher.java b/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleDeletedPublisher.java new file mode 100644 index 00000000..271878a6 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleDeletedPublisher.java @@ -0,0 +1,23 @@ +package com.sublinks.sublinksapi.authorization.events; + +import com.sublinks.sublinksapi.authorization.entities.Role; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; + +@Component +public class RoleDeletedPublisher { + + private final ApplicationEventPublisher applicationEventPublisher; + + public RoleDeletedPublisher(final ApplicationEventPublisher applicationEventPublisher) + { + + this.applicationEventPublisher = applicationEventPublisher; + } + + public void publish(final Role role) { + + RoleDeletedEvent roleDeletedEvent = new RoleDeletedEvent(this, role); + applicationEventPublisher.publishEvent(roleDeletedEvent); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleUpdatedEvent.java b/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleUpdatedEvent.java new file mode 100644 index 00000000..8e9ef3ac --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleUpdatedEvent.java @@ -0,0 +1,18 @@ +package com.sublinks.sublinksapi.authorization.events; + +import com.sublinks.sublinksapi.authorization.entities.Role; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class RoleUpdatedEvent extends ApplicationEvent { + + private final Role role; + + public RoleUpdatedEvent(final Object source, final Role updatedRole) + { + + super(source); + this.role = updatedRole; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleUpdatedPublisher.java b/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleUpdatedPublisher.java new file mode 100644 index 00000000..61d572f6 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/authorization/events/RoleUpdatedPublisher.java @@ -0,0 +1,23 @@ +package com.sublinks.sublinksapi.authorization.events; + +import com.sublinks.sublinksapi.authorization.entities.Role; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Component; + +@Component +public class RoleUpdatedPublisher { + + private final ApplicationEventPublisher applicationEventPublisher; + + public RoleUpdatedPublisher(final ApplicationEventPublisher applicationEventPublisher) + { + + this.applicationEventPublisher = applicationEventPublisher; + } + + public void publish(final Role role) { + + RoleUpdatedEvent roleUpdatedEvent = new RoleUpdatedEvent(this, role); + applicationEventPublisher.publishEvent(roleUpdatedEvent); + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/repositories/RoleRepository.java b/src/main/java/com/sublinks/sublinksapi/authorization/repositories/RoleRepository.java index 102d9fba..2890836d 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/repositories/RoleRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/repositories/RoleRepository.java @@ -1,22 +1,14 @@ package com.sublinks.sublinksapi.authorization.repositories; import com.sublinks.sublinksapi.authorization.entities.Role; -import java.util.Collection; +import java.util.List; import java.util.Optional; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; public interface RoleRepository extends JpaRepository, RoleSearchRepository { - Optional> findAllByName(String name); - Optional findFirstByName(String name); - Collection findByIdIn(Collection roleIds); - - Collection findAllByNameIn(Collection roleNames); - - Collection findAllByNameContaining(String roleName); - - Collection findAllByNameContainingIgnoreCase(String roleName); - + List findAllByNameIsLikeIgnoreCase(String name, Pageable pageable); } diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/RoleService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/RoleService.java index 9b7e0b48..290a02ae 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/RoleService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/RoleService.java @@ -1,7 +1,11 @@ package com.sublinks.sublinksapi.authorization.services; import com.sublinks.sublinksapi.authorization.entities.Role; +import com.sublinks.sublinksapi.authorization.entities.RolePermissions; import com.sublinks.sublinksapi.authorization.enums.RoleTypes; +import com.sublinks.sublinksapi.authorization.events.RoleCreatedPublisher; +import com.sublinks.sublinksapi.authorization.events.RoleDeletedPublisher; +import com.sublinks.sublinksapi.authorization.events.RoleUpdatedPublisher; import com.sublinks.sublinksapi.authorization.repositories.RoleRepository; import com.sublinks.sublinksapi.person.entities.Person; import com.sublinks.sublinksapi.person.repositories.PersonRepository; @@ -15,8 +19,13 @@ @RequiredArgsConstructor public class RoleService { - final private RoleRepository roleRepository; + private final RoleRepository roleRepository; private final PersonRepository personRepository; + private final RoleCreatedPublisher roleCreatedEventPublisher; + private final RoleUpdatedPublisher roleUpdatedEventPublisher; + private final RoleDeletedPublisher roleDeletedEventPublisher; + private final RolePermissionService rolePermissionService; + /** * Retrieves the admin role from the role repository. @@ -64,7 +73,8 @@ public Optional getDefaultRegisteredRole() { * @throws X The exception provided by the supplier if the default registered role is not found. */ public Role getDefaultRegisteredRole(Supplier supplier) - throws X { + throws X + { return getDefaultRegisteredRole().orElseThrow(supplier); } @@ -141,4 +151,48 @@ public Set getBannedUsers() { return personRepository.findAllByRole(getBannedRole(() -> new RuntimeException( "Cannot produce list of banned people because the Banned role doesn't exist."))); } + + public Role createRole(Role role) { + + roleRepository.save(role); + roleCreatedEventPublisher.publish(role); + + return role; + } + + public Role updateRole(Role role) { + + roleRepository.save(role); + roleUpdatedEventPublisher.publish(role); + + return role; + } + + public void deleteRole(Role role) { + + roleRepository.delete(role); + roleDeletedEventPublisher.publish(role); + } + + public RolePermissions getOrCreateRolePermission(Role role, String permission) { + + return role.getRolePermissions() + .stream() + .filter(rolePermission -> rolePermission.getPermission() + .equals(permission)) + .findFirst() + .orElseGet(() -> { + + RolePermissions rolePermission = RolePermissions.builder() + .role(role) + .permission(permission) + .build(); + + role.getRolePermissions() + .add(rolePermission); + this.updateRole(role); + + return rolePermission; + }); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/common/enums/SortOrder.java b/src/main/java/com/sublinks/sublinksapi/common/enums/SortOrder.java new file mode 100644 index 00000000..1187b11c --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/common/enums/SortOrder.java @@ -0,0 +1,6 @@ +package com.sublinks.sublinksapi.common.enums; + +public enum SortOrder { + Asc, + Desc, +} 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 524a7255..5b15056f 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 @@ -716,6 +716,8 @@ CREATE TABLE acl_roles updated_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL ); +CREATE UNIQUE INDEX IDX_ACL_ROLES_NAME_EXPIRES_AT ON acl_roles (name); + /** Role permissions table */ From 3a74604e3d842d2a21cfe4b693b9ff484b73de73 Mon Sep 17 00:00:00 2001 From: rooki Date: Sun, 4 Aug 2024 14:14:19 +0200 Subject: [PATCH 078/115] Update role management and ACL services for role expiration Modified SQL scripts to refine unique index for roles table. Updated the role service to handle expiration dates in role creation and updates. Introduced ACL checks in post-related services to enhance authorization, and adjusted controller methods to align with these changes. Signed-off-by: rooki --- .../v1/post/services/SublinksPostService.java | 38 ++++++++++++++----- .../controllers/SublinksRolesController.java | 13 ++++++- .../sublinks/v1/roles/models/CreateRole.java | 22 +++++++++-- .../sublinks/v1/roles/models/UpdateRole.java | 28 ++++++++++++-- .../roles/services/SublinksRoleService.java | 29 ++++++++++++-- ...20231003__Create_initial_entity_tables.sql | 2 +- 6 files changed, 107 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index ed76506b..dc8cd844 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -10,6 +10,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.PinPost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.RemovePost; import com.sublinks.sublinksapi.authorization.enums.RolePermissionPostTypes; +import com.sublinks.sublinksapi.authorization.services.AclService; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.community.entities.Community; import com.sublinks.sublinksapi.community.repositories.CommunityRepository; @@ -40,6 +41,7 @@ import com.sublinks.sublinksapi.utils.SiteMetadataUtil; import com.sublinks.sublinksapi.utils.UrlUtil; import java.util.List; +import java.util.Objects; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; import org.springframework.http.HttpStatus; @@ -66,6 +68,7 @@ public class SublinksPostService { private final PostAggregateRepository postAggregateRepository; private final LinkPersonPostService linkPersonPostService; private final LinkPersonPostRepository linkPersonPostRepository; + private final AclService aclService; /** * Retrieves a list of PostResponse objects based on the provided search criteria. @@ -76,6 +79,10 @@ public class SublinksPostService { */ public List index(final IndexPost indexPostForm, final Person person) { + aclService.canPerson(person) + .performTheAction(RolePermissionPostTypes.READ_POSTS) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + final List communities = indexPostForm.communityKeys() == null ? null : communityRepository.findCommunityByTitleSlugIn(indexPostForm.communityKeys()); @@ -144,6 +151,10 @@ public PostResponse show(final String key, final Person person) { */ public PostResponse create(final CreatePost createPostForm, final Person person) { + aclService.canPerson(person) + .performTheAction(RolePermissionPostTypes.CREATE_POST) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + final Community community = communityRepository.findCommunityByTitleSlug( createPostForm.communityKey()) .orElseThrow( @@ -273,6 +284,12 @@ public PostResponse update(final String postKey, final UpdatePost updatePostForm final Post post = postRepository.findByTitleSlug(postKey) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found")); final Community community = post.getCommunity(); + + aclService.canPerson(person) + .onCommunity(community) + .performTheAction(RolePermissionPostTypes.UPDATE_POST) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + if (updatePostForm.languageKey() != null && !updatePostForm.languageKey() .isEmpty()) { @@ -386,14 +403,14 @@ public PostResponse update(final String postKey, final UpdatePost updatePostForm } /** - * Removes a post. + * Removes a post from the system. * - * @param postKey The key of the post to pin. - * @param removePostForm The RemovePost object containing additional parameters for the removal. - * @param person The Person object representing the user performing the removal. + * @param postKey The key of the post to be removed. + * @param removePostForm The RemovePost object containing the removal information. + * @param person The Person object representing the user removing the post. * @return The PostResponse object for the removed post. - * @throws ResponseStatusException If the post is not found or the user is not permitted to pin - * the post. + * @throws ResponseStatusException If the post is not found, the user is unauthorized, or an error + * occurs during the removal process. */ public PostResponse remove(final String postKey, final RemovePost removePostForm, final Person person) @@ -434,10 +451,11 @@ public PostResponse delete(final String postKey, final DeletePost deletePostForm final Post post = postRepository.findByTitleSlug(postKey) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found")); - if (!(rolePermissionService.isPermitted(person, RolePermissionPostTypes.DELETE_POST) - && postService.getPostCreator(post) - .getId() - .equals(person.getId()) && !post.isRemoved())) { + if (!(aclService.canPerson(person) + .onCommunity(post.getCommunity()) + .performTheAction(RolePermissionPostTypes.DELETE_POST) + .isPermitted() && !Objects.equals(postService.getPostCreator(post) + .getId(), person.getId()) && !post.isRemoved())) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java index c23e4ad6..54b7d54d 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java @@ -1,12 +1,18 @@ package com.sublinks.sublinksapi.api.sublinks.v1.roles.controllers; +import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.IndexRole; import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.PersonRoleResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.services.SublinksRoleService; +import com.sublinks.sublinksapi.authorization.services.AclService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; +import java.util.Optional; +import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; @@ -14,17 +20,20 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; +@AllArgsConstructor @RestController @RequestMapping("api/v1/roles") @Tag(name = "Roles", description = "Roles API") public class SublinksRolesController extends AbstractSublinksApiController { + private final SublinksRoleService sublinksRoleService; + @Operation(summary = "Get a list of roles") @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List index() { - // TODO: implement + public List index(final Optional indexRoleForm, final SublinksJwtPerson sublinksJwtPerson) { + } @Operation(summary = "Get a specific role") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/CreateRole.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/CreateRole.java index 3233d255..df45d01c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/CreateRole.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/CreateRole.java @@ -1,11 +1,25 @@ package com.sublinks.sublinksapi.api.sublinks.v1.roles.models; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import java.util.List; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; public record CreateRole( - String name, - String description, - Boolean active, - List permissions) { + @Schema(description = "The name of the role", example = "admin") String name, + @Schema(description = "The description of the role", + example = "Administrator") String description, + @Schema(description = "The active status of the role", example = "true") Boolean active, + @Schema(description = "The permissions of the role") List permissions, + @Schema(description = "The expiration date of the role", + requiredMode = RequiredMode.NOT_REQUIRED) Long expiresAt) { + public CreateRole { + + if (expiresAt != null && expiresAt < System.currentTimeMillis() / 1000) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "expiration_timestamp_must_be_in_the_future"); + } + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/UpdateRole.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/UpdateRole.java index 16c014c3..a4719915 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/UpdateRole.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/UpdateRole.java @@ -1,11 +1,31 @@ package com.sublinks.sublinksapi.api.sublinks.v1.roles.models; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import java.util.List; +import org.springframework.http.HttpStatus; +import org.springframework.web.server.ResponseStatusException; public record UpdateRole( - String name, - String description, - Boolean active, - List permissions) { + @Schema(description = "The name of the role", + example = "admin", + requiredMode = RequiredMode.NOT_REQUIRED) String name, + @Schema(description = "The description of the role", + example = "Administrator", + requiredMode = RequiredMode.NOT_REQUIRED) String description, + @Schema(description = "The active status of the role", + example = "true", + requiredMode = RequiredMode.NOT_REQUIRED) Boolean active, + @Schema(description = "The permissions of the role", + requiredMode = RequiredMode.NOT_REQUIRED) List permissions, + @Schema(description = "The expiration date of the role", + requiredMode = RequiredMode.NOT_REQUIRED) Long expiresAt) { + public UpdateRole { + + if (expiresAt != null && expiresAt < System.currentTimeMillis() / 1000) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, + "expiration_timestamp_must_be_in_the_future"); + } + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java index ec47621e..253b86bc 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java @@ -4,6 +4,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.CreateRole; import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.IndexRole; import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.RoleResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.UpdateRole; import com.sublinks.sublinksapi.authorization.entities.Role; import com.sublinks.sublinksapi.authorization.entities.RolePermissions; import com.sublinks.sublinksapi.authorization.enums.RolePermissionRoleTypes; @@ -11,6 +12,7 @@ import com.sublinks.sublinksapi.authorization.services.AclService; import com.sublinks.sublinksapi.authorization.services.RoleService; import com.sublinks.sublinksapi.person.entities.Person; +import java.util.Date; import java.util.List; import java.util.stream.Collectors; import lombok.AllArgsConstructor; @@ -76,12 +78,14 @@ public RoleResponse create(final CreateRole createRoleForm, final Person person) .permission(permission) .build()) .collect(Collectors.toSet())) + .expiresAt(createRoleForm.expiresAt() != null ? new Date(createRoleForm.expiresAt() * 1000L) + : null) .build(); return conversionService.convert(roleService.createRole(role), RoleResponse.class); } - public RoleResponse update(final String key, final UpdateRole createRoleForm, final Person person) + public RoleResponse update(final String key, final UpdateRole updateRoleForm, final Person person) { aclService.canPerson(person) @@ -91,9 +95,26 @@ public RoleResponse update(final String key, final UpdateRole createRoleForm, fi final Role role = roleRepository.findFirstByName(key) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "role_not_found")); - role.setName(createRoleForm.name()); - role.setDescription(createRoleForm.description()); - role.setActive(createRoleForm.active()); + if (updateRoleForm.name() != null) { + role.setName(updateRoleForm.name()); + } + if (updateRoleForm.description() != null) { + role.setDescription(updateRoleForm.description()); + } + if (updateRoleForm.active() != null) { + role.setActive(updateRoleForm.active()); + } + if (updateRoleForm.permissions() != null) { + role.setRolePermissions(updateRoleForm.permissions() + .stream() + .map(permission -> RolePermissions.builder() + .permission(permission) + .build()) + .collect(Collectors.toSet())); + } + if (updateRoleForm.expiresAt() != null) { + role.setExpiresAt(new Date(updateRoleForm.expiresAt() * 1000L)); + } return conversionService.convert(roleService.updateRole(role), RoleResponse.class); } 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 5b15056f..69e0644c 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 @@ -716,7 +716,7 @@ CREATE TABLE acl_roles updated_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL ); -CREATE UNIQUE INDEX IDX_ACL_ROLES_NAME_EXPIRES_AT ON acl_roles (name); +CREATE UNIQUE INDEX IDX_ACL_ROLES_NAME ON acl_roles (name); /** Role permissions table From 07c22d864bc9d37a36e22a892acfd5c7d0e490f2 Mon Sep 17 00:00:00 2001 From: rooki Date: Sun, 4 Aug 2024 17:00:36 +0200 Subject: [PATCH 079/115] Refactor RequestResponse imports and enhance RolesController Renamed RequestResponse.java to its appropriate package path to improve code organization. Additionally, updated RolesController to accept RequestBody and manage roles more effectively by adding implementations for person-based CRUD operations on roles. Signed-off-by: rooki --- .../SublinksCommentModerationController.java | 2 +- .../common/{ => models}/RequestResponse.java | 2 +- ...SublinksCommunityModerationController.java | 2 +- .../SublinksPersonModerationController.java | 2 +- .../controllers/SublinksPostController.java | 2 +- .../SublinksPostModerationController.java | 2 +- .../controllers/SublinksRolesController.java | 54 +++++++++++++++---- .../sublinks/v1/roles/models/IndexRole.java | 2 + 8 files changed, 51 insertions(+), 17 deletions(-) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/{ => models}/RequestResponse.java (63%) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java index bcc00177..42747a96 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java @@ -6,7 +6,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.PurgeComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.RemoveComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; -import com.sublinks.sublinksapi.api.sublinks.v1.common.RequestResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.person.entities.Person; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/RequestResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/models/RequestResponse.java similarity index 63% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/RequestResponse.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/models/RequestResponse.java index 8592f4cb..4a46bf05 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/RequestResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/common/models/RequestResponse.java @@ -1,4 +1,4 @@ -package com.sublinks.sublinksapi.api.sublinks.v1.common; +package com.sublinks.sublinksapi.api.sublinks.v1.common.models; import lombok.Builder; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java index d40387b4..7b8475ca 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -1,7 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; -import com.sublinks.sublinksapi.api.sublinks.v1.common.RequestResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.moderation.CommunityBanPerson; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java index dfddaa1c..6d0704ac 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java @@ -1,7 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; -import com.sublinks.sublinksapi.api.sublinks.v1.common.RequestResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.moderation.BanPerson; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java index 2a740243..17753131 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java @@ -1,7 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.post.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; -import com.sublinks.sublinksapi.api.sublinks.v1.common.RequestResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.CreatePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.DeletePost; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java index 47496c0f..febbac15 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java @@ -1,7 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.post.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; -import com.sublinks.sublinksapi.api.sublinks.v1.common.RequestResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.PinPost; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java index 54b7d54d..578b304d 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java @@ -2,14 +2,18 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.CreateRole; import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.IndexRole; -import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.PersonRoleResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.RoleResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.UpdateRole; import com.sublinks.sublinksapi.api.sublinks.v1.roles.services.SublinksRoleService; -import com.sublinks.sublinksapi.authorization.services.AclService; +import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import java.util.List; import java.util.Optional; import lombok.AllArgsConstructor; @@ -17,6 +21,7 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -32,39 +37,66 @@ public class SublinksRolesController extends AbstractSublinksApiController { @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List index(final Optional indexRoleForm, final SublinksJwtPerson sublinksJwtPerson) { + public List index(final Optional indexRoleForm, + final SublinksJwtPerson sublinksJwtPerson) + { + final Optional person = getOptionalPerson(sublinksJwtPerson); + + return sublinksRoleService.indexRole(indexRoleForm.orElse(IndexRole.builder() + .build()), person.orElse(null)); } @Operation(summary = "Get a specific role") @GetMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public PersonRoleResponse show(@PathVariable String key) { - // TODO: implement + public RoleResponse show(@PathVariable String key, final SublinksJwtPerson sublinksJwtPerson) { + + final Optional person = getOptionalPerson(sublinksJwtPerson); + + return sublinksRoleService.show(key, person.orElse(null)); } @Operation(summary = "Create a new Role") @PostMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public PersonRoleResponse create() { - // TODO: implement + public RoleResponse create(@Valid @RequestBody CreateRole createRoleForm, + final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksRoleService.create(createRoleForm, person); } @Operation(summary = "Update an Role") @PostMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public PersonRoleResponse update(@PathVariable String key) { - // TODO: implement + public RoleResponse update(@PathVariable String key, + @Valid @RequestBody UpdateRole updateRoleForm, final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + return sublinksRoleService.update(key, updateRoleForm, person); } @Operation(summary = "Delete an Role") @DeleteMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void delete(@PathVariable String key) { - // TODO: implement + public RequestResponse delete(@PathVariable String key, final SublinksJwtPerson sublinksJwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); + + sublinksRoleService.delete(key, person); + + return RequestResponse.builder() + .success(true) + .build(); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/IndexRole.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/IndexRole.java index a7df5eb0..f26a4016 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/IndexRole.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/IndexRole.java @@ -1,7 +1,9 @@ package com.sublinks.sublinksapi.api.sublinks.v1.roles.models; import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortOrder; +import lombok.Builder; +@Builder public record IndexRole( String search, Integer page, From 341daf8de9981f5aac18e278d68497badccc98b5 Mon Sep 17 00:00:00 2001 From: rooki Date: Tue, 6 Aug 2024 20:24:52 +0200 Subject: [PATCH 080/115] Add inheritance support to roles and enhance role mapping. Added an "inherits_from" column to the acl_roles table with a foreign key reference. Updated role mapping services to handle inherited permissions and refactored related mappers and services to integrate with the updated role structure. Signed-off-by: rooki --- .../CommunityModActionsController.java | 32 +++++----- ...SublinksCommunityModerationController.java | 14 ++--- .../services/SublinksCommunityService.java | 5 +- .../SublinksPersonAggregationMapper.java | 5 +- .../person/mappers/SublinksPersonMapper.java | 4 +- .../mappers/SublinksPersonMetaDataMapper.java | 3 +- .../v1/person/models/PersonResponse.java | 2 +- .../v1/post/services/SublinksPostService.java | 4 +- .../controllers/SublinksRolesController.java | 5 +- .../SublinksPermissionInterfaceMapper.java | 33 ++++++++++ .../mappers/SublinksPersonRoleMapper.java | 18 +----- ...sonMapper.java => SublinksRoleMapper.java} | 39 ++++++++++-- .../v1/roles/models/RoleResponse.java | 4 ++ .../roles/services/SublinksRoleService.java | 29 +++++++-- .../authorization/entities/Acl.java | 2 - .../authorization/entities/Role.java | 12 +++- .../authorization/enums/AllRoleTypes.java | 27 ++++++++ .../services/InitialRoleSetupService.java | 40 +++++++----- .../services/RolePermissionService.java | 62 ++++++++++++++----- .../authorization/services/RoleService.java | 1 - .../services/LinkPersonCommunityService.java | 7 ++- ...20231003__Create_initial_entity_tables.sql | 16 ++--- ...0231006__Create_foreign_key_references.sql | 5 ++ 23 files changed, 259 insertions(+), 110 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPermissionInterfaceMapper.java rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/{SublinksPersonMapper.java => SublinksRoleMapper.java} (53%) create mode 100644 src/main/java/com/sublinks/sublinksapi/authorization/enums/AllRoleTypes.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java index cbf42788..b4bc6f0a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/community/controllers/CommunityModActionsController.java @@ -112,7 +112,7 @@ CommunityResponse hide(@Valid @RequestBody final HideCommunity hideCommunityForm return CommunityResponse.builder() - .community_view(lemmyCommunityService.communityViewFromCommunity(community)) + .community_view(lemmyCommunityService.communityViewFromCommunity(community)) .build(); } @@ -153,7 +153,7 @@ CommunityResponse delete(@Valid final DeleteCommunity deleteCommunityForm, JwtPe return CommunityResponse.builder() - .community_view(lemmyCommunityService.communityViewFromCommunity(community)) + .community_view(lemmyCommunityService.communityViewFromCommunity(community)) .build(); } @@ -200,7 +200,7 @@ CommunityResponse remove(@Valid @RequestBody final RemoveCommunity removeCommuni return CommunityResponse.builder() - .community_view(lemmyCommunityService.communityViewFromCommunity(community)) + .community_view(lemmyCommunityService.communityViewFromCommunity(community)) .build(); } @@ -224,7 +224,7 @@ GetCommunityResponse transfer(@Valid @RequestBody final TransferCommunity transf (long) transferCommunityForm.community_id()) .orElseThrow( () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - if (!linkPersonCommunityService.hasLinkOrAdmin(community, person, + if (!linkPersonCommunityService.hasLinkOrAdmin(person, community, LinkPersonCommunityType.owner)) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } @@ -237,12 +237,12 @@ GetCommunityResponse transfer(@Valid @RequestBody final TransferCommunity transf throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "person_not_moderator"); } - final Person oldOwner = linkPersonCommunityService.getLinksByEntity( - community, List.of(LinkPersonCommunityType.owner)) + final Person oldOwner = linkPersonCommunityService.getLinksByEntity(community, + List.of(LinkPersonCommunityType.owner)) .stream() .findFirst() - .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "owner_not_found")) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "owner_not_found")) .getPerson(); linkPersonCommunityService.createLinkPersonCommunityLink(community, oldOwner, LinkPersonCommunityType.moderator); @@ -267,7 +267,7 @@ GetCommunityResponse transfer(@Valid @RequestBody final TransferCommunity transf return GetCommunityResponse.builder() - .community_view(lemmyCommunityService.communityViewFromCommunity(community)) + .community_view(lemmyCommunityService.communityViewFromCommunity(community)) .build(); } @@ -290,7 +290,7 @@ BanFromCommunityResponse banUser(@Valid @RequestBody final BanFromCommunity banP .orElseThrow( () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - if (!linkPersonCommunityService.hasAnyLinkOrAdmin(community, person, + if (!linkPersonCommunityService.hasAnyLinkOrAdmin(person, community, List.of(LinkPersonCommunityType.moderator, LinkPersonCommunityType.owner))) { throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } @@ -338,7 +338,7 @@ BanFromCommunityResponse banUser(@Valid @RequestBody final BanFromCommunity banP return BanFromCommunityResponse.builder() .banned(banPersonForm.ban()) - .person_view(lemmyPersonService.getPersonView(personToBan)) + .person_view(lemmyPersonService.getPersonView(personToBan)) .build(); } @@ -394,12 +394,12 @@ AddModToCommunityResponse addMod(@Valid @RequestBody AddModToCommunity addModToC List moderatorsView = moderators.stream() - .map(moderator -> CommunityModeratorView.builder() - .moderator(conversionService.convert(moderator, - com.sublinks.sublinksapi.api.lemmy.v3.user.models.Person.class)) - .community(conversionService.convert(community, - com.sublinks.sublinksapi.api.lemmy.v3.community.models.Community.class)) - .build()) + .map(moderator -> CommunityModeratorView.builder() + .moderator(conversionService.convert(moderator, + com.sublinks.sublinksapi.api.lemmy.v3.user.models.Person.class)) + .community(conversionService.convert(community, + com.sublinks.sublinksapi.api.lemmy.v3.community.models.Community.class)) + .build()) .toList(); // Create Moderation Log diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java index 7b8475ca..541b6b0c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -1,8 +1,8 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; -import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.CommunityResponse; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.moderation.CommunityBanPerson; import com.sublinks.sublinksapi.api.sublinks.v1.community.models.moderation.CommunityModeratorResponse; @@ -92,8 +92,7 @@ public List show(@PathVariable final String key, rolePermissionService.isPermitted(person.orElse(null), RolePermissionCommunityTypes.READ_COMMUNITY_MODERATORS, () -> { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); }); return sublinksCommunityService.getCommunityModerators(key, person.orElse(null)) @@ -134,7 +133,8 @@ public List add(@PathVariable final String key, throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "person_already_moderator"); } - linkPersonCommunityService.addLink(newModerator, community, LinkPersonCommunityType.moderator); + linkPersonCommunityService.createLinkPersonCommunityLink(community, newModerator, + LinkPersonCommunityType.moderator); return sublinksCommunityService.getCommunityModerators(key, person) .stream() @@ -163,7 +163,7 @@ public List remove(@PathVariable final String key, throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } - linkPersonCommunityService.removeLink(person, community, LinkPersonCommunityType.moderator); + linkPersonCommunityService.deleteLink(community, person, LinkPersonCommunityType.moderator); return sublinksCommunityService.getCommunityModerators(key, person) .stream() @@ -208,8 +208,8 @@ public List banned(@PathVariable final String key) { .orElseThrow( () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - return linkPersonCommunityService.getPersonsFromCommunityAndListTypes(community, - List.of(LinkPersonCommunityType.banned)) + return linkPersonCommunityService.getLinkPersonCommunitiesByCommunityAndPersonAndLinkTypeIsIn( + community, List.of(LinkPersonCommunityType.banned)) .stream() .map(person -> conversionService.convert(person, PersonResponse.class)) .toList(); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index fbef7f03..6e22ed2a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -266,13 +266,14 @@ public Person banPerson(String key, String personKey, Person person, } if (!communityBanPersonForm.ban()) { - linkPersonCommunityService.removeLink(personToBan, community, LinkPersonCommunityType.banned); + linkPersonCommunityService.deleteLink(community, personToBan, LinkPersonCommunityType.banned); } else { linkPersonCommunityService.removeAnyLink(personToBan, community, List.of(LinkPersonCommunityType.owner, LinkPersonCommunityType.moderator, LinkPersonCommunityType.follower, LinkPersonCommunityType.pending_follow)); - linkPersonCommunityService.addLink(personToBan, community, LinkPersonCommunityType.banned); + linkPersonCommunityService.createLinkPersonCommunityLink(community, personToBan, + LinkPersonCommunityType.banned); } // @todo: Modlog diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonAggregationMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonAggregationMapper.java index 2660d858..52091949 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonAggregationMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonAggregationMapper.java @@ -1,7 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.mappers; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonAggregateResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksPersonRoleMapper; import com.sublinks.sublinksapi.api.sublinks.v1.utils.PersonKeyUtils; import com.sublinks.sublinksapi.person.entities.Person; import com.sublinks.sublinksapi.person.entities.PersonAggregate; @@ -14,7 +13,7 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksPersonRoleMapper.class}) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) @NoArgsConstructor public abstract class SublinksPersonAggregationMapper implements Converter { @@ -34,6 +33,6 @@ public abstract class SublinksPersonAggregationMapper implements String mapPersonKey(Person person) { return personKeyUtils.getPersonKey(person); - } + } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java index 8eeec611..981835f6 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java @@ -1,7 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.mappers; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksPersonRoleMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksRoleMapper; import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; import com.sublinks.sublinksapi.api.sublinks.v1.utils.PersonKeyUtils; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; @@ -15,7 +15,7 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksPersonRoleMapper.class}) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksRoleMapper.class}) public abstract class SublinksPersonMapper implements Converter { @Autowired diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMetaDataMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMetaDataMapper.java index 1722f85e..07d5ce7c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMetaDataMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMetaDataMapper.java @@ -1,7 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.mappers; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonSessionData; -import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksPersonRoleMapper; import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; import com.sublinks.sublinksapi.person.entities.PersonMetaData; import org.mapstruct.Mapper; @@ -10,7 +9,7 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksPersonRoleMapper.class}) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) public abstract class SublinksPersonMetaDataMapper implements Converter { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java index 7ccc9d4d..79bced04 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/PersonResponse.java @@ -15,13 +15,13 @@ public record PersonResponse( String bio, String matrixUserId, String actorId, + PersonRoleResponse role, Boolean isLocal, Boolean isBanned, @Schema(description = "The date and time the users ban expires at", requiredMode = RequiredMode.NOT_REQUIRED) String banExpiresAt, Boolean isDeleted, Boolean isBotAccount, - PersonRoleResponse personRoleResponse, String createdAt, String updatedAt) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index dc8cd844..0ca9ad4d 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -501,10 +501,10 @@ public PostResponse favorite(final String postKey, final FavoritePost favoritePo if (linkPersonPostRepository.getLinkPersonPostByPostAndPersonAndLinkType(post, person, LinkPersonPostType.follower) .isEmpty()) { - linkPersonPostService.createLink(person, post, LinkPersonPostType.follower); + linkPersonPostService.createPostLink(post, person, LinkPersonPostType.follower); } } else { - linkPersonPostService.removeLink(person, post, LinkPersonPostType.follower); + linkPersonPostService.deleteLink(post, person, LinkPersonPostType.follower); } return conversionService.convert(post, PostResponse.class); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java index 578b304d..bc0caff0 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java @@ -37,14 +37,13 @@ public class SublinksRolesController extends AbstractSublinksApiController { @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List index(final Optional indexRoleForm, + public List index(@Valid final IndexRole indexRoleForm, final SublinksJwtPerson sublinksJwtPerson) { final Optional person = getOptionalPerson(sublinksJwtPerson); - return sublinksRoleService.indexRole(indexRoleForm.orElse(IndexRole.builder() - .build()), person.orElse(null)); + return sublinksRoleService.indexRole(indexRoleForm, person.orElse(null)); } @Operation(summary = "Get a specific role") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPermissionInterfaceMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPermissionInterfaceMapper.java new file mode 100644 index 00000000..2c361002 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPermissionInterfaceMapper.java @@ -0,0 +1,33 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers; + +import com.sublinks.sublinksapi.authorization.enums.AllRoleTypes; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionInterface; +import com.sublinks.sublinksapi.authorization.services.RolePermissionService; +import java.util.logging.Logger; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants; +import org.springframework.core.convert.converter.Converter; +import org.springframework.lang.Nullable; + + +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, + uses = {RolePermissionService.class}) +public class SublinksPermissionInterfaceMapper implements + Converter { + + private final Logger logger = Logger.getLogger(SublinksPermissionInterfaceMapper.class.getName()); + + @Override + public RolePermissionInterface convert(@Nullable String permission) + { + + for (RolePermissionInterface rolePermissionInterface : AllRoleTypes.ALL_ROLE_TYPES) { + if (rolePermissionInterface.toString() + .equals(permission)) { + return rolePermissionInterface; + } + } + logger.warning("Permission not found: " + permission); + return null; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPersonRoleMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPersonRoleMapper.java index 65d8737f..40593525 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPersonRoleMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPersonRoleMapper.java @@ -1,12 +1,10 @@ package com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers; import com.sublinks.sublinksapi.api.lemmy.v3.utils.DateUtils; -import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.RoleResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.PersonRoleResponse; import com.sublinks.sublinksapi.authorization.entities.Role; -import com.sublinks.sublinksapi.authorization.entities.RolePermissions; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import java.util.Date; -import java.util.List; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; @@ -16,13 +14,12 @@ @Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {RolePermissionService.class}) -public abstract class SublinksPersonRoleMapper implements Converter { +public abstract class SublinksPersonRoleMapper implements Converter { @Override @Mapping(target = "key", source = "role.name") @Mapping(target = "name", source = "role.name") @Mapping(target = "description", source = "role.description") - @Mapping(target = "permissions", source = "role", qualifiedByName = "permissions") @Mapping(target = "isActive", source = "role.active") @Mapping(target = "isExpired", source = "role", qualifiedByName = "is_expired") @Mapping(target = "expiresAt", @@ -34,7 +31,7 @@ public abstract class SublinksPersonRoleMapper implements Converter mapPermissions(Role role) { - - return role.getRolePermissions() - .stream() - .map(RolePermissions::getPermission) - .toList(); - } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPersonMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java similarity index 53% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPersonMapper.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java index 5e220963..e5aeb76e 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksPersonMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java @@ -1,27 +1,40 @@ package com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers; import com.sublinks.sublinksapi.api.lemmy.v3.utils.DateUtils; -import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.PersonRoleResponse; import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.RoleResponse; import com.sublinks.sublinksapi.authorization.entities.Role; +import com.sublinks.sublinksapi.authorization.enums.RolePermissionInterface; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import java.util.Date; +import java.util.Set; +import java.util.stream.Collectors; import org.mapstruct.Mapper; import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; import org.mapstruct.Named; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING, - uses = {RolePermissionService.class}) -public abstract class SublinksPersonMapper implements - Converter { + uses = {RolePermissionService.class, SublinksPermissionInterfaceMapper.class}) +public abstract class SublinksRoleMapper implements Converter { + + @Autowired + public SublinksPermissionInterfaceMapper sublinksPermissionInterfaceMapper; + + @Autowired + public RolePermissionService rolePermissionService; @Override @Mapping(target = "key", source = "role.name") @Mapping(target = "name", source = "role.name") @Mapping(target = "description", source = "role.description") + @Mapping(target = "permissions", source = "role", qualifiedByName = "map_permissions") + @Mapping(target = "inheritedPermissions", + source = "role", + qualifiedByName = "map_inherited_permissions") + @Mapping(target = "inheritsFrom", source = "role.inheritsFrom.name") @Mapping(target = "isActive", source = "role.active") @Mapping(target = "isExpired", source = "role", qualifiedByName = "is_expired") @Mapping(target = "expiresAt", @@ -33,8 +46,7 @@ public abstract class SublinksPersonMapper implements @Mapping(target = "updatedAt", source = "role.updatedAt", dateFormat = DateUtils.FRONT_END_DATE_FORMAT) - public abstract RoleResponse convert( - @Nullable Role role); + public abstract RoleResponse convert(@Nullable Role role); @Named("is_expired") Boolean mapIsExpired(Role role) { @@ -44,4 +56,19 @@ Boolean mapIsExpired(Role role) { } return new Date().after(role.getExpiresAt()); } + + @Named("map_permissions") + Set mapPermissions(Role role) { + + return role.getRolePermissions() + .stream() + .map((permission) -> sublinksPermissionInterfaceMapper.convert(permission.getPermission())) + .collect(Collectors.toSet()); + } + + @Named("map_inherited_permissions") + Set mapInheritedPermissions(Role role) { + + return rolePermissionService.getRolePermissions(role); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/RoleResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/RoleResponse.java index 5bbd289b..0183e93d 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/RoleResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/models/RoleResponse.java @@ -12,6 +12,10 @@ public record RoleResponse( String name, String description, Set permissions, + @Schema(requiredMode = RequiredMode.REQUIRED, + description = "The permissions this role inherits") Set inheritedPermissions, + @Schema(requiredMode = RequiredMode.NOT_REQUIRED, + description = "The role this role inherits from") String inheritsFrom, Boolean isActive, Boolean isExpired, @Schema(requiredMode = RequiredMode.NOT_REQUIRED, diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java index 253b86bc..1eece90a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java @@ -39,12 +39,29 @@ public List indexRole(final IndexRole indexRoleForm, final Person .performTheAction(RolePermissionRoleTypes.ROLE_READ) .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); - return roleRepository.findAllByNameIsLikeIgnoreCase(indexRoleForm.search(), - PageRequest.of(indexRoleForm.page(), indexRoleForm.limit(), indexRoleForm.sort() - .equals(SortOrder.Asc) ? Sort.by("name") - .ascending() : Sort.by("name") - .descending())) - .stream() + final List roles = new java.util.ArrayList<>(); + + final int page = indexRoleForm.page() != null ? Math.max(indexRoleForm.page(), 0) : 0; + final int limit = indexRoleForm.limit() != null ? Math.max(Math.min(indexRoleForm.limit(), 20), + 0) : 20; + + if (indexRoleForm.search() != null) { + roles.addAll(roleRepository.findAllByNameIsLikeIgnoreCase(indexRoleForm.search(), + PageRequest.of(page, limit, indexRoleForm.sort() != null && indexRoleForm.sort() + .equals(SortOrder.Desc) ? Sort.by("name") + .descending() : Sort.by("name") + .ascending()))); + } else { + roles.addAll(roleRepository.findAll(PageRequest.of(page, limit, + indexRoleForm.sort() != null && indexRoleForm.sort() + .equals(SortOrder.Desc) ? Sort.by("name") + .ascending() : Sort.by("name") + .descending())) + .stream() + .toList()); + } + + return roles.stream() .map(role -> conversionService.convert(role, RoleResponse.class)) .toList(); } diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/entities/Acl.java b/src/main/java/com/sublinks/sublinksapi/authorization/entities/Acl.java index 777d49ff..3f1dab3e 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/entities/Acl.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/entities/Acl.java @@ -43,7 +43,6 @@ public class Acl { @Column(updatable = false, nullable = false, name = "entity_type") @Enumerated(EnumType.STRING) - private AuthorizedEntityType entityType; @Column(updatable = true, nullable = false, name = "entity_id") @@ -59,7 +58,6 @@ public class Acl { @Column(updatable = false, nullable = false, name = "created_at") private Date createdAt; - @UpdateTimestamp(source = SourceType.DB) @Column(updatable = false, name = "updated_at") private Date updatedAt; diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/entities/Role.java b/src/main/java/com/sublinks/sublinksapi/authorization/entities/Role.java index 82cafe72..767c8b41 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/entities/Role.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/entities/Role.java @@ -7,6 +7,8 @@ import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import java.util.Date; @@ -32,8 +34,15 @@ public class Role { @OneToMany(mappedBy = "role", fetch = FetchType.EAGER) Set rolePermissions; + @ManyToOne + @JoinColumn(name = "inherits_from", referencedColumnName = "id", nullable = true) + Role inheritsFrom; + + @OneToMany(mappedBy = "inheritsFrom", fetch = FetchType.LAZY) + Set inheritedRoles; + @OneToMany(mappedBy = "role") - private Set persons; + Set persons; @Id @@ -56,7 +65,6 @@ public class Role { @Column(updatable = false, nullable = false, name = "created_at") private Date createdAt; - @UpdateTimestamp(source = SourceType.DB) @Column(updatable = false, name = "updated_at") private Date updatedAt; diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/enums/AllRoleTypes.java b/src/main/java/com/sublinks/sublinksapi/authorization/enums/AllRoleTypes.java new file mode 100644 index 00000000..054c9ca7 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/authorization/enums/AllRoleTypes.java @@ -0,0 +1,27 @@ +package com.sublinks.sublinksapi.authorization.enums; + +import java.util.ArrayList; +import java.util.List; + +public class AllRoleTypes { + + public static final List ALL_ROLE_TYPES = getAuthorizedEntityTypes(); + + public static List getAuthorizedEntityTypes() { + + final List permissions = new ArrayList<>(); + + permissions.addAll(List.of(RolePermissionRoleTypes.values())); + permissions.addAll(List.of(RolePermissionPostTypes.values())); + permissions.addAll(List.of(RolePermissionCommentTypes.values())); + permissions.addAll(List.of(RolePermissionPrivateMessageTypes.values())); + permissions.addAll(List.of(RolePermissionInstanceTypes.values())); + permissions.addAll(List.of(RolePermissionCommunityTypes.values())); + permissions.addAll(List.of(RolePermissionEmojiTypes.values())); + permissions.addAll(List.of(RolePermissionMediaTypes.values())); + permissions.addAll(List.of(RolePermissionModLogTypes.values())); + permissions.addAll(List.of(RolePermissionPersonTypes.values())); + + return permissions; + } +} diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java index 1b3d3dc1..809c724a 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java @@ -14,6 +14,7 @@ import com.sublinks.sublinksapi.authorization.enums.RoleTypes; import com.sublinks.sublinksapi.authorization.repositories.RolePermissionsRepository; import com.sublinks.sublinksapi.authorization.repositories.RoleRepository; +import jakarta.transaction.Transactional; import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; @@ -33,14 +34,15 @@ public class InitialRoleSetupService { /** * Generates the initial roles for the application. */ + @Transactional public void generateInitialRoles() { if (roleRepository.findAll() .isEmpty()) { - createAdminRole(); - createBannedRole(); - createGuestRole(); - createRegisteredRole(); + final Role bannedRole = createBannedRole(); + final Role guestRole = createGuestRole(bannedRole); + final Role registeredRole = createRegisteredRole(guestRole); + createAdminRole(registeredRole); } } @@ -50,7 +52,8 @@ public void generateInitialRoles() { * @param role the role for which the permissions are being saved * @param rolePermissions the set of role permissions to be saved */ - private void savePermissions(Role role, Set rolePermissions) { + @Transactional + protected Role savePermissions(Role role, Set rolePermissions) { role.setRolePermissions(rolePermissions.stream() .map(rolePermission -> rolePermissionsRepository.save(RolePermissions.builder() @@ -58,6 +61,8 @@ private void savePermissions(Role role, Set rolePermiss .permission(rolePermission.toString()) .build())) .collect(Collectors.toSet())); + + return roleRepository.save(role); } /** @@ -65,7 +70,8 @@ private void savePermissions(Role role, Set rolePermiss * * @param rolePermissions the set of role permissions to which common permissions will be added */ - private void applyCommonPermissions(Set rolePermissions) { + @Transactional + protected void applyCommonPermissions(Set rolePermissions) { rolePermissions.add(RolePermissionPrivateMessageTypes.READ_PRIVATE_MESSAGE); rolePermissions.add(RolePermissionPrivateMessageTypes.READ_PRIVATE_MESSAGES); @@ -91,10 +97,12 @@ private void applyCommonPermissions(Set rolePermissions /** * Creates the admin role with the specified permissions. */ - private void createAdminRole() { + @Transactional + protected void createAdminRole(final Role inheritedRole) { Set rolePermissions = new HashSet<>(); Role adminRole = roleRepository.save(Role.builder() + .inheritsFrom(inheritedRole) .description("Admin role for admins") .name(RoleTypes.ADMIN.toString()) .isActive(true) @@ -106,24 +114,26 @@ private void createAdminRole() { /** * Creates the guest role with all associated permissions. */ - private void createGuestRole() { + @Transactional + protected Role createGuestRole(final Role inheritedRole) { Set rolePermissions = new HashSet<>(); - applyCommonPermissions(rolePermissions); Role defaultUserRole = roleRepository.save(Role.builder() + .inheritsFrom(inheritedRole) .description("Default role for all users") .name(RoleTypes.GUEST.toString()) .isActive(true) .build()); - savePermissions(defaultUserRole, rolePermissions); + return savePermissions(defaultUserRole, rolePermissions); } /** * Creates the banned role with all associated permissions. */ - private void createBannedRole() { + @Transactional + protected Role createBannedRole() { Set rolePermissions = new HashSet<>(); applyCommonPermissions(rolePermissions); @@ -134,13 +144,14 @@ private void createBannedRole() { .isActive(true) .build()); - savePermissions(bannedRole, rolePermissions); + return savePermissions(bannedRole, rolePermissions); } /** * Creates the registered role with all associated permissions. */ - private void createRegisteredRole() { + @Transactional + protected Role createRegisteredRole(final Role inheritedRole) { Set rolePermissions = new HashSet<>(); applyCommonPermissions(rolePermissions); @@ -214,10 +225,11 @@ private void createRegisteredRole() { Role registeredUserRole = roleRepository.save(Role.builder() .description("Default Role for all registered users") + .inheritsFrom(inheritedRole) .name(RoleTypes.REGISTERED.toString()) .isActive(true) .build()); - savePermissions(registeredUserRole, rolePermissions); + return savePermissions(registeredUserRole, rolePermissions); } } diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/RolePermissionService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/RolePermissionService.java index e884c727..04851943 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/RolePermissionService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/RolePermissionService.java @@ -1,6 +1,7 @@ package com.sublinks.sublinksapi.authorization.services; import com.sublinks.sublinksapi.authorization.entities.Role; +import com.sublinks.sublinksapi.authorization.entities.RolePermissions; import com.sublinks.sublinksapi.authorization.enums.RolePermissionInterface; import com.sublinks.sublinksapi.authorization.enums.RoleTypes; import com.sublinks.sublinksapi.community.entities.Community; @@ -9,11 +10,14 @@ import com.sublinks.sublinksapi.person.enums.LinkPersonCommunityType; import com.sublinks.sublinksapi.person.services.LinkPersonCommunityService; import jakarta.annotation.Nullable; +import java.util.HashSet; import java.util.Objects; import java.util.Set; import java.util.function.Supplier; +import java.util.stream.Collectors; import lombok.NonNull; import lombok.RequiredArgsConstructor; +import org.springframework.core.convert.ConversionService; import org.springframework.stereotype.Service; /** @@ -26,6 +30,7 @@ public class RolePermissionService { private final RoleService roleService; private final LinkPersonCommunityService linkPersonCommunityService; private final CommunityRepository communityRepository; + private final ConversionService conversionService; /** * Checks if a role is banned. @@ -112,7 +117,8 @@ public static boolean isAdmin(@NonNull final Role role) { * @throws X The exception provided by the exceptionSupplier if the person is not an admin. */ public static void isAdminElseThrow(Person person, - Supplier exceptionSupplier) throws X { + Supplier exceptionSupplier) throws X + { if (!isAdmin(person)) { throw exceptionSupplier.get(); @@ -127,7 +133,8 @@ public static void isAdminElseThrow(Person person, * @return True if the person is permitted, false otherwise. */ public boolean isPermitted(@Nullable final Person person, - final RolePermissionInterface rolePermission) { + final RolePermissionInterface rolePermission) + { final Role role = person == null ? roleService.getDefaultGuestRole( () -> new RuntimeException("No Guest role found.")) : person.getRole(); @@ -142,8 +149,8 @@ public boolean isPermitted(@Nullable final Person person, * @param rolePermission The permission to check. * @return True if the role is permitted, false otherwise. */ - public boolean isPermitted(@NonNull final Role role, - final RolePermissionInterface rolePermission) { + public boolean isPermitted(@NonNull final Role role, final RolePermissionInterface rolePermission) + { return isAdmin(role) || doesRoleHavePermission(role, rolePermission); } @@ -156,7 +163,8 @@ public boolean isPermitted(@NonNull final Role role, * @return True if the role is permitted, false otherwise. */ public boolean isPermitted(@NonNull final Role role, - final Set rolePermissions) { + final Set rolePermissions) + { return rolePermissions.stream() .anyMatch(x -> doesRoleHavePermission(role, x)); @@ -174,7 +182,8 @@ public boolean isPermitted(@NonNull final Role role, */ public void isPermitted(@NonNull final Role role, final Set rolePermissions, Supplier exceptionSupplier) - throws X { + throws X + { if (!isPermitted(role, rolePermissions)) { throw exceptionSupplier.get(); @@ -193,7 +202,8 @@ public void isPermitted(@NonNull final Role role, */ public void isPermitted(final Person person, final Set rolePermissions, Supplier exceptionSupplier) - throws X { + throws X + { final Role role = person == null ? roleService.getDefaultGuestRole( () -> new RuntimeException("No Guest role found.")) : person.getRole(); @@ -213,7 +223,8 @@ public void isPermitted(final Person person, */ public void isPermitted(@NonNull final Role role, final RolePermissionInterface rolePermission, Supplier exceptionSupplier) - throws X { + throws X + { if (!isPermitted(role, rolePermission)) { throw exceptionSupplier.get(); @@ -232,7 +243,8 @@ public void isPermitted(@NonNull final Role role, */ public void isPermitted(final Person person, final RolePermissionInterface rolePermission, Supplier exceptionSupplier) - throws X { + throws X + { if (person != null && person.isDeleted()) { throw exceptionSupplier.get(); @@ -253,12 +265,13 @@ public void isPermitted(final Person person, * @return True if the person is permitted, false otherwise. */ public boolean isPermitted(final Person person, final RolePermissionInterface rolePermission, - final Long communityId) { + final Long communityId) + { if (person != null && person.isDeleted()) { return false; } - Role role = null; + Role role; if (person != null) { role = isBannedInCommunity(person, communityId) ? this.roleService.getBannedRole() @@ -283,7 +296,8 @@ public boolean isPermitted(final Person person, final RolePermissionInterface ro */ public void isPermitted(final Person person, final RolePermissionInterface rolePermission, final Long communityId, - final Supplier exceptionSupplier) throws X { + final Supplier exceptionSupplier) throws X + { if (person != null && person.isDeleted()) { throw exceptionSupplier.get(); @@ -306,14 +320,30 @@ public void isPermitted(final Person person, * @return True if the role has the permission, false otherwise. */ private boolean doesRoleHavePermission(final Role role, - final RolePermissionInterface rolePermission) { + final RolePermissionInterface rolePermission) + { if (isAdmin(role)) { return true; } - return role.getRolePermissions() - .stream() - .anyMatch(x -> x.getPermission() + return getRolePermissions(role).stream() + .anyMatch(x -> x.toString() .equals(rolePermission.toString())); } + + public Set getRolePermissions(final Role role) { + + final Set rolePermissions = new HashSet<>(); + + Role currentRole = role; + + while (currentRole != null) { + rolePermissions.addAll(currentRole.getRolePermissions()); + currentRole = currentRole.getInheritsFrom(); + } + + return rolePermissions.stream() + .map(x -> conversionService.convert(x.getPermission(), RolePermissionInterface.class)) + .collect(Collectors.toSet()); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/RoleService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/RoleService.java index 290a02ae..6d54feff 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/RoleService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/RoleService.java @@ -24,7 +24,6 @@ public class RoleService { private final RoleCreatedPublisher roleCreatedEventPublisher; private final RoleUpdatedPublisher roleUpdatedEventPublisher; private final RoleDeletedPublisher roleDeletedEventPublisher; - private final RolePermissionService rolePermissionService; /** diff --git a/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java b/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java index 8fee773e..7a71f070 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/person/services/LinkPersonCommunityService.java @@ -233,13 +233,14 @@ public List getLinksByEntity(Community community, community, linkPersonCommunityType); } - public Collection getLinkPersonCommunitiesByCommunityAndPersonAndLinkTypeIsIn( + public List getLinkPersonCommunitiesByCommunityAndPersonAndLinkTypeIsIn( Community community, List types) { - return linkPersonCommunityRepository.getLinkPersonCommunitiesByCommunityAndLinkTypeIsIn( - community, types); + return linkPersonCommunityRepository.getLinkPersonCommunitiesByCommunityAndLinkTypeIn(community, + types); } + @Override public List getLinksByEntity(Community community) { 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 724fa7b3..aed2c3bc 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 @@ -678,16 +678,18 @@ CREATE INDEX IDX_CUSTOM_EMOJI_KEYWORD_CUSTOM_EMOJI_ID ON custom_emoji_keywords ( */ CREATE TABLE acl_roles ( - id BIGSERIAL PRIMARY KEY, - name VARCHAR(255) NOT NULL, - description TEXT NOT NULL, - is_active BOOL NOT NULL DEFAULT true, - expires_at TIMESTAMP(3) NULL, - created_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL, - updated_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL + id BIGSERIAL PRIMARY KEY, + name VARCHAR(255) NOT NULL, + description TEXT NOT NULL, + inherits_from BIGINT NULL, + is_active BOOL NOT NULL DEFAULT true, + expires_at TIMESTAMP(3) NULL, + created_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL, + updated_at TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL ); CREATE UNIQUE INDEX IDX_ACL_ROLES_NAME ON acl_roles (name); +CREATE INDEX IDX_ACL_ROLES_INHERITS_FROM ON acl_roles (inherits_from); /** Role permissions table diff --git a/src/main/resources/db/migration/V20231006__Create_foreign_key_references.sql b/src/main/resources/db/migration/V20231006__Create_foreign_key_references.sql index bc45a3df..d4f61b93 100644 --- a/src/main/resources/db/migration/V20231006__Create_foreign_key_references.sql +++ b/src/main/resources/db/migration/V20231006__Create_foreign_key_references.sql @@ -196,6 +196,11 @@ ALTER TABLE custom_emoji_keywords ALTER TABLE acl_role_permissions ADD FOREIGN KEY (role_id) REFERENCES acl_roles (id) ON DELETE CASCADE; +/** + Role Table + */ +ALTER TABLE acl_roles + ADD FOREIGN KEY (inherits_from) REFERENCES acl_roles (id) ON DELETE SET NULL; /** People table From efb22c17a6101354d024268f8a8da57296ec8004 Mon Sep 17 00:00:00 2001 From: rooki Date: Tue, 6 Aug 2024 21:02:02 +0200 Subject: [PATCH 081/115] Enable Hikari pool suspension and update transactional annotations Added properties for Hikari connection pool suspension and PostgreSQL dialect to `application.properties`. Replaced `jakarta.transaction.Transactional` with `org.springframework.transaction.annotation.Transactional` and updated method implementations in `InitialRoleSetupService`. Signed-off-by: rooki --- .../services/InitialRoleSetupService.java | 12 +++--------- src/main/resources/application.properties | 2 ++ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java index 809c724a..ba85a6b3 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java @@ -14,12 +14,12 @@ import com.sublinks.sublinksapi.authorization.enums.RoleTypes; import com.sublinks.sublinksapi.authorization.repositories.RolePermissionsRepository; import com.sublinks.sublinksapi.authorization.repositories.RoleRepository; -import jakarta.transaction.Transactional; import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; /** * Service class for generating the initial roles for the application. @@ -34,7 +34,7 @@ public class InitialRoleSetupService { /** * Generates the initial roles for the application. */ - @Transactional + @Transactional() public void generateInitialRoles() { if (roleRepository.findAll() @@ -52,7 +52,6 @@ public void generateInitialRoles() { * @param role the role for which the permissions are being saved * @param rolePermissions the set of role permissions to be saved */ - @Transactional protected Role savePermissions(Role role, Set rolePermissions) { role.setRolePermissions(rolePermissions.stream() @@ -62,7 +61,7 @@ protected Role savePermissions(Role role, Set rolePermi .build())) .collect(Collectors.toSet())); - return roleRepository.save(role); + return roleRepository.saveAndFlush(role); } /** @@ -70,7 +69,6 @@ protected Role savePermissions(Role role, Set rolePermi * * @param rolePermissions the set of role permissions to which common permissions will be added */ - @Transactional protected void applyCommonPermissions(Set rolePermissions) { rolePermissions.add(RolePermissionPrivateMessageTypes.READ_PRIVATE_MESSAGE); @@ -97,7 +95,6 @@ protected void applyCommonPermissions(Set rolePermissio /** * Creates the admin role with the specified permissions. */ - @Transactional protected void createAdminRole(final Role inheritedRole) { Set rolePermissions = new HashSet<>(); @@ -114,7 +111,6 @@ protected void createAdminRole(final Role inheritedRole) { /** * Creates the guest role with all associated permissions. */ - @Transactional protected Role createGuestRole(final Role inheritedRole) { Set rolePermissions = new HashSet<>(); @@ -132,7 +128,6 @@ protected Role createGuestRole(final Role inheritedRole) { /** * Creates the banned role with all associated permissions. */ - @Transactional protected Role createBannedRole() { Set rolePermissions = new HashSet<>(); @@ -150,7 +145,6 @@ protected Role createBannedRole() { /** * Creates the registered role with all associated permissions. */ - @Transactional protected Role createRegisteredRole(final Role inheritedRole) { Set rolePermissions = new HashSet<>(); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index cf6d23e8..d107ed11 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -87,3 +87,5 @@ sublinks.keep_comment_history=${KEEP_COMMENT_HISTORY:false} spring.thymeleaf.check-template-location=false # enable enable_lazy_load_no_trans spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true +spring.datasource.hikari.allow-pool-suspension=true +spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect \ No newline at end of file From ba4f7d0b5d80513e3a8fce260174be39df9a300b Mon Sep 17 00:00:00 2001 From: rooki Date: Tue, 6 Aug 2024 23:21:50 +0200 Subject: [PATCH 082/115] Refactor role saving to use saveAndFlush Changed repository calls to saveAndFlush for immediate persistence. Updated savePermissions method to return void and improved role initialization with empty permissions set. This change ensures that roles and permissions are persistently stored in the database immediately. Signed-off-by: rooki --- .../services/InitialRoleSetupService.java | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java index ba85a6b3..31b1d88c 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java @@ -52,16 +52,14 @@ public void generateInitialRoles() { * @param role the role for which the permissions are being saved * @param rolePermissions the set of role permissions to be saved */ - protected Role savePermissions(Role role, Set rolePermissions) { + protected void savePermissions(Role role, Set rolePermissions) { role.setRolePermissions(rolePermissions.stream() - .map(rolePermission -> rolePermissionsRepository.save(RolePermissions.builder() + .map(rolePermission -> rolePermissionsRepository.saveAndFlush(RolePermissions.builder() .role(role) .permission(rolePermission.toString()) .build())) .collect(Collectors.toSet())); - - return roleRepository.saveAndFlush(role); } /** @@ -98,10 +96,11 @@ protected void applyCommonPermissions(Set rolePermissio protected void createAdminRole(final Role inheritedRole) { Set rolePermissions = new HashSet<>(); - Role adminRole = roleRepository.save(Role.builder() + Role adminRole = roleRepository.saveAndFlush(Role.builder() .inheritsFrom(inheritedRole) .description("Admin role for admins") .name(RoleTypes.ADMIN.toString()) + .rolePermissions(new HashSet<>()) .isActive(true) .build()); @@ -115,14 +114,16 @@ protected Role createGuestRole(final Role inheritedRole) { Set rolePermissions = new HashSet<>(); - Role defaultUserRole = roleRepository.save(Role.builder() + Role defaultUserRole = roleRepository.saveAndFlush(Role.builder() .inheritsFrom(inheritedRole) .description("Default role for all users") .name(RoleTypes.GUEST.toString()) + .rolePermissions(new HashSet<>()) .isActive(true) .build()); - return savePermissions(defaultUserRole, rolePermissions); + savePermissions(defaultUserRole, rolePermissions); + return defaultUserRole; } /** @@ -133,13 +134,15 @@ protected Role createBannedRole() { Set rolePermissions = new HashSet<>(); applyCommonPermissions(rolePermissions); - Role bannedRole = roleRepository.save(Role.builder() + Role bannedRole = roleRepository.saveAndFlush(Role.builder() .description("Banned role for banned users") .name(RoleTypes.BANNED.toString()) + .rolePermissions(new HashSet<>()) .isActive(true) .build()); - return savePermissions(bannedRole, rolePermissions); + savePermissions(bannedRole, rolePermissions); + return bannedRole; } /** @@ -217,13 +220,15 @@ protected Role createRegisteredRole(final Role inheritedRole) { rolePermissions.add(RolePermissionCommunityTypes.REPORT_COMMUNITY_RESOLVE); rolePermissions.add(RolePermissionCommunityTypes.REPORT_COMMUNITY_READ); - Role registeredUserRole = roleRepository.save(Role.builder() + Role registeredUserRole = roleRepository.saveAndFlush(Role.builder() .description("Default Role for all registered users") .inheritsFrom(inheritedRole) + .rolePermissions(new HashSet<>()) .name(RoleTypes.REGISTERED.toString()) .isActive(true) .build()); - return savePermissions(registeredUserRole, rolePermissions); + savePermissions(registeredUserRole, rolePermissions); + return registeredUserRole; } } From 89e2b8d1c6180b1347eb4daa6a1aa9e0638af583 Mon Sep 17 00:00:00 2001 From: rooki Date: Wed, 7 Aug 2024 17:25:16 +0200 Subject: [PATCH 083/115] Refactor role and permissions mapping for improved handling Simplify the SublinksRoleMapper by removing the injection of unused dependencies and use lambda expressions mapping permissions directly. Update InitialRoleSetupService to use appropriate transaction handling and entity merging for improved data consistency. Adjust Role and RolePermission entities to correctly manage relationships with cascade operations for easier maintenance of role-based permissions. Signed-off-by: rooki --- .../v1/roles/mappers/SublinksRoleMapper.java | 21 +++++-------------- .../authorization/entities/Role.java | 16 +++++++------- .../entities/RolePermissions.java | 4 +++- .../services/InitialRoleSetupService.java | 12 +++++++---- 4 files changed, 25 insertions(+), 28 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java index e5aeb76e..c26aaf13 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java @@ -12,19 +12,14 @@ import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; import org.mapstruct.Named; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING, - uses = {RolePermissionService.class, SublinksPermissionInterfaceMapper.class}) + uses = {RolePermissionService.class}) public abstract class SublinksRoleMapper implements Converter { - @Autowired - public SublinksPermissionInterfaceMapper sublinksPermissionInterfaceMapper; - - @Autowired - public RolePermissionService rolePermissionService; + protected RolePermissionService rolePermissionService; @Override @Mapping(target = "key", source = "role.name") @@ -32,8 +27,7 @@ public abstract class SublinksRoleMapper implements Converter mapPermissions(Role role) { return role.getRolePermissions() .stream() - .map((permission) -> sublinksPermissionInterfaceMapper.convert(permission.getPermission())) + .map((permission) -> new SublinksPermissionInterfaceMapper().convert( + permission.getPermission())) .collect(Collectors.toSet()); } - - @Named("map_inherited_permissions") - Set mapInheritedPermissions(Role role) { - - return rolePermissionService.getRolePermissions(role); - } } diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/entities/Role.java b/src/main/java/com/sublinks/sublinksapi/authorization/entities/Role.java index 767c8b41..76f10e3b 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/entities/Role.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/entities/Role.java @@ -1,6 +1,7 @@ package com.sublinks.sublinksapi.authorization.entities; import com.sublinks.sublinksapi.person.entities.Person; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; @@ -12,6 +13,7 @@ import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import java.util.Date; +import java.util.HashSet; import java.util.Set; import lombok.AllArgsConstructor; import lombok.Builder; @@ -31,18 +33,18 @@ @Table(name = "acl_roles") public class Role { - @OneToMany(mappedBy = "role", fetch = FetchType.EAGER) - Set rolePermissions; + @OneToMany(mappedBy = "role", fetch = FetchType.EAGER, cascade = CascadeType.ALL) + private Set rolePermissions; - @ManyToOne + @ManyToOne(targetEntity = Role.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "inherits_from", referencedColumnName = "id", nullable = true) - Role inheritsFrom; + private Role inheritsFrom; - @OneToMany(mappedBy = "inheritsFrom", fetch = FetchType.LAZY) - Set inheritedRoles; + @OneToMany(mappedBy = "inheritsFrom", fetch = FetchType.LAZY, cascade = CascadeType.ALL) + private Set inheritedRoles; @OneToMany(mappedBy = "role") - Set persons; + private Set persons; @Id diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/entities/RolePermissions.java b/src/main/java/com/sublinks/sublinksapi/authorization/entities/RolePermissions.java index 52e29594..571ea77f 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/entities/RolePermissions.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/entities/RolePermissions.java @@ -1,7 +1,9 @@ package com.sublinks.sublinksapi.authorization.entities; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -27,7 +29,7 @@ public class RolePermissions { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; - @ManyToOne + @ManyToOne(targetEntity = Role.class, fetch = FetchType.LAZY, cascade = CascadeType.ALL) @JoinColumn(name = "role_id", referencedColumnName = "id", nullable = false) private Role role; diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java index 31b1d88c..29c4741f 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java @@ -14,12 +14,13 @@ import com.sublinks.sublinksapi.authorization.enums.RoleTypes; import com.sublinks.sublinksapi.authorization.repositories.RolePermissionsRepository; import com.sublinks.sublinksapi.authorization.repositories.RoleRepository; +import jakarta.persistence.EntityManager; +import jakarta.transaction.Transactional; import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; /** * Service class for generating the initial roles for the application. @@ -30,11 +31,12 @@ public class InitialRoleSetupService { private final RoleRepository roleRepository; private final RolePermissionsRepository rolePermissionsRepository; + private final EntityManager entityManager; /** * Generates the initial roles for the application. */ - @Transactional() + @Transactional public void generateInitialRoles() { if (roleRepository.findAll() @@ -54,12 +56,14 @@ public void generateInitialRoles() { */ protected void savePermissions(Role role, Set rolePermissions) { - role.setRolePermissions(rolePermissions.stream() + entityManager.merge(role); + Set rolePermissionsSet = rolePermissions.stream() .map(rolePermission -> rolePermissionsRepository.saveAndFlush(RolePermissions.builder() .role(role) .permission(rolePermission.toString()) .build())) - .collect(Collectors.toSet())); + .collect(Collectors.toSet()); + } /** From c058838152380688235fa650a0e48e2d696cc993 Mon Sep 17 00:00:00 2001 From: rooki Date: Thu, 8 Aug 2024 09:16:47 +0200 Subject: [PATCH 084/115] Enhance mapper configurations and simplify role permission logic Added unmapped target policy to SublinksPersonMapper to ignore mapping issues. Removed unused import and simplified permission saving logic in InitialRoleSetupService. Refactored SublinksRoleMapper by removing dependency on RolePermissionService and added methods for mapping inherited permissions. Signed-off-by: rooki --- .../person/mappers/SublinksPersonMapper.java | 5 +++- .../v1/roles/mappers/SublinksRoleMapper.java | 30 +++++++++++++++---- .../services/InitialRoleSetupService.java | 10 +++---- src/main/resources/application.properties | 1 - 4 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java index 981835f6..bfc19341 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java @@ -11,11 +11,14 @@ import org.mapstruct.Mapping; import org.mapstruct.MappingConstants; import org.mapstruct.Named; +import org.mapstruct.ReportingPolicy; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, uses = {SublinksRoleMapper.class}) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, + uses = {SublinksRoleMapper.class}, + unmappedTargetPolicy = ReportingPolicy.IGNORE) public abstract class SublinksPersonMapper implements Converter { @Autowired diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java index c26aaf13..c14c4860 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/mappers/SublinksRoleMapper.java @@ -3,9 +3,10 @@ import com.sublinks.sublinksapi.api.lemmy.v3.utils.DateUtils; import com.sublinks.sublinksapi.api.sublinks.v1.roles.models.RoleResponse; import com.sublinks.sublinksapi.authorization.entities.Role; +import com.sublinks.sublinksapi.authorization.entities.RolePermissions; import com.sublinks.sublinksapi.authorization.enums.RolePermissionInterface; -import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import java.util.Date; +import java.util.HashSet; import java.util.Set; import java.util.stream.Collectors; import org.mapstruct.Mapper; @@ -15,19 +16,17 @@ import org.springframework.core.convert.converter.Converter; import org.springframework.lang.Nullable; -@Mapper(componentModel = MappingConstants.ComponentModel.SPRING, - uses = {RolePermissionService.class}) +@Mapper(componentModel = MappingConstants.ComponentModel.SPRING) public abstract class SublinksRoleMapper implements Converter { - protected RolePermissionService rolePermissionService; - @Override @Mapping(target = "key", source = "role.name") @Mapping(target = "name", source = "role.name") @Mapping(target = "description", source = "role.description") @Mapping(target = "permissions", source = "role", qualifiedByName = "map_permissions") @Mapping(target = "inheritedPermissions", - expression = "java(rolePermissionService.getRolePermissions(role))") + source = "role", + qualifiedByName = "map_inherited_permissions") @Mapping(target = "inheritsFrom", source = "role.inheritsFrom.name") @Mapping(target = "isActive", source = "role.active") @Mapping(target = "isExpired", source = "role", qualifiedByName = "is_expired") @@ -51,6 +50,7 @@ Boolean mapIsExpired(Role role) { return new Date().after(role.getExpiresAt()); } + // @code-duplication TODO: Improve the following two methods as i didnt knew any better way to do inject those dependencies @Named("map_permissions") Set mapPermissions(Role role) { @@ -60,4 +60,22 @@ Set mapPermissions(Role role) { permission.getPermission())) .collect(Collectors.toSet()); } + + @Named("map_inherited_permissions") + Set mapInheritedPermissions(Role role) { + + final Set rolePermissions = new HashSet<>(); + + Role currentRole = role; + + while (currentRole != null) { + rolePermissions.addAll(currentRole.getRolePermissions()); + currentRole = currentRole.getInheritsFrom(); + } + + return rolePermissions.stream() + .map((permission) -> new SublinksPermissionInterfaceMapper().convert( + permission.getPermission())) + .collect(Collectors.toSet()); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java index 29c4741f..672086ec 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/InitialRoleSetupService.java @@ -18,7 +18,6 @@ import jakarta.transaction.Transactional; import java.util.HashSet; import java.util.Set; -import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -57,12 +56,12 @@ public void generateInitialRoles() { protected void savePermissions(Role role, Set rolePermissions) { entityManager.merge(role); - Set rolePermissionsSet = rolePermissions.stream() - .map(rolePermission -> rolePermissionsRepository.saveAndFlush(RolePermissions.builder() + rolePermissionsRepository.saveAllAndFlush(rolePermissions.stream() + .map(rolePermission -> RolePermissions.builder() .role(role) .permission(rolePermission.toString()) - .build())) - .collect(Collectors.toSet()); + .build()) + .toList()); } @@ -155,7 +154,6 @@ protected Role createBannedRole() { protected Role createRegisteredRole(final Role inheritedRole) { Set rolePermissions = new HashSet<>(); - applyCommonPermissions(rolePermissions); rolePermissions.add(RolePermissionMediaTypes.CREATE_MEDIA); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d107ed11..94f349f1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -88,4 +88,3 @@ spring.thymeleaf.check-template-location=false # enable enable_lazy_load_no_trans spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true spring.datasource.hikari.allow-pool-suspension=true -spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect \ No newline at end of file From d3be4233cb4317f14b672abbaecdba377d4249cb Mon Sep 17 00:00:00 2001 From: rooki Date: Thu, 8 Aug 2024 12:59:24 +0200 Subject: [PATCH 085/115] Refactor role service pagination and formatting of controller methods Reorganized pagination logic in SublinksRoleService for better readability and consistency. Improved code formatting in AdminController by aligning opening braces for better style compliance and readability. Signed-off-by: rooki --- .../v3/admin/controllers/AdminController.java | 23 +++++++++++-------- .../roles/services/SublinksRoleService.java | 20 ++++++++-------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/admin/controllers/AdminController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/admin/controllers/AdminController.java index e87f73ca..5316b7bb 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/admin/controllers/AdminController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/admin/controllers/AdminController.java @@ -144,7 +144,8 @@ AddAdminResponse create(@Valid @RequestBody final AddAdmin addAdminForm, JwtPers @GetMapping("registration_application/count") GetUnreadRegistrationApplicationCountResponse registrationApplicationCount( @Valid GetUnreadRegistrationApplicationCount getUnreadRegistrationApplicationCountForm, - JwtPerson principal) { + JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -168,7 +169,8 @@ GetUnreadRegistrationApplicationCountResponse registrationApplicationCount( @GetMapping("registration_application/list") ListRegistrationApplicationsResponse registrationApplicationList( @Valid final ListRegistrationApplications listRegistrationApplicationsForm, - JwtPerson principal) { + JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -194,7 +196,8 @@ ListRegistrationApplicationsResponse registrationApplicationList( @PutMapping("registration_application/approve") RegistrationApplicationResponse registrationApplicationApprove( @Valid final ApproveRegistrationApplication approveRegistrationApplicationForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -231,7 +234,8 @@ RegistrationApplicationResponse registrationApplicationApprove( schema = @Schema(implementation = PurgeItemResponse.class))})}) @PostMapping("purge/person") PurgeItemResponse purgePerson(@Valid @RequestBody final PurgePerson purgePersonForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -242,8 +246,6 @@ PurgeItemResponse purgePerson(@Valid @RequestBody final PurgePerson purgePersonF final Person personToPurge = personRepository.findById((long) purgePersonForm.person_id()) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_not_found")); - final int removedPostHistory = postHistoryService.deleteAllByCreator(personToPurge); - final int removedCommentHistory = commentHistoryService.deleteAllByCreator(personToPurge); // @todo: Log purged history amount? // @todo: Implement purging @@ -257,7 +259,8 @@ PurgeItemResponse purgePerson(@Valid @RequestBody final PurgePerson purgePersonF schema = @Schema(implementation = PurgeItemResponse.class))})}) @PostMapping("purge/community") PurgeItemResponse purgeCommunity(@Valid @RequestBody final PurgeCommunity purgeCommunityForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -275,7 +278,8 @@ PurgeItemResponse purgeCommunity(@Valid @RequestBody final PurgeCommunity purgeC schema = @Schema(implementation = PurgeItemResponse.class))})}) @PostMapping("purge/post") PurgeItemResponse purgePost(@Valid @RequestBody final PurgePost purgePostForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); @@ -302,7 +306,8 @@ PurgeItemResponse purgePost(@Valid @RequestBody final PurgePost purgePostForm, schema = @Schema(implementation = PurgeItemResponse.class))})}) @PostMapping("purge/comment") PurgeItemResponse purgeComment(@Valid @RequestBody final PurgeComment purgeCommentForm, - final JwtPerson principal) { + final JwtPerson principal) + { final Person person = getPersonOrThrowUnauthorized(principal); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java index 1eece90a..17b61c05 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java @@ -45,18 +45,18 @@ public List indexRole(final IndexRole indexRoleForm, final Person final int limit = indexRoleForm.limit() != null ? Math.max(Math.min(indexRoleForm.limit(), 20), 0) : 20; + Sort sortOrder = indexRoleForm.sort() != null && indexRoleForm.sort() + .equals(SortOrder.Desc) ? Sort.by("name") + .descending() : Sort.by("name") + .ascending(); + + PageRequest pageRequest = PageRequest.of(page, limit, sortOrder); + if (indexRoleForm.search() != null) { - roles.addAll(roleRepository.findAllByNameIsLikeIgnoreCase(indexRoleForm.search(), - PageRequest.of(page, limit, indexRoleForm.sort() != null && indexRoleForm.sort() - .equals(SortOrder.Desc) ? Sort.by("name") - .descending() : Sort.by("name") - .ascending()))); + roles.addAll( + roleRepository.findAllByNameIsLikeIgnoreCase(indexRoleForm.search(), pageRequest)); } else { - roles.addAll(roleRepository.findAll(PageRequest.of(page, limit, - indexRoleForm.sort() != null && indexRoleForm.sort() - .equals(SortOrder.Desc) ? Sort.by("name") - .ascending() : Sort.by("name") - .descending())) + roles.addAll(roleRepository.findAll(pageRequest) .stream() .toList()); } From f364f080e355049abb457df68a9e240e10ef3a37 Mon Sep 17 00:00:00 2001 From: rooki Date: Thu, 8 Aug 2024 16:10:12 +0200 Subject: [PATCH 086/115] Add missing newline for better readability Added a newline between import statements in AdminController.java. This enhances code readability and conforms to standard formatting practices. No functional changes were made. Signed-off-by: rooki --- .../api/lemmy/v3/admin/controllers/AdminController.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/admin/controllers/AdminController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/admin/controllers/AdminController.java index 5316b7bb..954215d3 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/admin/controllers/AdminController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/admin/controllers/AdminController.java @@ -50,7 +50,9 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; + import java.util.List; + import lombok.AllArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; From 9510654dd5c04aacec2021da73a8fb7219421c29 Mon Sep 17 00:00:00 2001 From: rooki Date: Fri, 9 Aug 2024 09:27:19 +0200 Subject: [PATCH 087/115] Add purge post functionality to moderation Implemented a new purge feature with corresponding service method and controller endpoint to handle requests. Added the PurgePost model to represent the request payload and updated ACL service to check permissions. Signed-off-by: rooki --- .../SublinksPostModerationController.java | 16 ++++++--------- .../v1/post/models/moderation/PurgePost.java | 13 ++++++++++++ .../v1/post/services/SublinksPostService.java | 20 +++++++++++++++++++ .../authorization/services/AclService.java | 11 +++++----- 4 files changed, 45 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/PurgePost.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java index febbac15..9f3cce7d 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java @@ -1,10 +1,11 @@ package com.sublinks.sublinksapi.api.sublinks.v1.post.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; -import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.PostResponse; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.PinPost; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.PurgePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.RemovePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.services.SublinksPostService; import com.sublinks.sublinksapi.person.entities.Person; @@ -66,24 +67,19 @@ public PostResponse pinInCommunity(@PathVariable final String key, return sublinksPostService.pinCommunity(key, pinPostForm, person); } - @Operation(summary = "Purge a post") @PostMapping("/purge") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public RequestResponse delete(@PathVariable final String key, - @RequestBody final RemovePost removePostForm, final SublinksJwtPerson sublinksJwtPerson) + @RequestBody final PurgePost purgePostForm, final SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - // @todo: implement - - return RequestResponse.builder() - .success(false) - .error("not_implemented") - .build(); + return sublinksPostService.purge(key, purgePostForm != null ? purgePostForm + : PurgePost.builder() + .build(), person); } - } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/PurgePost.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/PurgePost.java new file mode 100644 index 00000000..60ca9b96 --- /dev/null +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/models/moderation/PurgePost.java @@ -0,0 +1,13 @@ +package com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation; + +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import lombok.Builder; + +@Builder +public record PurgePost( + @Schema(description = "The reason for purging the post", + example = "This post is spam", + requiredMode = RequiredMode.NOT_REQUIRED) String reason) { + +} diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index 0ca9ad4d..f6938f32 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -1,5 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.post.services; +import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.AggregatePostResponse; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.CreatePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.DeletePost; @@ -8,6 +9,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.post.models.UpdatePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.FavoritePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.PinPost; +import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.PurgePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.moderation.RemovePost; import com.sublinks.sublinksapi.authorization.enums.RolePermissionPostTypes; import com.sublinks.sublinksapi.authorization.services.AclService; @@ -564,4 +566,22 @@ public PostResponse pinCommunity(final String postKey, final PinPost pinPostForm return conversionService.convert(post, PostResponse.class); } + + public RequestResponse purge(final String postKey, final PurgePost removePostForm, + final Person person) + { + + final Post post = postRepository.findByTitleSlug(postKey) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "post_not_found")); + + aclService.canPerson(person) + .onCommunity(post.getCommunity()) + .performTheAction(RolePermissionPostTypes.PURGE_POST) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + + return RequestResponse.builder() + .success(false) + .error("not_implemented") + .build(); + } } \ No newline at end of file diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/AclService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/AclService.java index 85733fd2..41e5ce61 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/AclService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/AclService.java @@ -107,7 +107,8 @@ public static class EntityPolicy { * @param aclRepository the repository for accessing and manipulating ACL entities */ public EntityPolicy(final ActionType actionType, final AclRepository aclRepository, - final RolePermissionService rolePermissionService, RoleService roleService) { + final RolePermissionService rolePermissionService, RoleService roleService) + { this.roleService = roleService; @@ -127,7 +128,8 @@ public EntityPolicy(final ActionType actionType, final AclRepository aclReposito */ public EntityPolicy(final Person person, final ActionType actionType, final AclRepository aclRepository, RolePermissionService rolePermissionService, - RoleService roleService) { + RoleService roleService) + { this.person = person; this.actionType = actionType; @@ -201,8 +203,8 @@ public boolean isPermitted() { * @param the type of exception to be thrown * @throws X the thrown exception if the condition is not satisfied */ - public void orElseThrow(Supplier exceptionSupplier) - throws X { + public void orElseThrow(Supplier exceptionSupplier) throws X + { execute(); if (!isPermitted) { @@ -248,7 +250,6 @@ private void checkRolePermission() { this.isPermitted = this.authorizedActions.stream() .allMatch(permission -> rolePermissionService.isPermitted(this.person, permission, community.getId())); - } else { this.isPermitted = this.authorizedActions.stream() .allMatch(permission -> rolePermissionService.isPermitted(this.person, permission)); From eecb193ddb265f9a0ae0df5a66cefbf4e3de2348 Mon Sep 17 00:00:00 2001 From: rooki Date: Fri, 9 Aug 2024 09:30:59 +0200 Subject: [PATCH 088/115] Implement purge post placeholder in SublinksPostService Added a placeholder for the purge post functionality in SublinksPostService. This includes a TODO comment to mark where the implementation should occur and sets the response to indicate the feature is not yet implemented. Signed-off-by: rooki --- .../api/sublinks/v1/post/services/SublinksPostService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index f6938f32..83382620 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -579,6 +579,8 @@ public RequestResponse purge(final String postKey, final PurgePost removePostFor .performTheAction(RolePermissionPostTypes.PURGE_POST) .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + // @todo: implement + return RequestResponse.builder() .success(false) .error("not_implemented") From 2625aae58536ad02807419aaefb974fcd5a6f083 Mon Sep 17 00:00:00 2001 From: rooki Date: Fri, 9 Aug 2024 11:10:05 +0200 Subject: [PATCH 089/115] Refactor method parameter name in purge method Updated the parameter name from `removePostForm` to `purgePostForm` in the `purge` method for better consistency and clarity. This change improves code readability and maintains alignment with naming conventions used throughout the class. Signed-off-by: rooki --- .../api/sublinks/v1/post/services/SublinksPostService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index 83382620..7bf4b407 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -567,7 +567,7 @@ public PostResponse pinCommunity(final String postKey, final PinPost pinPostForm return conversionService.convert(post, PostResponse.class); } - public RequestResponse purge(final String postKey, final PurgePost removePostForm, + public RequestResponse purge(final String postKey, final PurgePost purgePostForm, final Person person) { From 417249951b27c46f4b1c695add5c0862171d4db3 Mon Sep 17 00:00:00 2001 From: rooki Date: Fri, 9 Aug 2024 18:31:38 +0200 Subject: [PATCH 090/115] Add @NonNull annotations and comprehensive JavaDocs Enhanced the codebase with @NonNull annotations to prevent null values and added detailed JavaDocs for better method documentation. This improves code robustness and readability by clearly specifying non-null contract and providing usage guidelines for methods. Signed-off-by: rooki --- .../v1/post/services/SublinksPostService.java | 10 ++++++ .../roles/services/SublinksRoleService.java | 33 +++++++++++++++++-- .../services/InstanceConfigService.java | 6 ++-- .../instance/services/InstanceService.java | 7 ++-- 4 files changed, 47 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index 7bf4b407..5b5d8777 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -567,6 +567,16 @@ public PostResponse pinCommunity(final String postKey, final PinPost pinPostForm return conversionService.convert(post, PostResponse.class); } + /** + * Removes a post from the system. + * + * @param postKey The key of the post to be removed. + * @param purgePostForm The PurgePost object containing the removal information. + * @param person The Person object representing the user removing the post. + * @return The RequestResponse object indicating the result of the purge operation. + * @throws ResponseStatusException If the post is not found, the user is unauthorized, or an error + * occurs during the removal process. + */ public RequestResponse purge(final String postKey, final PurgePost purgePostForm, final Person person) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java index 17b61c05..af619606 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java @@ -16,6 +16,7 @@ import java.util.List; import java.util.stream.Collectors; import lombok.AllArgsConstructor; +import lombok.NonNull; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; @@ -66,7 +67,7 @@ public List indexRole(final IndexRole indexRoleForm, final Person .toList(); } - public RoleResponse show(final String key, final Person person) + public RoleResponse show(@NonNull final String key, final Person person) { aclService.canPerson(person) @@ -78,7 +79,17 @@ public RoleResponse show(final String key, final Person person) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "role not found")); } - public RoleResponse create(final CreateRole createRoleForm, final Person person) + /** + * Creates a new role with the provided information. + * + * @param createRoleForm The CreateRole object containing the details of the role to be created. + * Must not be null. + * @param person The Person object representing the user performing the action. Must not + * be null. + * @return The RoleResponse object representing the newly created role. + * @throws ResponseStatusException if the user is not authorized to perform the action. + */ + public RoleResponse create(@NonNull final CreateRole createRoleForm, final Person person) { aclService.canPerson(person) @@ -102,7 +113,20 @@ public RoleResponse create(final CreateRole createRoleForm, final Person person) return conversionService.convert(roleService.createRole(role), RoleResponse.class); } - public RoleResponse update(final String key, final UpdateRole updateRoleForm, final Person person) + /** + * Updates an existing role with the provided information. + * + * @param key The key of the role to be updated. Must not be null. + * @param updateRoleForm The UpdateRole object containing the updated details of the role. Must + * not be null. + * @param person The Person object representing the user performing the action. Must not + * be null. + * @return The RoleResponse object representing the updated role. + * @throws ResponseStatusException if the user is not authorized to perform the action or if the + * role is not found. + */ + public RoleResponse update(final String key, @NonNull final UpdateRole updateRoleForm, + final Person person) { aclService.canPerson(person) @@ -136,6 +160,9 @@ public RoleResponse update(final String key, final UpdateRole updateRoleForm, fi return conversionService.convert(roleService.updateRole(role), RoleResponse.class); } + /** + * + */ public void delete(final String key, final Person person) { diff --git a/src/main/java/com/sublinks/sublinksapi/instance/services/InstanceConfigService.java b/src/main/java/com/sublinks/sublinksapi/instance/services/InstanceConfigService.java index df38d25e..36e28e0f 100644 --- a/src/main/java/com/sublinks/sublinksapi/instance/services/InstanceConfigService.java +++ b/src/main/java/com/sublinks/sublinksapi/instance/services/InstanceConfigService.java @@ -4,7 +4,7 @@ import com.sublinks.sublinksapi.instance.events.InstanceConfigCreatedPublisher; import com.sublinks.sublinksapi.instance.events.InstanceConfigUpdatedPublisher; import com.sublinks.sublinksapi.instance.repositories.InstanceConfigRepository; -import jakarta.validation.constraints.NotNull; +import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -18,14 +18,14 @@ public class InstanceConfigService { private final InstanceConfigUpdatedPublisher instanceConfigUpdatedPublisher; @Transactional - public void createInstanceConfig(@NotNull InstanceConfig instance) { + public void createInstanceConfig(@NonNull InstanceConfig instance) { instanceConfigRepository.save(instance); instanceConfigCreatedPublisher.publish(instance); } @Transactional - public void updateInstanceConfig(@NotNull InstanceConfig instance) { + public void updateInstanceConfig(@NonNull InstanceConfig instance) { instanceConfigRepository.save(instance); instanceConfigUpdatedPublisher.publish(instance); diff --git a/src/main/java/com/sublinks/sublinksapi/instance/services/InstanceService.java b/src/main/java/com/sublinks/sublinksapi/instance/services/InstanceService.java index 78b09aea..605198ee 100644 --- a/src/main/java/com/sublinks/sublinksapi/instance/services/InstanceService.java +++ b/src/main/java/com/sublinks/sublinksapi/instance/services/InstanceService.java @@ -6,6 +6,7 @@ import com.sublinks.sublinksapi.utils.KeyGeneratorUtil; import com.sublinks.sublinksapi.utils.KeyStore; import jakarta.validation.constraints.NotNull; +import lombok.NonNull; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -20,7 +21,7 @@ public class InstanceService { private final KeyGeneratorUtil keyGeneratorUtil; @Transactional - public void createInstance(@NotNull Instance instance) { + public void createInstance(@NonNull Instance instance) { KeyStore keys = keyGeneratorUtil.generate(); instance.setPublicKey(keys.publicKey()); @@ -29,14 +30,14 @@ public void createInstance(@NotNull Instance instance) { } @Transactional - public void createInstanceAndFlush(@NotNull Instance instance) { + public void createInstanceAndFlush(@NonNull Instance instance) { createInstance(instance); instanceRepository.flush(); } @Transactional - public void updateInstance(@NotNull Instance instance) { + public void updateInstance(@NonNull Instance instance) { instanceRepository.save(instance); } From 528cc2f26ee049e9b91418a44eea892eb0226fe3 Mon Sep 17 00:00:00 2001 From: rooki Date: Fri, 9 Aug 2024 18:38:26 +0200 Subject: [PATCH 091/115] Adjust pagination to start from the first page. Previously, the pagination could start at an invalid state due to using zero as the lower bound. This change ensures that pagination correctly starts at page 1 instead of 0. Signed-off-by: rooki --- .../sublinksapi/post/services/PostSearchQueryService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/sublinks/sublinksapi/post/services/PostSearchQueryService.java b/src/main/java/com/sublinks/sublinksapi/post/services/PostSearchQueryService.java index fd8cc58f..58bec90e 100644 --- a/src/main/java/com/sublinks/sublinksapi/post/services/PostSearchQueryService.java +++ b/src/main/java/com/sublinks/sublinksapi/post/services/PostSearchQueryService.java @@ -70,7 +70,7 @@ public Results setPerPage(final Integer perPage) { public Results setPage(final Integer page) { - int p = Math.max(Math.abs(page), 0); + int p = Math.max(Math.abs(page), 1); getQuery().setFirstResult((p - 1) * this.getPerPage()); return this; } From 42662ce9ca21039f202d860e6f00f3c630a817ad Mon Sep 17 00:00:00 2001 From: rooki Date: Fri, 9 Aug 2024 21:05:11 +0200 Subject: [PATCH 092/115] Add SublinksCommentSortTypeMapper and refactor sort type logic Introduced SublinksCommentSortTypeMapper to map SortType to CommentSortType. Refactored SublinksCommentService for improved sortType and listingType handling, ensuring null checks and default value assignments. Removed redundant imports in the affected classes. Signed-off-by: rooki --- .../mappers/SublinksCommentMapper.java | 2 - .../SublinksCommentSortTypeMapper.java | 38 +++++++++++++++++++ .../services/SublinksCommentService.java | 35 +++++++++-------- 3 files changed, 55 insertions(+), 20 deletions(-) create mode 100644 src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentSortTypeMapper.java diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java index d8139dba..94d0b575 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java @@ -33,6 +33,4 @@ public abstract class SublinksCommentMapper implements Converter { + + @Override + @ValueMapping(source = "Hot", target = "Hot") + @ValueMapping(source = "New", target = "New") + @ValueMapping(source = "Old", target = "Old") + @ValueMapping(source = "Active", target = "Hot") + @ValueMapping(source = "TopDay", target = "Top") + @ValueMapping(source = "TopWeek", target = "Top") + @ValueMapping(source = "TopMonth", target = "Top") + @ValueMapping(source = "TopYear", target = "Top") + @ValueMapping(source = "TopAll", target = "Top") + @ValueMapping(source = "MostComments", target = "Top") + @ValueMapping(source = "NewComments", target = "Top") + @ValueMapping(source = "TopHour", target = "Top") + @ValueMapping(source = "TopSixHour", target = "Top") + @ValueMapping(source = "TopTwelveHour", target = "Top") + @ValueMapping(source = "TopThreeMonths", target = "Top") + @ValueMapping(source = "TopSixMonths", target = "Top") + @ValueMapping(source = "TopNineMonths", target = "Top") + @ValueMapping(source = "Controversial", target = "New") + @ValueMapping(source = "Scaled", target = "New") + public abstract CommentSortType convert(@Nullable SortType sortType); +} \ No newline at end of file diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index 3f9fd4ed..5394679e 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -8,8 +8,6 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.PinComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.RemoveComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.UpdateComment; -import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SortType; -import com.sublinks.sublinksapi.api.sublinks.v1.common.enums.SublinksListingType; import com.sublinks.sublinksapi.authorization.enums.RolePermissionCommentTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.comment.entities.Comment; @@ -27,6 +25,7 @@ import com.sublinks.sublinksapi.person.entities.Person; import com.sublinks.sublinksapi.person.enums.LinkPersonCommunityType; import com.sublinks.sublinksapi.person.enums.ListingType; +import com.sublinks.sublinksapi.person.enums.SortType; import com.sublinks.sublinksapi.person.services.LinkPersonCommunityService; import com.sublinks.sublinksapi.post.entities.Post; import com.sublinks.sublinksapi.post.repositories.PostRepository; @@ -88,24 +87,24 @@ public List index(final IndexComment indexCommentForm, final Pe post = postRepository.findByTitleSlug(indexCommentForm.postKey()); } - SortType sortType = indexCommentForm.sortType(); - if (sortType == null) { - if (person.getDefaultSortType() != null) { - sortType = conversionService.convert(person.getDefaultSortType(), SortType.class); - } else { - sortType = SortType.New; - } + com.sublinks.sublinksapi.person.enums.SortType sortType; + if (indexCommentForm.sortType() != null) { + sortType = conversionService.convert(indexCommentForm.sortType(), SortType.class); + } else if (person != null && person.getDefaultSortType() != null) { + sortType = person.getDefaultSortType(); + } else { + sortType = SortType.New; } - SublinksListingType sublinksListingType = indexCommentForm.listingType(); + ListingType sublinksListingType; - if (sublinksListingType == null) { - if (person.getDefaultListingType() != null) { - sublinksListingType = conversionService.convert(person.getDefaultListingType(), - SublinksListingType.class); - } else { - sublinksListingType = SublinksListingType.Local; - } + if (indexCommentForm.listingType() != null) { + sublinksListingType = conversionService.convert(indexCommentForm.listingType(), + ListingType.class); + } else if (person != null && person.getDefaultListingType() != null) { + sublinksListingType = person.getDefaultListingType(); + } else { + sublinksListingType = ListingType.Local; } CommentSearchCriteria.CommentSearchCriteriaBuilder commentSearchCriteria = CommentSearchCriteria.builder() @@ -114,7 +113,7 @@ public List index(final IndexComment indexCommentForm, final Pe .commentSortType(conversionService.convert(sortType, CommentSortType.class)) .perPage(indexCommentForm.perPage()) .savedOnly(indexCommentForm.savedOnly()) - .listingType(conversionService.convert(sublinksListingType, ListingType.class)) + .listingType(sublinksListingType) .community(community.orElse(null)) .parent(parentComment.orElse(null)) .post(post.orElse(null)) From a0c129d80955c4dc4e6b7365e2649e2a540a3658 Mon Sep 17 00:00:00 2001 From: rooki Date: Sun, 11 Aug 2024 09:45:59 +0200 Subject: [PATCH 093/115] Refactor comment entities and service methods Updated the Comment entity to have a nullable searchVector, modified service logic for saving comments, and added toBuilder for CommentResponse. Refactored SublinksRoleMapper usage and improved formatting consistency in service classes. Signed-off-by: rooki --- .../mappers/SublinksCommentMapper.java | 4 +-- .../v1/comment/models/CommentResponse.java | 2 +- .../services/SublinksCommentService.java | 3 +- .../person/mappers/SublinksPersonMapper.java | 4 +-- .../services/SublinksPersonService.java | 30 +++++++------------ .../sublinksapi/comment/entities/Comment.java | 2 +- .../comment/services/CommentService.java | 16 +++++----- src/main/resources/application.properties | 1 + 8 files changed, 28 insertions(+), 34 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java index 94d0b575..cd15a3ad 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java @@ -22,7 +22,7 @@ public abstract class SublinksCommentMapper implements Converter index(final IndexComment indexCommentForm, final Pe Math.min(indexCommentForm.maxDepth() != null ? indexCommentForm.maxDepth() : 3, 5), 0)) .person(person); - return commentRepository.allCommentsBySearchCriteria(commentSearchCriteria.build()) + return commentRepository.allCommentsBySearchCriteria( + commentSearchCriteria.build()) .stream() .map(comment -> conversionService.convert(comment, CommentResponse.class)) .toList(); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java index bfc19341..a080414d 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java @@ -1,7 +1,7 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.mappers; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; -import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksRoleMapper; +import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksPersonRoleMapper; import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; import com.sublinks.sublinksapi.api.sublinks.v1.utils.PersonKeyUtils; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; @@ -17,7 +17,7 @@ import org.springframework.lang.Nullable; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING, - uses = {SublinksRoleMapper.class}, + uses = {SublinksPersonRoleMapper.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE) public abstract class SublinksPersonMapper implements Converter { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index 47f16bb8..98445a80 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -470,8 +470,7 @@ public PersonResponse banPerson(final BanPerson banPersonForm, final Person pers public PersonAggregateResponse showAggregate(final String key, final Person person) { rolePermissionService.isPermitted(person, RolePermissionPersonTypes.READ_PERSON_AGGREGATION, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final PersonIdentity personIdentity = getPersonIdentifiersFromKey(key); @@ -530,10 +529,7 @@ public PersonResponse deletePerson(final String key, final DeletePerson deletePe /** * Retrieves the meta data of a person's sessions based on the provided target key and person. * - * @param targetKey The key containing the target person's information. If the key contains "@", - * it is split into name and domain using "@" as the separator. Otherwise, the - * name is set as the key and the domain is obtained from the local instance - * context. + * @param targetKey The key containing the target person's information. * @param person The Person object representing the person making the request. * @return The PersonSessionDataResponse object containing the meta data of the target person's * sessions. @@ -553,8 +549,7 @@ public PersonSessionDataResponse getMetaData(final String targetKey, final Perso RolePermissionPersonTypes.READ_USER_OWN_METADATAS) && person.equals(target)) && !rolePermissionService.isPermitted(person, RolePermissionPersonTypes.READ_USER_METADATAS)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } final List personMetaData = userDataRepository.findAllByPerson(target); @@ -578,12 +573,12 @@ public PersonSessionDataResponse getMetaData(final String targetKey, final Perso * or if the person does not have permission to read the session's * meta data or if the session is not found. */ - public PersonSessionDataResponse getOneMetaData(final String targetsessionKey, + public PersonSessionDataResponse getOneMetaData(final String targetSessionKey, final Person person) { final PersonMetaData personMetaData = userDataRepository.findById( - Long.parseLong(targetsessionKey)) + Long.parseLong(targetSessionKey)) .orElseThrow( () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_metadata_not_found")); @@ -591,8 +586,7 @@ public PersonSessionDataResponse getOneMetaData(final String targetsessionKey, RolePermissionPersonTypes.READ_USER_OWN_METADATAS) && personMetaData.getPerson() .equals(person)) && !rolePermissionService.isPermitted(person, RolePermissionPersonTypes.READ_USER_METADATAS)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } return PersonSessionDataResponse.builder() @@ -625,8 +619,7 @@ public void invalidateUserData(String targetUserData, final Person person) { RolePermissionPersonTypes.INVALIDATE_USER_OWN_METADATA) && personMetaData.getPerson() .equals(person)) && !rolePermissionService.isPermitted(person, RolePermissionPersonTypes.INVALIDATE_USER_METADATA)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } userDataService.invalidate(personMetaData); @@ -654,8 +647,7 @@ public void invalidateAllUserData(final String targetPersonKey, final Person per RolePermissionPersonTypes.INVALIDATE_USER_OWN_METADATA) && target.equals(person)) && !rolePermissionService.isPermitted(person, RolePermissionPersonTypes.INVALIDATE_USER_METADATA)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } userDataService.invalidateAllUserData(target); @@ -679,8 +671,7 @@ public void deleteUserData(final String targetUserDataKey, final Person person) RolePermissionPersonTypes.DELETE_USER_OWN_METADATA) && !personMetaData.getPerson() .equals(person) && !rolePermissionService.isPermitted(person, RolePermissionPersonTypes.DELETE_USER_METADATA)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } userDataRepository.delete(personMetaData); @@ -706,8 +697,7 @@ public void deleteAllUserData(final String targetPersonKey, final Person person) RolePermissionPersonTypes.DELETE_USER_OWN_METADATA) && target.equals(person)) && !rolePermissionService.isPermitted(person, RolePermissionPersonTypes.DELETE_USER_METADATA)) { - throw new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized"); + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } userDataRepository.deleteAll(userDataRepository.findAllByPerson(target)); } diff --git a/src/main/java/com/sublinks/sublinksapi/comment/entities/Comment.java b/src/main/java/com/sublinks/sublinksapi/comment/entities/Comment.java index 512f8954..0ac3c292 100644 --- a/src/main/java/com/sublinks/sublinksapi/comment/entities/Comment.java +++ b/src/main/java/com/sublinks/sublinksapi/comment/entities/Comment.java @@ -110,7 +110,7 @@ public class Comment implements Serializable, AclEntityInterface { @Column(nullable = false) private String path; - @Column(nullable = false, name = "search_vector") + @Column(nullable = true, updatable = false, insertable = false, name = "search_vector") private String searchVector; @CreationTimestamp(source = SourceType.DB) diff --git a/src/main/java/com/sublinks/sublinksapi/comment/services/CommentService.java b/src/main/java/com/sublinks/sublinksapi/comment/services/CommentService.java index 9d7e76ff..d9c0690d 100644 --- a/src/main/java/com/sublinks/sublinksapi/comment/services/CommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/comment/services/CommentService.java @@ -38,7 +38,8 @@ public class CommentService { * @return A string representing the ActivityPub ID. */ public String generateActivityPubId( - final com.sublinks.sublinksapi.comment.entities.Comment comment) { + final com.sublinks.sublinksapi.comment.entities.Comment comment) + { String domain = localInstanceContext.instance() .getDomain(); @@ -75,9 +76,9 @@ public Optional getParentComment(final Comment comment) { @Transactional public void createComment(final Comment comment) { + commentRepository.saveAndFlush(comment); if (comment.getPath() == null || comment.getPath() .isBlank()) { - commentRepository.saveAndFlush(comment); comment.setPath(String.format("0.%d", comment.getId())); } comment.setActivityPubId(generateActivityPubId(comment)); @@ -195,11 +196,12 @@ public void updateComment(final Comment comment) { */ @Transactional public void removeAllCommentsFromCommunityAndUser(final Community community, final Person person, - final boolean removed) { + final boolean removed) + { commentRepository.allCommentsByCommunityAndPersonAndRemoved(community, person, - List.of(removed ? RemovedState.NOT_REMOVED : RemovedState.REMOVED_BY_COMMUNITY)).forEach( - comment -> { + List.of(removed ? RemovedState.NOT_REMOVED : RemovedState.REMOVED_BY_COMMUNITY)) + .forEach(comment -> { comment.setRemovedState( removed ? RemovedState.REMOVED_BY_COMMUNITY : RemovedState.NOT_REMOVED); commentRepository.save(comment); @@ -216,8 +218,8 @@ public void removeAllCommentsFromCommunityAndUser(final Community community, fin public void removeAllCommentsFromUser(final Person person, final boolean removed) { commentRepository.allCommentsByPersonAndRemoved(person, - List.of(removed ? RemovedState.NOT_REMOVED : RemovedState.REMOVED_BY_INSTANCE)).forEach( - comment -> { + List.of(removed ? RemovedState.NOT_REMOVED : RemovedState.REMOVED_BY_INSTANCE)) + .forEach(comment -> { comment.setRemovedState( removed ? RemovedState.REMOVED_BY_INSTANCE : RemovedState.NOT_REMOVED); commentRepository.save(comment); diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 94f349f1..d22c67cd 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -88,3 +88,4 @@ spring.thymeleaf.check-template-location=false # enable enable_lazy_load_no_trans spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true spring.datasource.hikari.allow-pool-suspension=true +SublinksRoleMapper \ No newline at end of file From 700476ee786610f8626aacff8a731899c8a49a28 Mon Sep 17 00:00:00 2001 From: rooki Date: Sun, 11 Aug 2024 09:53:02 +0200 Subject: [PATCH 094/115] Remove replies field from CommentResponse model Removed the `replies` field and associated annotations from the `CommentResponse` model. Updated dependent mappers and services to reflect this change. This simplifies the comment structure and reduces unnecessary data handling. Signed-off-by: rooki --- .../sublinks/v1/comment/mappers/SublinksCommentMapper.java | 1 - .../api/sublinks/v1/comment/models/CommentResponse.java | 5 ----- .../sublinks/v1/comment/services/SublinksCommentService.java | 3 +-- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java index cd15a3ad..ee8133ee 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java @@ -31,6 +31,5 @@ public abstract class SublinksCommentMapper implements Converter replies, String updatedAt) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index 4f18b687..5394679e 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -121,8 +121,7 @@ public List index(final IndexComment indexCommentForm, final Pe Math.min(indexCommentForm.maxDepth() != null ? indexCommentForm.maxDepth() : 3, 5), 0)) .person(person); - return commentRepository.allCommentsBySearchCriteria( - commentSearchCriteria.build()) + return commentRepository.allCommentsBySearchCriteria(commentSearchCriteria.build()) .stream() .map(comment -> conversionService.convert(comment, CommentResponse.class)) .toList(); From 15a53ea44b325f40cc4d56c7ff9867324487eb48 Mon Sep 17 00:00:00 2001 From: rooki Date: Sun, 11 Aug 2024 10:28:09 +0200 Subject: [PATCH 095/115] Remove @Builder annotation from CommentResponse The @Builder annotation was removed from the CommentResponse record. This change likely addresses redundancy or issues caused by the builder pattern in this specific context. Signed-off-by: rooki --- .../api/sublinks/v1/comment/models/CommentResponse.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentResponse.java index cb814ce8..87582533 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentResponse.java @@ -3,7 +3,6 @@ import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import lombok.Builder; -@Builder(toBuilder = true) public record CommentResponse( String key, String activityPubId, From ebb7e4118b01d472d9a35e9bed96ca72527ae999 Mon Sep 17 00:00:00 2001 From: rooki Date: Sun, 11 Aug 2024 11:37:06 +0200 Subject: [PATCH 096/115] Add instance data to PersonResponse and improve search handling Added InstanceResponse to PersonResponse model and integrated ACL service in person search for better permissions handling. Refactored pagination and search methods, optimizing query execution, and cleaned up unused imports. Signed-off-by: rooki --- .../SublinksCommentController.java | 9 +-- .../controllers/SublinksPersonController.java | 12 +-- .../person/mappers/SublinksPersonMapper.java | 4 +- .../v1/person/models/PersonResponse.java | 2 + .../services/SublinksPersonService.java | 81 +++++++++++++------ .../person/repositories/PersonRepository.java | 6 +- .../sublinksapi/utils/PaginationUtils.java | 24 +++++- 7 files changed, 93 insertions(+), 45 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java index aa6ee966..6948e167 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java @@ -21,7 +21,6 @@ import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -37,15 +36,15 @@ public class SublinksCommentController extends AbstractSublinksApiController { @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List index( - @RequestParam(required = false) Optional indexCommentParam, + public List index(IndexComment indexCommentParam, final SublinksJwtPerson sublinksJwtPerson) { final Optional person = getOptionalPerson(sublinksJwtPerson); - return sublinksCommentService.index(indexCommentParam.orElse(IndexComment.builder() - .build()), person.orElse(null)); + return sublinksCommentService.index(indexCommentParam != null ? indexCommentParam + : IndexComment.builder() + .build(), person.orElse(null)); } @Operation(summary = "Get a specific comment") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java index 57607a01..6ec89fe2 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java @@ -11,7 +11,6 @@ import com.sublinks.sublinksapi.api.sublinks.v1.person.models.UpdatePerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; import com.sublinks.sublinksapi.person.entities.Person; -import com.sublinks.sublinksapi.person.repositories.PersonRepository; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -21,14 +20,12 @@ import java.util.List; import java.util.Optional; import lombok.AllArgsConstructor; -import org.springframework.core.convert.ConversionService; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -37,22 +34,21 @@ @AllArgsConstructor public class SublinksPersonController extends AbstractSublinksApiController { - private final PersonRepository personRepository; private final SublinksPersonService sublinksPersonService; - private final ConversionService conversionService; @Operation(summary = "Get a list of persons") @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List index(@RequestParam(required = false) final IndexPerson indexPerson, + public List index(final IndexPerson indexPersonParam, final SublinksJwtPerson principal) { final Optional person = getOptionalPerson(principal); - return sublinksPersonService.index(indexPerson == null ? IndexPerson.builder() - .build() : indexPerson, person.orElse(null)); + return sublinksPersonService.index(indexPersonParam != null ? indexPersonParam + : IndexPerson.builder() + .build(), person.orElse(null)); } @Operation(summary = "Get a specific person") diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java index a080414d..717c2d2e 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/mappers/SublinksPersonMapper.java @@ -1,5 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.mappers; +import com.sublinks.sublinksapi.api.sublinks.v1.instance.mappers.SublinksInstanceMapper; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.api.sublinks.v1.roles.mappers.SublinksPersonRoleMapper; import com.sublinks.sublinksapi.api.sublinks.v1.utils.DateUtils; @@ -17,7 +18,7 @@ import org.springframework.lang.Nullable; @Mapper(componentModel = MappingConstants.ComponentModel.SPRING, - uses = {SublinksPersonRoleMapper.class}, + uses = {SublinksPersonRoleMapper.class, SublinksInstanceMapper.class}, unmappedTargetPolicy = ReportingPolicy.IGNORE) public abstract class SublinksPersonMapper implements Converter { @@ -37,6 +38,7 @@ public abstract class SublinksPersonMapper implements Converter index(final IndexPerson indexPerson, final Person person) { - rolePermissionService.isPermitted(person, RolePermissionPersonTypes.READ_USERS, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + aclService.canPerson(person) + .performTheAction(RolePermissionPersonTypes.READ_USER) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + + final int page = PaginationUtils.getPage(indexPerson.page() == null ? 0 : indexPerson.page()) + - 1; + final int perPage = PaginationUtils.getPerPage( + indexPerson.perPage() == null ? 20 : indexPerson.perPage()) - 1; if (indexPerson.search() == null) { - if (indexPerson.listingType() == SublinksListingType.Local) { - return personRepository.findAllByIsLocal(true, - PageRequest.of(Math.max(indexPerson.page(), 0), - PaginationUtils.Clamp(indexPerson.perPage(), 1, 20))) - .stream() - .map(p -> conversionService.convert(p, PersonResponse.class)) - .toList(); - } - return personRepository.findAll(PageRequest.of(Math.max(indexPerson.page(), 1), - PaginationUtils.Clamp(indexPerson.perPage(), 1, 20))) + return handleNullSearch(indexPerson, page, perPage); + } + + return handleNonNullSearch(indexPerson, page, perPage); + } + + /** + * Handles a null search for person records based on the specified index person, page number, and + * items per page. + * + * @param indexPerson the index person used for determining the type of listing + * @param page the page number for pagination + * @param perPage the number of items per page + * @return a list of {@code PersonResponse} objects based on the search query + */ + private List handleNullSearch(IndexPerson indexPerson, int page, int perPage) { + + if (indexPerson.listingType() == SublinksListingType.Local) { + return personRepository.findAllByIsLocal(true, PageRequest.of(page, perPage)) .stream() .map(p -> conversionService.convert(p, PersonResponse.class)) .toList(); } + return personRepository.findAll(PageRequest.of(page, perPage)) + .stream() + .map(p -> conversionService.convert(p, PersonResponse.class)) + .toList(); + } + + /** + * Handles a non-null search for persons in the index. + * + * @param indexPerson the IndexPerson object containing the search parameters + * @param page the page number for pagination + * @param perPage the number of results per page for pagination + * @return a List of PersonResponse objects that match the search criteria + */ + private List handleNonNullSearch(IndexPerson indexPerson, int page, int perPage) { if (indexPerson.listingType() == SublinksListingType.Local) { return personRepository.findAllByNameAndBiographyAndLocal(indexPerson.search(), true, - PageRequest.of(Math.max(indexPerson.page(), 1), - PaginationUtils.Clamp(indexPerson.perPage(), 1, 20))) + PageRequest.of(page, perPage)) .stream() .map(p -> conversionService.convert(p, PersonResponse.class)) .toList(); } - return personRepository.findAllByNameAndBiography(indexPerson.search(), - PageRequest.of(Math.max(indexPerson.page(), 1), - PaginationUtils.Clamp(indexPerson.perPage(), 1, 20))) + PageRequest.of(page, perPage)) .stream() .map(p -> conversionService.convert(p, PersonResponse.class)) .toList(); diff --git a/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java b/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java index 05be9503..a0e156a9 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/person/repositories/PersonRepository.java @@ -29,18 +29,18 @@ public interface PersonRepository extends JpaRepository { List findAllByIsLocal(Boolean local, Pageable pageable); - @Query(value = "SELECT p FROM people p WHERE p.search_vector @@ to_tsquery('keyword', :keyword)", + @Query(value = "SELECT p.*, i.id as link_instance_id, i.instance_id FROM people p JOIN link_person_instances i ON p.id = i.person_id WHERE p.search_vector @@ to_tsquery('english', :keyword)", countQuery = "SELECT COUNT(p.id) FROM people p WHERE p.search_vector @@ to_tsquery('english', :keyword)", nativeQuery = true) List findAllByNameAndBiography(@Param("keyword") String keyword, Pageable pageable); - @Query(value = "SELECT p FROM people p WHERE p.search_vector @@ to_tsquery('keyword', :keyword) AND p.is_local = :local", + @Query(value = "SELECT p.*, i.id as link_instance_id, i.instance_id FROM people p JOIN link_person_instances i ON p.id = i.person_id WHERE p.search_vector @@ to_tsquery('english', :keyword) AND p.is_local = :local", countQuery = "SELECT COUNT(p.id) FROM people p WHERE p.search_vector @@ to_tsquery('english', :keyword)", nativeQuery = true) List findAllByNameAndBiographyAndLocal(@Param("keyword") String keyword, @Param("local") Boolean local, Pageable pageable); - @Query(value = "SELECT p FROM people p WHERE p.search_vector @@ to_tsquery('keyword', :keyword) AND p.role_id = :role", + @Query(value = "SELECT p.*, i.id as link_instance_id, i.instance_id FROM people p JOIN link_person_instances i ON p.id = i.person_id WHERE p.search_vector @@ to_tsquery('english', :keyword) AND p.role_id = :role", countQuery = "SELECT COUNT(p.id) FROM people p WHERE p.search_vector @@ to_tsquery('english', :keyword) AND p.role_id = :role", nativeQuery = true) List findAllByNameAndBiographyAndRole(@Param("keyword") String keyword, diff --git a/src/main/java/com/sublinks/sublinksapi/utils/PaginationUtils.java b/src/main/java/com/sublinks/sublinksapi/utils/PaginationUtils.java index dfbca098..e64c806d 100644 --- a/src/main/java/com/sublinks/sublinksapi/utils/PaginationUtils.java +++ b/src/main/java/com/sublinks/sublinksapi/utils/PaginationUtils.java @@ -25,8 +25,7 @@ public static int getOffset(int page, int size) { * @param page The page number to retrieve. * @param size The number of records per page. */ - public static void applyPagination(TypedQuery query, @Nullable Integer page, - Integer size) + public static void applyPagination(TypedQuery query, @Nullable Integer page, Integer size) { if (page != null) { @@ -39,4 +38,25 @@ public static int Clamp(T value, T min, T max) { return Math.min(Math.max(value, min), max); } + + public static int getPage(T value) { + + return Clamp(value, 1, Integer.MAX_VALUE); + } + + public static int getPerPage(T value, T min, T max) { + + return Clamp(value, min, max); + } + + public static int getPerPage(T value, T max) { + + return getPerPage(value, 1, max); + } + + public static int getPerPage(T value) { + + return getPerPage(value, 1, 20); + } + } From eaad4406f49da8d83afa1753a68b12936beff601 Mon Sep 17 00:00:00 2001 From: rooki Date: Mon, 12 Aug 2024 11:39:10 +0200 Subject: [PATCH 097/115] Refactor comment handling and clean up unused dependencies Implemented a tree structure for handling comment replies, enabling nested comment representation. Removed unused dependencies and redundant fields in various controllers and services for better code maintainability. Also adjusted mappers to accommodate new replies structure, improving overall data handling. Signed-off-by: rooki --- .../SublinksCommentAggerateController.java | 1 - .../mappers/SublinksCommentMapper.java | 1 + .../v1/comment/models/CommentResponse.java | 15 +++++ .../services/SublinksCommentService.java | 65 ++++++++++++++++++- .../services/SublinksCommunityService.java | 3 +- .../SublinksLanguageController.java | 1 - 6 files changed, 81 insertions(+), 5 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java index 6c508365..9a485f18 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java @@ -24,7 +24,6 @@ public class SublinksCommentAggerateController extends AbstractSublinksApiController { private final SublinksCommentService sublinksCommentService; - private final RolePermissionService rolePermissionService; @Operation(summary = "Aggregate a comment") @GetMapping diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java index ee8133ee..5c85d5d7 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/mappers/SublinksCommentMapper.java @@ -31,5 +31,6 @@ public abstract class SublinksCommentMapper implements Converter replies, String updatedAt) { + + public String getId() { + + List ids = List.of(key.split("\\.")); + return ids.get(ids.size() - 1); + } + + public String getParentKey() { + + List ids = List.of(key.split("\\.")); + return ids.size() > 1 ? String.join(".", ids.subList(0, ids.size() - 1)) : null; + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index 5394679e..182a85d4 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -30,6 +30,7 @@ import com.sublinks.sublinksapi.post.entities.Post; import com.sublinks.sublinksapi.post.repositories.PostRepository; import com.sublinks.sublinksapi.shared.RemovedState; +import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -121,10 +122,10 @@ public List index(final IndexComment indexCommentForm, final Pe Math.min(indexCommentForm.maxDepth() != null ? indexCommentForm.maxDepth() : 3, 5), 0)) .person(person); - return commentRepository.allCommentsBySearchCriteria(commentSearchCriteria.build()) + return buildReplies(commentRepository.allCommentsBySearchCriteria(commentSearchCriteria.build()) .stream() .map(comment -> conversionService.convert(comment, CommentResponse.class)) - .toList(); + .toList()); } /** @@ -348,4 +349,64 @@ public AggregateCommentResponse aggregate(String commentKey, Person person) { .orElseThrow( () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); } + + // @todo refactor this fine piece of code. + + /** + * Builds a tree structure of comment replies based on the provided list of CommentResponse + * objects. + * + * @param commentResponses The list of CommentResponse objects representing the comments. + * @return A list of CommentResponse objects representing the comments organized in a tree + * structure. + */ + public List buildReplies(List commentResponses) { + + List rootComments = commentResponses.stream() + .filter( + commentResponse -> commentResponse.getParentKey() == null || commentResponses.stream() + .noneMatch(commentResponse1 -> commentResponse1.key() + .equals(commentResponse.getParentKey()))) + .toList(); + + List commentTree = new ArrayList<>(rootComments.stream() + .map(commentResponse -> buildTree(commentResponse, commentResponses)) + .toList()); + + List leftOverComments = commentResponses.stream() + .filter(commentResponse -> rootComments.stream() + .noneMatch(commentResponse1 -> commentResponse1.key() + .equals(commentResponse.key()))) + .toList(); + + commentTree.addAll(leftOverComments); + + return commentTree; + } + + /** + * Builds a tree structure of comment replies based on the provided list of CommentResponse + * objects. + * + * @param commentResponse The CommentResponse object representing the root comment. + * @param commentResponses The list of CommentResponse objects representing all the comments. + * @return A CommentResponse object representing the root comment with its replies organized in a + * tree structure. + */ + private CommentResponse buildTree(final CommentResponse commentResponse, + List commentResponses) + { + + List replies = commentResponses.stream() + .filter(commentResponse1 -> Objects.equals(commentResponse1.getParentKey(), + commentResponse.key())) + .toList(); + + return commentResponse.toBuilder() + .replies(replies.stream() + .map(commentResponse1 -> buildTree(commentResponse1, commentResponses)) + .toList()) + .build(); + + } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index 6e22ed2a..56b2dcde 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -361,6 +361,7 @@ public CommunityAggregateResponse showAggregate(String communityKey, Person pers .orElseThrow( () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "community_not_found")); - return conversionService.convert(community, CommunityAggregateResponse.class); + return conversionService.convert(community.getCommunityAggregate(), + CommunityAggregateResponse.class); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java index 65b2894b..9c083aca 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java @@ -25,7 +25,6 @@ @AllArgsConstructor public class SublinksLanguageController extends AbstractSublinksApiController { - private final LanguageService languageService; private final LocalInstanceContext localInstanceContext; private final ConversionService conversionService; From b79b558d43752c14132914fc17133d2ac29bca05 Mon Sep 17 00:00:00 2001 From: rooki Date: Mon, 12 Aug 2024 11:41:18 +0200 Subject: [PATCH 098/115] Clarify comment handling in buildHierarchy method Updated the JavaDoc to specify that comments without replies or missing parents are placed at the end of the list. This documentation update ensures developers understand the behavior when organizing comments into a hierarchy. Signed-off-by: rooki --- .../sublinks/v1/comment/services/SublinksCommentService.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index 182a85d4..9323c3f2 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -355,6 +355,9 @@ public AggregateCommentResponse aggregate(String commentKey, Person person) { /** * Builds a tree structure of comment replies based on the provided list of CommentResponse * objects. + *

+ * If a comment has not been replied to or its parent is missing, it will be placed at the end of + * the list. * * @param commentResponses The list of CommentResponse objects representing the comments. * @return A list of CommentResponse objects representing the comments organized in a tree From c4007d1cb46a515b9862521bc9df33e6ba1f981e Mon Sep 17 00:00:00 2001 From: rooki Date: Mon, 12 Aug 2024 12:02:36 +0200 Subject: [PATCH 099/115] Fix incomplete comment in SublinksCommentService.java Corrected the documentation comment in the build comment tree method. This ensures the explanation for the comment reply structure is clear and complete, aiding future maintenance and understanding. Signed-off-by: rooki --- .../sublinks/v1/comment/services/SublinksCommentService.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index 9323c3f2..8bfe3bc3 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -356,8 +356,7 @@ public AggregateCommentResponse aggregate(String commentKey, Person person) { * Builds a tree structure of comment replies based on the provided list of CommentResponse * objects. *

- * If a comment has not been replied to or its parent is missing, it will be placed at the end of - * the list. + * If a comment has * * @param commentResponses The list of CommentResponse objects representing the comments. * @return A list of CommentResponse objects representing the comments organized in a tree From 96cff2eea5f0ebd5d116350bea6e33f435c165f5 Mon Sep 17 00:00:00 2001 From: rooki Date: Mon, 12 Aug 2024 14:31:52 +0200 Subject: [PATCH 100/115] Fix typos in comment structuring logic documentation. Corrected description to clarify handling of comments with no parent or missing parent in the list. This ensures better understanding of how orphaned comments are treated in the tree building process. Signed-off-by: rooki --- .../sublinks/v1/comment/services/SublinksCommentService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index 8bfe3bc3..805936dd 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -356,7 +356,8 @@ public AggregateCommentResponse aggregate(String commentKey, Person person) { * Builds a tree structure of comment replies based on the provided list of CommentResponse * objects. *

- * If a comment has + * If a comment has no parent or the parent is not found in the list of comments, it is considered + * orphans and added to the root of the tree. * * @param commentResponses The list of CommentResponse objects representing the comments. * @return A list of CommentResponse objects representing the comments organized in a tree From fa2fed4527165bc888dbfa46c8e7c785d4b1f9eb Mon Sep 17 00:00:00 2001 From: rooki Date: Mon, 12 Aug 2024 16:08:59 +0200 Subject: [PATCH 101/115] Refactor pagination logic and improve SQL query Refactored pagination handling into utility methods in `PaginationUtils` for consistency and reusability. Updated SQL query in `InstanceRepository` to correct syntax and improve domain search functionality. Enhanced readability by simplifying instance response conversion logic in `SublinksInstanceService`. Signed-off-by: rooki --- .../SublinksInstanceController.java | 4 +-- .../service/SublinksInstanceService.java | 34 +++++++++++-------- .../services/SublinksSearchService.java | 7 ++-- .../repositories/InstanceRepository.java | 2 +- .../sublinksapi/utils/PaginationUtils.java | 19 ++++++----- 5 files changed, 34 insertions(+), 32 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java index ceb85eef..8759c7c2 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java @@ -13,7 +13,6 @@ import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @AllArgsConstructor @@ -28,8 +27,7 @@ public class SublinksInstanceController extends AbstractSublinksApiController { @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public List index( - @RequestParam(required = false) final IndexInstance indexInstance) + public List index(final IndexInstance indexInstance) { return sublinksInstanceService.index(indexInstance == null ? IndexInstance.builder() diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/service/SublinksInstanceService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/service/SublinksInstanceService.java index 51e3c560..7f0b95cd 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/service/SublinksInstanceService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/service/SublinksInstanceService.java @@ -7,6 +7,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.instance.models.UpdateInstanceConfig; import com.sublinks.sublinksapi.authorization.enums.RolePermissionInstanceTypes; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; +import com.sublinks.sublinksapi.instance.entities.Instance; import com.sublinks.sublinksapi.instance.entities.InstanceConfig; import com.sublinks.sublinksapi.instance.repositories.InstanceAggregateRepository; import com.sublinks.sublinksapi.instance.repositories.InstanceConfigRepository; @@ -14,8 +15,8 @@ import com.sublinks.sublinksapi.instance.services.InstanceConfigService; import com.sublinks.sublinksapi.instance.services.InstanceService; import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.utils.PaginationUtils; import java.util.List; -import java.util.stream.Collectors; import lombok.AllArgsConstructor; import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.PageRequest; @@ -37,19 +38,26 @@ public class SublinksInstanceService { public List index(final IndexInstance indexInstance) { + PageRequest pageRequest = PageRequest.of(PaginationUtils.getPage(indexInstance.page()), + PaginationUtils.getPerPage(indexInstance.perPage())); + List instances; + if (indexInstance.search() == null) { - return instanceRepository.findAll( - PageRequest.of(indexInstance.page(), indexInstance.perPage())) - .stream() - .map(instance -> conversionService.convert(instance, InstanceResponse.class)) - .collect(Collectors.toList()); + instances = instanceRepository.findAll(pageRequest) + .getContent(); + } else { + instances = instanceRepository.findInstancesByDomainOrDescriptionOrSidebar( + indexInstance.search(), pageRequest); } - return instanceRepository.findInstancesByDomainOrDescriptionOrSidebar(indexInstance.search(), - PageRequest.of(indexInstance.page(), indexInstance.perPage())) - .stream() + return convertToInstanceResponses(instances); + } + + private List convertToInstanceResponses(List instances) { + + return instances.stream() .map(instance -> conversionService.convert(instance, InstanceResponse.class)) - .collect(Collectors.toList()); + .toList(); } public InstanceResponse show(final String key) { @@ -68,8 +76,7 @@ public InstanceConfigResponse showConfig(final String key, final Person person) rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_READ_ANNOUNCEMENT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); return conversionService.convert(instanceConfigRepository.findByInstance_Domain(key) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "instance_not_found")), @@ -81,8 +88,7 @@ public InstanceConfigResponse updateConfig(final String key, { rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_UPDATE_SETTINGS, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final InstanceConfig config = instanceConfigRepository.findByInstance_Domain(key) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "instance_not_found")); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java index 783e551f..e75912f4 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/services/SublinksSearchService.java @@ -53,9 +53,8 @@ public SearchResponse list(final Search searchForm, final Optional perso final String search = searchForm.search(); - final Integer page = searchForm.page() == null ? 1 : Math.max(searchForm.page(), 1); - final Integer perPage = searchForm.perPage() == null ? 20 : PaginationUtils.Clamp( - searchForm.perPage(), 1, 20); + final int page = PaginationUtils.getPage(searchForm.page()); + final int perPage = PaginationUtils.getPerPage(searchForm.perPage()); final SearchResponse.SearchResponseBuilder searchResponseBuilder = SearchResponse.builder(); @@ -124,8 +123,6 @@ public SearchResponse list(final Search searchForm, final Optional perso .build())); } - - return searchResponseBuilder.build(); } } diff --git a/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceRepository.java b/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceRepository.java index e33871c7..7bdd5e2e 100644 --- a/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/instance/repositories/InstanceRepository.java @@ -11,7 +11,7 @@ public interface InstanceRepository extends JpaRepository { Instance findInstanceByDomain(String domain); - @Query(value = "SELECT s FROM instances s WHERE s.search_vector @@ to_tsquery('keyword', :keyword)", + @Query(value = "SELECT s.* FROM instances s WHERE s.search_vector @@ to_tsquery('english', :keyword)", countQuery = "SELECT COUNT(s.id) FROM instances s WHERE s.search_vector @@ to_tsquery('english', :keyword)", nativeQuery = true) List findInstancesByDomainOrDescriptionOrSidebar(@Param("keyword") String keyword, Pageable pageable); diff --git a/src/main/java/com/sublinks/sublinksapi/utils/PaginationUtils.java b/src/main/java/com/sublinks/sublinksapi/utils/PaginationUtils.java index e64c806d..03c1e31c 100644 --- a/src/main/java/com/sublinks/sublinksapi/utils/PaginationUtils.java +++ b/src/main/java/com/sublinks/sublinksapi/utils/PaginationUtils.java @@ -1,6 +1,7 @@ package com.sublinks.sublinksapi.utils; import jakarta.persistence.TypedQuery; +import org.springframework.lang.NonNull; import org.springframework.lang.Nullable; public class PaginationUtils { @@ -34,29 +35,29 @@ public static void applyPagination(TypedQuery query, @Nullable Integer pa query.setMaxResults(Math.abs(size)); } - public static int Clamp(T value, T min, T max) { + public static int Clamp(@NonNull final T value, final T min, final T max) { return Math.min(Math.max(value, min), max); } - public static int getPage(T value) { + public static int getPage(final T value) { - return Clamp(value, 1, Integer.MAX_VALUE); + return Clamp(value == null ? 0 : value, 0, Integer.MAX_VALUE); } - public static int getPerPage(T value, T min, T max) { + public static int getPerPage(final T value, final T min, final T max) { - return Clamp(value, min, max); + return Clamp(value == null ? max : value, min, max); } - public static int getPerPage(T value, T max) { + public static int getPerPage(final T value, final T max) { - return getPerPage(value, 1, max); + return getPerPage(value == null ? max : value, 1, max); } - public static int getPerPage(T value) { + public static int getPerPage(final T value) { - return getPerPage(value, 1, 20); + return getPerPage(value == null ? 20 : value, 1, 20); } } From 81bc5d85d298766c47a98a5737505df1c7a152ac Mon Sep 17 00:00:00 2001 From: rooki Date: Mon, 12 Aug 2024 18:26:45 +0200 Subject: [PATCH 102/115] Standardize API tags and refactor unauthorized exception Standardized the naming of API tags across multiple controllers for clarity. Refactored exception handling to use the `orThrowUnauthorized` method, reducing redundancy in authorization checks. Signed-off-by: rooki --- .../SublinksAnnouncementController.java | 2 +- .../SublinksCommentAggerateController.java | 2 +- .../SublinksCommentController.java | 2 +- .../SublinksCommentModerationController.java | 4 +- ...ublinksCommunityAggregationController.java | 2 +- .../SublinksCommunityController.java | 8 +- ...SublinksCommunityModerationController.java | 6 +- .../SublinksInstanceAggregateController.java | 2 +- .../SublinksInstanceConfigController.java | 2 +- .../SublinksInstanceController.java | 2 +- .../SublinksLanguageController.java | 3 +- .../SublinksPersonAggregationController.java | 5 +- .../controllers/SublinksPersonController.java | 2 +- .../SublinksPersonModerationController.java | 16 ++-- .../SublinksPersonSessionController.java | 2 +- .../person/models/moderation/BanPerson.java | 4 +- .../services/SublinksPersonService.java | 2 +- .../SublinksPostAggerateController.java | 2 +- .../controllers/SublinksPostController.java | 10 +- .../SublinksPostModerationController.java | 2 +- .../v1/post/services/SublinksPostService.java | 8 +- .../SublinksPrivatemessageController.java | 13 ++- ...inksPrivatemessageModeratorController.java | 19 ++-- .../SublinksPrivateMessageService.java | 93 ++++++++++++------- .../controllers/SublinksRolesController.java | 2 +- .../roles/services/SublinksRoleService.java | 10 +- .../controllers/SublinksSearchController.java | 2 +- .../authorization/services/AclService.java | 18 ++++ src/main/resources/application.properties | 1 - 29 files changed, 146 insertions(+), 100 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java index 6465ee62..f5b2b9ea 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/controllers/SublinksAnnouncementController.java @@ -25,7 +25,7 @@ @RestController @RequestMapping("api/v1/announcement") -@Tag(name = "Announcement", description = "Announcement API") +@Tag(name = "Sublinks Announcement", description = "Announcement API") @AllArgsConstructor public class SublinksAnnouncementController extends AbstractSublinksApiController { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java index 9a485f18..33d11350 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java @@ -20,7 +20,7 @@ @RestController @AllArgsConstructor @RequestMapping("api/v1/comment/{key}/aggregate") -@Tag(name = "Comment Aggregation", description = "Comment Aggregate API") +@Tag(name = "Sublinks Comment Aggregation", description = "Comment Aggregate API") public class SublinksCommentAggerateController extends AbstractSublinksApiController { private final SublinksCommentService sublinksCommentService; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java index 6948e167..c4edb446 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentController.java @@ -26,7 +26,7 @@ @RestController @AllArgsConstructor @RequestMapping("api/v1/comment") -@Tag(name = "Comment", description = "Comment API") +@Tag(name = "Sublinks Comment", description = "Comment API") public class SublinksCommentController extends AbstractSublinksApiController { private final SublinksCommentService sublinksCommentService; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java index 42747a96..27d32d89 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java @@ -6,8 +6,8 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.PurgeComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.Moderation.RemoveComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; -import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; @@ -26,7 +26,7 @@ @RestController @AllArgsConstructor @RequestMapping("api/v1/comment/{key}/moderation") -@Tag(name = "Comment Moderation", description = "Comment Moderation API") +@Tag(name = "Sublinks Comment Moderation", description = "Comment Moderation API") public class SublinksCommentModerationController extends AbstractSublinksApiController { private final SublinksCommentService sublinksCommentService; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java index 0ff6135d..6e40bb9d 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityAggregationController.java @@ -22,7 +22,7 @@ @RestController @AllArgsConstructor @RequestMapping("/api/v1/community/{key}/aggregate") -@Tag(name = "Community Aggregation", description = "Community Aggregation API") +@Tag(name = "Sublinks Community Aggregation", description = "Community Aggregation API") public class SublinksCommunityAggregationController extends AbstractSublinksApiController { private final CommunityAggregateRepository communityAggregateRepository; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java index 7a921b80..36ecd062 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityController.java @@ -29,7 +29,7 @@ @RestController @AllArgsConstructor @RequestMapping("/api/v1/community") -@Tag(name = "Community", description = "Community API") +@Tag(name = "Sublinks Community", description = "Community API") public class SublinksCommunityController extends AbstractSublinksApiController { private final SublinksCommunityService sublinksCommunityService; @@ -93,12 +93,12 @@ public CommunityResponse update(@PathVariable final String key, @DeleteMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public CommunityResponse delete(@RequestBody final DeleteCommunity deleteCommunityForm, - @PathVariable final String key, final SublinksJwtPerson sublinksJwtPerson) + public CommunityResponse delete(@PathVariable final String key, + final DeleteCommunity deleteCommunityParam, final SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - return sublinksCommunityService.delete(key, deleteCommunityForm, person); + return sublinksCommunityService.delete(key, deleteCommunityParam, person); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java index 541b6b0c..79a95e85 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/controllers/SublinksCommunityModerationController.java @@ -39,7 +39,7 @@ @RestController @AllArgsConstructor @RequestMapping("/api/v1/community/{key}/moderation") -@Tag(name = "Community Moderation", description = "Community Moderation API") +@Tag(name = "Sublinks Community Moderation", description = "Community Moderation API") public class SublinksCommunityModerationController extends AbstractSublinksApiController { private final LinkPersonCommunityService linkPersonCommunityService; @@ -50,11 +50,11 @@ public class SublinksCommunityModerationController extends AbstractSublinksApiCo private final RolePermissionService rolePermissionService; @Operation(summary = "Remove a community") - @GetMapping("/remove") + @PostMapping("/remove") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommunityResponse remove(@PathVariable final String key, - @RequestBody @Valid RemoveCommunity removeCommunityForm, SublinksJwtPerson sublinksJwtPerson) + @RequestBody RemoveCommunity removeCommunityForm, SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceAggregateController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceAggregateController.java index 974696e1..01ad2adc 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceAggregateController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceAggregateController.java @@ -16,7 +16,7 @@ @AllArgsConstructor @RestController @RequestMapping("api/v1/instance/{key}/aggregate") -@Tag(name = "Instance Aggregate", description = "Instance Aggretate API") +@Tag(name = "Sublinks Instance Aggregate", description = "Instance Aggretate API") public class SublinksInstanceAggregateController extends AbstractSublinksApiController { private final SublinksInstanceService sublinksInstanceService; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceConfigController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceConfigController.java index 881c57b1..0baeb122 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceConfigController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceConfigController.java @@ -22,7 +22,7 @@ @AllArgsConstructor @RestController @RequestMapping("api/v1/instance/{key}/config") -@Tag(name = "Instance Config", description = "Instance Config API") +@Tag(name = "Sublinks Instance Config", description = "Instance Config API") public class SublinksInstanceConfigController extends AbstractSublinksApiController { private final SublinksInstanceService sublinksInstanceService; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java index 8759c7c2..d50cc284 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/instance/controllers/SublinksInstanceController.java @@ -18,7 +18,7 @@ @AllArgsConstructor @RestController @RequestMapping("api/v1/instance") -@Tag(name = "Instance", description = "Instance API") +@Tag(name = "Sublinks Instance", description = "Instance API") public class SublinksInstanceController extends AbstractSublinksApiController { private final SublinksInstanceService sublinksInstanceService; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java index 9c083aca..49f1fc0f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/languages/controllers/SublinksLanguageController.java @@ -4,7 +4,6 @@ import com.sublinks.sublinksapi.api.sublinks.v1.languages.models.LanguageResponse; import com.sublinks.sublinksapi.instance.models.LocalInstanceContext; import com.sublinks.sublinksapi.language.entities.Language; -import com.sublinks.sublinksapi.language.services.LanguageService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; @@ -21,7 +20,7 @@ @RestController @RequestMapping("api/v1/languages") -@Tag(name = "Languages", description = "Languages API") +@Tag(name = "Sublinks Languages", description = "Languages API") @AllArgsConstructor public class SublinksLanguageController extends AbstractSublinksApiController { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java index 432b0352..38940f4f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonAggregationController.java @@ -25,7 +25,7 @@ @RestController @RequestMapping("api/v1/person/{key}/aggregation") -@Tag(name = "Person Aggegation", description = "Person Aggregation API") +@Tag(name = "Sublinks Person Aggegation", description = "Person Aggregation API") @AllArgsConstructor public class SublinksPersonAggregationController extends AbstractSublinksApiController { @@ -47,8 +47,7 @@ public PersonAggregateResponse aggregate(@PathVariable final String key, rolePermissionService.isPermitted(person.orElse(null), RolePermissionPersonTypes.READ_PERSON_AGGREGATION, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); return conversionService.convert(sublinksPersonService.showAggregate(key, person.orElse(null)), PersonAggregateResponse.class); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java index 6ec89fe2..cf160bf3 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java @@ -30,7 +30,7 @@ @RestController @RequestMapping("api/v1/person") -@Tag(name = "Person", description = "Person API") +@Tag(name = "Sublinks Person", description = "Person API") @AllArgsConstructor public class SublinksPersonController extends AbstractSublinksApiController { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java index 6d0704ac..54882404 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonModerationController.java @@ -1,11 +1,12 @@ package com.sublinks.sublinksapi.api.sublinks.v1.person.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; -import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.moderation.BanPerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.moderation.PurgePrivateMessage; import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.services.SublinksPrivateMessageService; import com.sublinks.sublinksapi.authorization.enums.RolePermissionPrivateMessageTypes; import com.sublinks.sublinksapi.authorization.services.AclService; @@ -25,7 +26,7 @@ @RestController @RequestMapping("api/v1/person/{key}/moderation") -@Tag(name = "Person Moderation", description = "Person Moderation API") +@Tag(name = "Sublinks Person Moderation", description = "Person Moderation API") @AllArgsConstructor public class SublinksPersonModerationController extends AbstractSublinksApiController { @@ -37,7 +38,7 @@ public class SublinksPersonModerationController extends AbstractSublinksApiContr @GetMapping("/ban") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public PersonResponse ban(@RequestBody @Valid BanPerson banPersonForm, + public PersonResponse ban(@PathVariable String key, @RequestBody @Valid BanPerson banPersonForm, final SublinksJwtPerson jwtPerson) { @@ -63,15 +64,18 @@ public RequestResponse purge(@PathVariable String key) { @DeleteMapping("/purge/privatemessages") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public RequestResponse purgeAll(@PathVariable String key, final SublinksJwtPerson jwtPerson) + public RequestResponse purgeAll(@PathVariable String key, + @RequestBody final PurgePrivateMessage purgePrivateMessageForm, + final SublinksJwtPerson jwtPerson) { final Person person = getPersonOrThrowUnauthorized(jwtPerson); aclService.canPerson(person) - .performTheAction(RolePermissionPrivateMessageTypes.PURGE_PRIVATE_MESSAGES); + .performTheAction(RolePermissionPrivateMessageTypes.PURGE_PRIVATE_MESSAGES) + .orThrowUnauthorized(); - sublinksPrivateMessageService.purgeAllPrivateMessages(person); + sublinksPrivateMessageService.purgeAllPrivateMessages(key, purgePrivateMessageForm, person); return RequestResponse.builder() .success(true) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonSessionController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonSessionController.java index 200b995e..41635599 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonSessionController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonSessionController.java @@ -18,7 +18,7 @@ @RestController @RequestMapping("api/v1/session/") -@Tag(name = "Person Moderation", description = "Person Session API") +@Tag(name = "Sublinks Person Session", description = "Person Session API") @AllArgsConstructor public class SublinksPersonSessionController extends AbstractSublinksApiController { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/moderation/BanPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/moderation/BanPerson.java index da8e62f8..e235e99b 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/moderation/BanPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/models/moderation/BanPerson.java @@ -2,13 +2,15 @@ import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; +import jakarta.validation.constraints.NotNull; import lombok.Builder; import org.springframework.http.HttpStatus; import org.springframework.web.server.ResponseStatusException; @Builder public record BanPerson( - String key, + @Schema(description = "The person key", + requiredMode = RequiredMode.REQUIRED) @NotNull() String key, @Schema(description = "The reason for the ban", requiredMode = RequiredMode.NOT_REQUIRED) String reason, @Schema(description = "Ban the user", diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index 35c45083..db3361cd 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -126,7 +126,7 @@ public List index(final IndexPerson indexPerson, final Person pe aclService.canPerson(person) .performTheAction(RolePermissionPersonTypes.READ_USER) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + .orThrowUnauthorized(); final int page = PaginationUtils.getPage(indexPerson.page() == null ? 0 : indexPerson.page()) - 1; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostAggerateController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostAggerateController.java index 64cc5501..274de6e2 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostAggerateController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostAggerateController.java @@ -19,7 +19,7 @@ @RestController @AllArgsConstructor @RequestMapping("api/v1/post/{key}/aggregate") -@Tag(name = "Comment Aggregation", description = "Comment Aggregate API") +@Tag(name = "Sublinks Post Aggregation", description = "Post Aggregate API") public class SublinksPostAggerateController extends AbstractSublinksApiController { private final SublinksPostService sublinksPostService; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java index 17753131..b54ae3dd 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostController.java @@ -1,8 +1,8 @@ package com.sublinks.sublinksapi.api.sublinks.v1.post.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; -import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.CreatePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.DeletePost; import com.sublinks.sublinksapi.api.sublinks.v1.post.models.IndexPost; @@ -28,7 +28,7 @@ @RestController @RequestMapping("api/v1/post") -@Tag(name = "Post", description = "Post API") +@Tag(name = "Sublinks Post", description = "Post API") @AllArgsConstructor public class SublinksPostController extends AbstractSublinksApiController { @@ -87,13 +87,13 @@ public PostResponse update(final UpdatePost updatePostForm, @PathVariable final @DeleteMapping("/{key}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public RequestResponse delete(@PathVariable final String key, - @RequestBody final DeletePost deletePostForm, final SublinksJwtPerson sublinksJwtPerson) + public RequestResponse delete(@PathVariable final String key, final DeletePost deletePostParam, + final SublinksJwtPerson sublinksJwtPerson) { final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - sublinksPostService.delete(key, deletePostForm, person); + sublinksPostService.delete(key, deletePostParam, person); return RequestResponse.builder() .success(true) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java index 9f3cce7d..10df95f0 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/controllers/SublinksPostModerationController.java @@ -22,7 +22,7 @@ @RestController @RequestMapping("api/v1/post/{key}/moderation") -@Tag(name = "Post Moderation", description = "Post Moderation API") +@Tag(name = "Sublinks Post Moderation", description = "Post Moderation API") @AllArgsConstructor public class SublinksPostModerationController extends AbstractSublinksApiController { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java index 5b5d8777..dbde0449 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/post/services/SublinksPostService.java @@ -83,7 +83,7 @@ public List index(final IndexPost indexPostForm, final Person pers aclService.canPerson(person) .performTheAction(RolePermissionPostTypes.READ_POSTS) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + .orThrowUnauthorized(); final List communities = indexPostForm.communityKeys() == null ? null : communityRepository.findCommunityByTitleSlugIn(indexPostForm.communityKeys()); @@ -155,7 +155,7 @@ public PostResponse create(final CreatePost createPostForm, final Person person) aclService.canPerson(person) .performTheAction(RolePermissionPostTypes.CREATE_POST) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + .orThrowUnauthorized(); final Community community = communityRepository.findCommunityByTitleSlug( createPostForm.communityKey()) @@ -290,7 +290,7 @@ public PostResponse update(final String postKey, final UpdatePost updatePostForm aclService.canPerson(person) .onCommunity(community) .performTheAction(RolePermissionPostTypes.UPDATE_POST) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + .orThrowUnauthorized(); if (updatePostForm.languageKey() != null && !updatePostForm.languageKey() .isEmpty()) { @@ -587,7 +587,7 @@ public RequestResponse purge(final String postKey, final PurgePost purgePostForm aclService.canPerson(person) .onCommunity(post.getCommunity()) .performTheAction(RolePermissionPostTypes.PURGE_POST) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + .orThrowUnauthorized(); // @todo: implement diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java index cbe7e4d1..efd193d1 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageController.java @@ -14,7 +14,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import java.util.List; -import java.util.Optional; import lombok.AllArgsConstructor; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -26,7 +25,7 @@ @AllArgsConstructor @RestController @RequestMapping("api/v1/privatemessage") -@Tag(name = "Privatemessage", description = "Privatemessage API") +@Tag(name = "Sublinks Private Messages", description = "Private Messages API") public class SublinksPrivatemessageController extends AbstractSublinksApiController { private final SublinksPrivateMessageService sublinksPrivateMessageService; @@ -39,9 +38,9 @@ public List index(final IndexPrivateMessages indexPrivat final SublinksJwtPerson sublinksJwtPerson) { - final Optional person = getOptionalPerson(sublinksJwtPerson); + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - return sublinksPrivateMessageService.index(indexPrivateMessagesForm, person.orElse(null)); + return sublinksPrivateMessageService.index(indexPrivateMessagesForm, person); } @Operation(summary = "Get a specific privatemessage") @@ -52,9 +51,9 @@ public PrivateMessageResponse show(@PathVariable String key, final SublinksJwtPerson sublinksJwtPerson) { - final Optional person = getOptionalPerson(sublinksJwtPerson); + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - return sublinksPrivateMessageService.show(key, person.orElse(null)); + return sublinksPrivateMessageService.show(key, person); } @Operation(summary = "Create a new privatemessage") @@ -65,7 +64,7 @@ public PrivateMessageResponse create(final CreatePrivateMessage createPrivateMes final SublinksJwtPerson sublinksJwtPerson) { - final Person person = getPersonOrThrowBadRequest(sublinksJwtPerson); + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); return sublinksPrivateMessageService.create(createPrivateMessageForm, person); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageModeratorController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageModeratorController.java index 1ceaf38f..c17b12d6 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageModeratorController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/controllers/SublinksPrivatemessageModeratorController.java @@ -2,8 +2,8 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; -import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.DeletePrivateMessage; -import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.PrivateMessageResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.moderation.PurgePrivateMessage; import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.services.SublinksPrivateMessageService; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; @@ -19,22 +19,21 @@ @AllArgsConstructor @RestController @RequestMapping("api/v1/privatemessage/{key}/") -@Tag(name = "Privatemessage", description = "Privatemessage API") +@Tag(name = "Sublinks Private Messages Moderation", description = "Private Messages Moderation API") public class SublinksPrivatemessageModeratorController extends AbstractSublinksApiController { private final SublinksPrivateMessageService sublinksPrivateMessageService; - @Operation(summary = "Purge an privatemessage") - @DeleteMapping + @Operation(summary = "Purge an private message") + @DeleteMapping("/purge") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public PrivateMessageResponse purge(@PathVariable String key, - final DeletePrivateMessage deletePrivateMessageForm, - final SublinksJwtPerson sublinksJwtPerson) + public RequestResponse purge(@PathVariable String key, + final PurgePrivateMessage purgePrivateMessageParam, final SublinksJwtPerson sublinksJwtPerson) { - final Person person = getPersonOrThrowBadRequest(sublinksJwtPerson); + final Person person = getPersonOrThrowUnauthorized(sublinksJwtPerson); - return sublinksPrivateMessageService.delete(key, deletePrivateMessageForm, person); + return sublinksPrivateMessageService.purgePrivateMessage(key, purgePrivateMessageParam, person); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/services/SublinksPrivateMessageService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/services/SublinksPrivateMessageService.java index 0078a0f5..ccbc70c5 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/services/SublinksPrivateMessageService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/privatemessage/services/SublinksPrivateMessageService.java @@ -1,14 +1,20 @@ package com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.services; +import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonIdentity; +import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.CreatePrivateMessage; import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.DeletePrivateMessage; import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.IndexPrivateMessages; import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.MarkAsReadPrivateMessage; import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.PrivateMessageResponse; import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.UpdatePrivateMessage; +import com.sublinks.sublinksapi.api.sublinks.v1.privatemessage.models.moderation.PurgePrivateMessage; import com.sublinks.sublinksapi.authorization.enums.RolePermissionPrivateMessageTypes; import com.sublinks.sublinksapi.authorization.services.AclService; import com.sublinks.sublinksapi.person.entities.Person; +import com.sublinks.sublinksapi.person.repositories.PersonRepository; +import com.sublinks.sublinksapi.person.services.PersonService; import com.sublinks.sublinksapi.post.repositories.PostRepository; import com.sublinks.sublinksapi.privatemessages.entities.PrivateMessage; import com.sublinks.sublinksapi.privatemessages.models.PrivateMessageSearchCriteria; @@ -32,6 +38,9 @@ public class SublinksPrivateMessageService { private final PostRepository postRepository; private final AclService aclService; private final PrivateMessageService privateMessageService; + private final PersonRepository personRepository; + private final PersonService personService; + private final SublinksPersonService sublinksPersonService; /** * Retrieves a list of private messages based on the given search criteria. @@ -49,8 +58,7 @@ public List index(final IndexPrivateMessages indexPrivat aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.READ_PRIVATE_MESSAGES) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized")); + .orThrowUnauthorized(); return privateMessageRepository.allPrivateMessagesBySearchCriteria( PrivateMessageSearchCriteria.builder() @@ -81,8 +89,7 @@ public PrivateMessageResponse show(final String id, final Person person) { aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.READ_PRIVATE_MESSAGES) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized")); + .orThrowUnauthorized(); final PrivateMessage privateMessage = privateMessageRepository.findById(Long.parseLong(id)) .orElseThrow(() -> new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, @@ -112,8 +119,7 @@ public PrivateMessageResponse create(final CreatePrivateMessage createPrivateMes aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.CREATE_PRIVATE_MESSAGE) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized")); + .orThrowUnauthorized(); final PrivateMessage privateMessage = PrivateMessage.builder() .recipient(person) @@ -143,8 +149,7 @@ public PrivateMessageResponse update(final UpdatePrivateMessage updatePrivateMes aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.UPDATE_PRIVATE_MESSAGE) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized")); + .orThrowUnauthorized(); final PrivateMessage privateMessage = privateMessageRepository.findById( Long.parseLong(updatePrivateMessageForm.privateMessageKey())) @@ -180,8 +185,7 @@ public PrivateMessageResponse delete(final String id, aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.DELETE_PRIVATE_MESSAGE) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized")); + .orThrowUnauthorized(); final PrivateMessage privateMessage = privateMessageRepository.findById(Long.parseLong(id)) .orElseThrow( @@ -207,8 +211,7 @@ public void markAllAsRead(final Person person) { aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.MARK_PRIVATE_MESSAGE_AS_READ) - .orElseThrow( - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + .orThrowUnauthorized(); List privateMessages = privateMessageRepository.findByRecipientAndReadIsFalse( person); @@ -234,8 +237,7 @@ public PrivateMessageResponse markAsRead(final String id, aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.MARK_PRIVATE_MESSAGE_AS_READ) - .orElseThrow( - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + .orThrowUnauthorized(); final PrivateMessage privateMessage = privateMessageRepository.findById(Long.parseLong(id)) .orElseThrow( @@ -253,20 +255,23 @@ public PrivateMessageResponse markAsRead(final String id, } /** - * Purge a specific private message belonging to a person. + * Purges a private message. * - * @param person The person who owns the private message. - * @param id The ID of the private message to purge. - * @return The response after purging the private message or null if the person is not authorized. - * @throws ResponseStatusException If the person is not allowed to delete private messages or if - * the private message is not found. + * @param id The ID of the private message to be purged. + * @param purgePrivateMessage The purge details for the private message. + * @param person The person performing the purge. + * @return The response of the operation as a PrivateMessageResponse object, or null if the person + * is not authorized. + * @throws ResponseStatusException If the person is not authorized or if the private message is + * not found. */ - public PrivateMessageResponse purgePrivateMessage(final Person person, final String id) { + public RequestResponse purgePrivateMessage(final String id, + final PurgePrivateMessage purgePrivateMessage, final Person person) + { aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.PURGE_PRIVATE_MESSAGE) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized")); + .orThrowUnauthorized(); final PrivateMessage privateMessage = privateMessageRepository.findById(Long.parseLong(id)) .orElseThrow( @@ -280,24 +285,27 @@ public PrivateMessageResponse purgePrivateMessage(final Person person, final Str privateMessageService.deletePrivateMessage(privateMessage); - return conversionService.convert(privateMessage, PrivateMessageResponse.class); + // @todo: Modlog + + return RequestResponse.builder() + .success(true) + .build(); } /** - * Purges a list of private messages belonging to a specific person. + * Purges private messages based on the provided IDs and person performing the purge. * - * @param ids The list of IDs of the private messages to purge. - * @param person The person who owns the private messages. + * @param ids The IDs of the private messages to be purged. + * @param person The person performing the purge. * @return A list of PrivateMessageResponse objects representing the purged private messages. - * @throws ResponseStatusException If the person is not allowed to delete private messages or if - * any of the private messages specified by the IDs are not found. + * @throws ResponseStatusException If the person is not authorized or if any of the private + * messages are not found. */ public List purgePrivateMessages(List ids, final Person person) { aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.PURGE_PRIVATE_MESSAGES) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized")); + .orThrowUnauthorized(); return privateMessageRepository.findAllById(ids.stream() .map(Long::parseLong) @@ -320,8 +328,7 @@ public List purgeAllPrivateMessages(final Person person) aclService.canPerson(person) .performTheAction(RolePermissionPrivateMessageTypes.PURGE_PRIVATE_MESSAGE) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized")); + .orThrowUnauthorized(); return privateMessageService.deleteAllPrivateMessagesByPerson(person, true) .stream() @@ -329,4 +336,24 @@ public List purgeAllPrivateMessages(final Person person) PrivateMessageResponse.class)) .collect(Collectors.toList()); } + + public void purgeAllPrivateMessages(final String key, + final PurgePrivateMessage purgePrivateMessageForm, final Person person) + { + + aclService.canPerson(person) + .performTheAction(RolePermissionPrivateMessageTypes.PURGE_PRIVATE_MESSAGES) + .orThrowUnauthorized(); + + final PersonIdentity personToPurgeIdentity = sublinksPersonService.getPersonIdentifiersFromKey( + key); + + final Person personToPurge = personRepository.findOneByNameAndInstance_Domain( + personToPurgeIdentity.name(), personToPurgeIdentity.domain()) + .orElseThrow(); + + // @todo: Modlog + + privateMessageService.deleteAllPrivateMessagesByPerson(personToPurge, true); + } } \ No newline at end of file diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java index bc0caff0..9932f10f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/controllers/SublinksRolesController.java @@ -28,7 +28,7 @@ @AllArgsConstructor @RestController @RequestMapping("api/v1/roles") -@Tag(name = "Roles", description = "Roles API") +@Tag(name = "Sublinks Roles", description = "Roles API") public class SublinksRolesController extends AbstractSublinksApiController { private final SublinksRoleService sublinksRoleService; diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java index af619606..5342de74 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/roles/services/SublinksRoleService.java @@ -38,7 +38,7 @@ public List indexRole(final IndexRole indexRoleForm, final Person aclService.canPerson(person) .performTheAction(RolePermissionRoleTypes.ROLE_READ) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + .orThrowUnauthorized(); final List roles = new java.util.ArrayList<>(); @@ -72,7 +72,7 @@ public RoleResponse show(@NonNull final String key, final Person person) aclService.canPerson(person) .performTheAction(RolePermissionRoleTypes.ROLE_READ) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + .orThrowUnauthorized(); return roleRepository.findFirstByName(key) .map(role -> conversionService.convert(role, RoleResponse.class)) @@ -94,7 +94,7 @@ public RoleResponse create(@NonNull final CreateRole createRoleForm, final Perso aclService.canPerson(person) .performTheAction(RolePermissionRoleTypes.ROLE_CREATE) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + .orThrowUnauthorized(); final Role role = Role.builder() .name(createRoleForm.name()) @@ -131,7 +131,7 @@ public RoleResponse update(final String key, @NonNull final UpdateRole updateRol aclService.canPerson(person) .performTheAction(RolePermissionRoleTypes.ROLE_UPDATE) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + .orThrowUnauthorized(); final Role role = roleRepository.findFirstByName(key) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "role_not_found")); @@ -168,7 +168,7 @@ public void delete(final String key, final Person person) aclService.canPerson(person) .performTheAction(RolePermissionRoleTypes.ROLE_DELETE) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); + .orThrowUnauthorized(); final Role role = roleRepository.findFirstByName(key) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "role_not_found")); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java index 366ce43b..4999a3c5 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/search/controllers/SublinksSearchController.java @@ -20,7 +20,7 @@ @AllArgsConstructor @RestController @RequestMapping("api/v1/search") -@Tag(name = "Search", description = "Search API") +@Tag(name = "Sublinks Search", description = "Search API") public class SublinksSearchController extends AbstractSublinksApiController { private final SublinksSearchService sublinksSearchService; diff --git a/src/main/java/com/sublinks/sublinksapi/authorization/services/AclService.java b/src/main/java/com/sublinks/sublinksapi/authorization/services/AclService.java index 41e5ce61..5ab3b436 100644 --- a/src/main/java/com/sublinks/sublinksapi/authorization/services/AclService.java +++ b/src/main/java/com/sublinks/sublinksapi/authorization/services/AclService.java @@ -13,7 +13,9 @@ import java.util.List; import java.util.function.Supplier; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; +import org.springframework.web.server.ResponseStatusException; /** * The AclService class provides methods for handling Access Control List (ACL) policies. @@ -362,5 +364,21 @@ private void createAclRules() { } } } + + public void orThrowUnauthorized() { + + execute(); + if (!isPermitted) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); + } + } + + public void orThrowBadRequest() { + + execute(); + if (!isPermitted) { + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "bad_request"); + } + } } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index d22c67cd..94f349f1 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -88,4 +88,3 @@ spring.thymeleaf.check-template-location=false # enable enable_lazy_load_no_trans spring.jpa.properties.hibernate.enable_lazy_load_no_trans=true spring.datasource.hikari.allow-pool-suspension=true -SublinksRoleMapper \ No newline at end of file From 53dd65a8dbce80c00ea27fd2126182459b0c74eb Mon Sep 17 00:00:00 2001 From: rooki Date: Tue, 13 Aug 2024 19:30:17 +0200 Subject: [PATCH 103/115] Add Swagger annotations to community models Introduced Swagger annotations for `CreateCommunity`, `DeleteCommunity`, and `IndexCommunity` models. This improves API documentation by providing detailed descriptions and default values for relevant fields. These changes enhance developer understanding and API usability. Signed-off-by: rooki --- .../v1/community/models/CreateCommunity.java | 19 ++++++++++++------- .../v1/community/models/DeleteCommunity.java | 10 ++++++++-- .../v1/community/models/IndexCommunity.java | 2 ++ 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CreateCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CreateCommunity.java index 478d7fc3..0c98fc3a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CreateCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CreateCommunity.java @@ -1,12 +1,17 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + public record CreateCommunity( - String title, - String titleSlug, - String description, - String iconImageUrl, - String bannerImageUrl, - Boolean isNsfw, - Boolean isPostingRestrictedToMods) { + @Schema(description = "The title of the community") String title, + @Schema(description = "The title slug of the community") String titleSlug, + @Schema(description = "The description of the community") String description, + @Schema(description = "The icon image URL of the community", + requiredMode = RequiredMode.NOT_REQUIRED) String iconImageUrl, + @Schema(description = "The banner image URL of the community", + requiredMode = RequiredMode.NOT_REQUIRED) String bannerImageUrl, + @Schema(description = "The active status of the community") Boolean isNsfw, + @Schema(description = "The active status of the community") Boolean isPostingRestrictedToMods) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/DeleteCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/DeleteCommunity.java index 6fd7aa32..22423a5e 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/DeleteCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/DeleteCommunity.java @@ -1,7 +1,13 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + public record DeleteCommunity( - String reason, - Boolean remove) { + @Schema(description = "The reason for deleting the community", + requiredMode = RequiredMode.NOT_REQUIRED) String reason, + @Schema(description = "The boolean to remove the community", + requiredMode = RequiredMode.NOT_REQUIRED, + defaultValue = "true") Boolean remove) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java index acbb23cf..88dd3049 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/IndexCommunity.java @@ -19,10 +19,12 @@ public record IndexCommunity( requiredMode = RequiredMode.NOT_REQUIRED) SublinksListingType listingType, @Schema(description = "Show NSFW", example = "false", + defaultValue = "false", requiredMode = RequiredMode.NOT_REQUIRED) Boolean showNsfw, Integer perPage, @Schema(description = "Page", example = "1", + defaultValue = "1", requiredMode = RequiredMode.NOT_REQUIRED) Integer page) { @Override From 66d4ee9c83b15c89cf1b5478f12a597249ba1ed3 Mon Sep 17 00:00:00 2001 From: rooki Date: Wed, 14 Aug 2024 14:15:56 +0200 Subject: [PATCH 104/115] Add Swagger annotations to CommentResponse and UpdateComment Added Swagger annotations to fields in `CommentResponse` and `UpdateComment` for improved API documentation clarity. Annotations provide thorough descriptions and specify required fields, enhancing the maintainability and user comprehension of the API. Signed-off-by: rooki --- .../v1/comment/models/CommentResponse.java | 39 +++++++++++++------ .../v1/comment/models/UpdateComment.java | 4 +- 2 files changed, 29 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentResponse.java index 13a83bfa..9e52c919 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentResponse.java @@ -1,23 +1,38 @@ package com.sublinks.sublinksapi.api.sublinks.v1.comment.models; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonResponse; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import java.util.List; import lombok.Builder; @Builder(toBuilder = true) public record CommentResponse( - String key, - String activityPubId, - String body, - String path, - Boolean isLocal, - Boolean isDeleted, - Boolean isFeatured, - Boolean isRemoved, - String createdAt, - PersonResponse creator, - List replies, - String updatedAt) { + @Schema(description = "The key of the comment", + requiredMode = RequiredMode.REQUIRED) String key, + @Schema(description = "The activity pub id of the comment", + requiredMode = RequiredMode.REQUIRED) String activityPubId, + @Schema(description = "The body of the comment", + requiredMode = RequiredMode.REQUIRED) String body, + @Schema(description = "The path of the comment", + requiredMode = RequiredMode.REQUIRED) String path, + @Schema(description = "Is the comment local", + requiredMode = RequiredMode.REQUIRED) Boolean isLocal, + @Schema(description = "Is the comment deleted", + requiredMode = RequiredMode.REQUIRED) Boolean isDeleted, + @Schema(description = "Is the comment featured", + requiredMode = RequiredMode.REQUIRED) Boolean isFeatured, + @Schema(description = "Is the comment removed", + requiredMode = RequiredMode.REQUIRED) Boolean isRemoved, + + @Schema(description = "The creator of the comment", + requiredMode = RequiredMode.REQUIRED) PersonResponse creator, + @Schema(description = "The parent of the comment", + requiredMode = RequiredMode.NOT_REQUIRED) List replies, + @Schema(description = "The created at date", + requiredMode = RequiredMode.REQUIRED) String createdAt, + @Schema(description = "The updated at date", + requiredMode = RequiredMode.REQUIRED) String updatedAt) { public String getId() { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/UpdateComment.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/UpdateComment.java index 9c7da67f..05cc63a7 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/UpdateComment.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/UpdateComment.java @@ -6,13 +6,13 @@ @Builder public record UpdateComment( - String body, + @Schema(description = "The new content of the comment", + requiredMode = RequiredMode.REQUIRED) String body, @Schema(description = "The language key of the comment", defaultValue = "und", example = "und", requiredMode = RequiredMode.NOT_REQUIRED) String languageKey, @Schema(description = "Whether the comment is featured ( Requires Moderator or Admin )", - defaultValue = "false", example = "false", requiredMode = RequiredMode.NOT_REQUIRED) Boolean featured) { From cfa198a00f636d15fc5ca7e7bf7cb87be2d22969 Mon Sep 17 00:00:00 2001 From: rooki Date: Wed, 14 Aug 2024 14:47:03 +0200 Subject: [PATCH 105/115] Add Swagger annotations and rename model class Added Swagger annotations to enhance API documentation across multiple community and comment models. Renamed `AggregateCommentResponse` to `CommentAggregateResponse` for consistency and updated the relevant references in the service and controller files. Also included title update handling in the Community service logic. Signed-off-by: rooki --- .../SublinksCommentAggerateController.java | 5 +- ...nse.java => CommentAggregateResponse.java} | 2 +- .../services/SublinksCommentService.java | 10 ++-- .../models/CommunityAggregateResponse.java | 41 ++++++++++++---- .../community/models/CommunityResponse.java | 48 +++++++++++++------ .../v1/community/models/UpdateCommunity.java | 20 ++++++-- .../services/SublinksCommunityService.java | 3 ++ 7 files changed, 92 insertions(+), 37 deletions(-) rename src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/{AggregateCommentResponse.java => CommentAggregateResponse.java} (95%) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java index 33d11350..9617dc1c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java @@ -1,10 +1,9 @@ package com.sublinks.sublinksapi.api.sublinks.v1.comment.controllers; import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; -import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.AggregateCommentResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentAggregateResponse; import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; -import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -29,7 +28,7 @@ public class SublinksCommentAggerateController extends AbstractSublinksApiContro @GetMapping @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public AggregateCommentResponse aggregate(@PathVariable final String key, + public CommentAggregateResponse aggregate(@PathVariable final String key, final SublinksJwtPerson sublinksJwtPerson) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/AggregateCommentResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentAggregateResponse.java similarity index 95% rename from src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/AggregateCommentResponse.java rename to src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentAggregateResponse.java index f860141c..cf203887 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/AggregateCommentResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/models/CommentAggregateResponse.java @@ -5,7 +5,7 @@ import lombok.Builder; @Builder -public record AggregateCommentResponse( +public record CommentAggregateResponse( @Schema(description = "Search query", requiredMode = RequiredMode.NOT_REQUIRED) String commentKey, @Schema(description = "The number of upvotes", diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java index 805936dd..7b4cc646 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/services/SublinksCommentService.java @@ -1,6 +1,6 @@ package com.sublinks.sublinksapi.api.sublinks.v1.comment.services; -import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.AggregateCommentResponse; +import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentAggregateResponse; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CommentResponse; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.CreateComment; import com.sublinks.sublinksapi.api.sublinks.v1.comment.models.DeleteComment; @@ -331,21 +331,21 @@ public CommentResponse pin(String key, PinComment pinCommentForm, Person person) } /** - * Retrieves a {@link AggregateCommentResponse} based on the provided comment key and person. + * Retrieves a {@link CommentAggregateResponse} based on the provided comment key and person. * * @param commentKey The key of the comment to retrieve the aggregate for. * @param person The {@link Person} object representing the user performing the operation. - * @return A {@link AggregateCommentResponse} object representing the retrieved comment aggregate. + * @return A {@link CommentAggregateResponse} object representing the retrieved comment aggregate. * @throws ResponseStatusException If the comment is not found. */ - public AggregateCommentResponse aggregate(String commentKey, Person person) { + public CommentAggregateResponse aggregate(String commentKey, Person person) { rolePermissionService.isPermitted(person, RolePermissionCommentTypes.READ_COMMENT_AGGREGATE, () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); return commentAggregateRepository.findByComment_Path(commentKey) .map(commentAggregate -> conversionService.convert(commentAggregate, - AggregateCommentResponse.class)) + CommentAggregateResponse.class)) .orElseThrow( () -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "comment_not_found")); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregateResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregateResponse.java index 2ebb3a4e..2ff88440 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregateResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityAggregateResponse.java @@ -1,13 +1,38 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + public record CommunityAggregateResponse( - String communityKey, - Integer subscriberCount, - Integer postCount, - Integer commentCount, - Integer activeDailyUserCount, - Integer activeWeeklyUserCount, - Integer activeMonthlyUserCount, - Integer activeHalfYearUserCount) { + @Schema(description = "The key of the community", + requiredMode = RequiredMode.REQUIRED) String communityKey, + @Schema(description = "The subscriber count of the community", + requiredMode = RequiredMode.REQUIRED, + example = "0", + defaultValue = "0") Integer subscriberCount, + @Schema(description = "The post count of the community", + requiredMode = RequiredMode.REQUIRED, + example = "0", + defaultValue = "0") Integer postCount, + @Schema(description = "The comment count of the community", + requiredMode = RequiredMode.REQUIRED, + example = "0", + defaultValue = "0") Integer commentCount, + @Schema(description = "The active daily user count of the community", + requiredMode = RequiredMode.REQUIRED, + example = "0", + defaultValue = "0") Integer activeDailyUserCount, + @Schema(description = "The active weekly user count of the community", + requiredMode = RequiredMode.REQUIRED, + example = "0", + defaultValue = "0") Integer activeWeeklyUserCount, + @Schema(description = "The active monthly user count of the community", + requiredMode = RequiredMode.REQUIRED, + example = "0", + defaultValue = "0") Integer activeMonthlyUserCount, + @Schema(description = "The active quarterly user count of the community", + requiredMode = RequiredMode.REQUIRED, + example = "0", + defaultValue = "0") Integer activeHalfYearUserCount) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityResponse.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityResponse.java index cb1c036b..c2cec1f8 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityResponse.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/CommunityResponse.java @@ -1,28 +1,46 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models; import com.sublinks.sublinksapi.api.sublinks.v1.languages.models.LanguageResponse; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; import java.util.List; -import java.util.Optional; import lombok.Builder; @Builder public record CommunityResponse( - String key, - String title, - String titleSlug, + @Schema(description = "The key of the community", + requiredMode = RequiredMode.REQUIRED) String key, + @Schema(description = "The name of the community", + requiredMode = RequiredMode.REQUIRED) String title, + @Schema(description = "The slug of the community", + requiredMode = RequiredMode.REQUIRED) String titleSlug, String description, - String iconImageUrl, - Optional bannerImageUrl, - String activityPubId, - List languages, + @Schema(description = "The icon image URL of the community", + requiredMode = RequiredMode.NOT_REQUIRED) String iconImageUrl, + String bannerImageUrl, + @Schema(description = "The activityPub ID of the community", + requiredMode = RequiredMode.REQUIRED) String activityPubId, + @Schema(description = "The activityPub followers count of the community", + requiredMode = RequiredMode.NOT_REQUIRED) List languages, Boolean isLocal, - Boolean isDeleted, - Boolean isRemoved, - Boolean isNsfw, - Boolean restrictedToModerators, - String publicKey, - String createdAt, - String updatedAt + @Schema(description = "Is the community deleted", + requiredMode = RequiredMode.REQUIRED, + defaultValue = "false") Boolean isDeleted, + @Schema(description = "Is the community featured", + requiredMode = RequiredMode.REQUIRED, + defaultValue = "false") Boolean isRemoved, + @Schema(description = "Is the community nsfw", + requiredMode = RequiredMode.REQUIRED, + defaultValue = "false") Boolean isNsfw, + @Schema(description = "Is the community restricted to moderators", + requiredMode = RequiredMode.REQUIRED, + defaultValue = "false") Boolean restrictedToModerators, + @Schema(description = "Public key of the community", + requiredMode = RequiredMode.REQUIRED) String publicKey, + @Schema(description = "The creation date of the community", + requiredMode = RequiredMode.REQUIRED) String createdAt, + @Schema(description = "The update date of the community", + requiredMode = RequiredMode.REQUIRED) String updatedAt ) { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java index 81055b2d..59eefa2f 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/models/UpdateCommunity.java @@ -1,10 +1,20 @@ package com.sublinks.sublinksapi.api.sublinks.v1.community.models; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.media.Schema.RequiredMode; + public record UpdateCommunity( - String description, - String iconImageUrl, - String bannerImageUrl, - Boolean isNsfw, - Boolean isPostingRestrictedToMods) { + @Schema(description = "The title of the community", + requiredMode = RequiredMode.NOT_REQUIRED) String title, + @Schema(description = "The description of the community", + requiredMode = RequiredMode.NOT_REQUIRED) String description, + @Schema(description = "The icon image URL of the community", + requiredMode = RequiredMode.NOT_REQUIRED) String iconImageUrl, + @Schema(description = "The banner image URL of the community", + requiredMode = RequiredMode.NOT_REQUIRED) String bannerImageUrl, + @Schema(description = "The active status of the community", + requiredMode = RequiredMode.NOT_REQUIRED) Boolean isNsfw, + @Schema(description = "Is the community restricted to moderators", + requiredMode = RequiredMode.NOT_REQUIRED) Boolean isPostingRestrictedToMods) { } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java index 56b2dcde..65813b8c 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/community/services/SublinksCommunityService.java @@ -186,6 +186,9 @@ public CommunityResponse updateCommunity(String key, final UpdateCommunity updat throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); } + if (updateCommunityForm.title() != null) { + community.setTitle(updateCommunityForm.title()); + } if (updateCommunityForm.description() != null) { community.setDescription(updateCommunityForm.description()); } From 04a03be28d8a5523116c92204bfd8062c11b862d Mon Sep 17 00:00:00 2001 From: rooki Date: Mon, 19 Aug 2024 15:47:48 +0200 Subject: [PATCH 106/115] Fix indentation in SublinksCommentAggregateController Corrected the indentation in the aggregate method to improve code readability. This change ensures consistent formatting and maintains coding standards. Signed-off-by: rooki --- .../comment/controllers/SublinksCommentAggerateController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java index 9617dc1c..245f56c9 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentAggerateController.java @@ -29,7 +29,7 @@ public class SublinksCommentAggerateController extends AbstractSublinksApiContro @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) public CommentAggregateResponse aggregate(@PathVariable final String key, - final SublinksJwtPerson sublinksJwtPerson) + final SublinksJwtPerson sublinksJwtPerson) { final Optional person = getOptionalPerson(sublinksJwtPerson); From 9e0f129e2242233cb28dbe6f6c07333af0c04e4b Mon Sep 17 00:00:00 2001 From: rooki Date: Mon, 26 Aug 2024 13:36:09 +0200 Subject: [PATCH 107/115] Remove RolePermissionService from SublinksCommentModerationController Refactored the SublinksCommentModerationController by removing the unused RolePermissionService dependency. This cleanup simplifies the code and removes redundant imports, enhancing maintainability. Signed-off-by: rooki --- .../controllers/SublinksCommentModerationController.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java index 27d32d89..10f4eb6a 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/comment/controllers/SublinksCommentModerationController.java @@ -8,7 +8,6 @@ import com.sublinks.sublinksapi.api.sublinks.v1.comment.services.SublinksCommentService; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; -import com.sublinks.sublinksapi.authorization.services.RolePermissionService; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -30,7 +29,6 @@ public class SublinksCommentModerationController extends AbstractSublinksApiController { private final SublinksCommentService sublinksCommentService; - private final RolePermissionService rolePermissionService; @Operation(summary = "Remove a comment") @PostMapping("/remove") From 2dbeae8fba3da1c40f96387e6d676879c921880f Mon Sep 17 00:00:00 2001 From: rooki Date: Thu, 5 Sep 2024 17:20:52 +0200 Subject: [PATCH 108/115] Add token handling to session management Introduced token field and related methods for handling user sessions. Added endpoints for managing session invalidation and logout, enhancing session control functionalities. Signed-off-by: rooki --- .../v1/authentication/SublinksJwtPerson.java | 6 +- .../controllers/SublinksPersonController.java | 13 +++++ .../SublinksPersonSessionController.java | 58 +++++++++++++++++-- .../services/SublinksPersonService.java | 25 ++++++++ .../sublinksapi/person/entities/Person.java | 5 ++ .../repositories/UserDataRepository.java | 15 +++-- 6 files changed, 113 insertions(+), 9 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtPerson.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtPerson.java index 8fef3ab7..50577670 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtPerson.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtPerson.java @@ -2,19 +2,23 @@ import com.sublinks.sublinksapi.person.entities.Person; import java.util.Collection; +import lombok.Getter; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.GrantedAuthority; public class SublinksJwtPerson extends AbstractAuthenticationToken { private final Person person; + @Getter + private final String token; public SublinksJwtPerson(final Person person, - final Collection authorities) + final Collection authorities, final String token) { super(authorities); this.person = person; + this.token = token; setAuthenticated(true); } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java index cf160bf3..b26d54ca 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonController.java @@ -2,6 +2,7 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.CreatePerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.DeletePerson; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.IndexPerson; @@ -35,6 +36,7 @@ public class SublinksPersonController extends AbstractSublinksApiController { private final SublinksPersonService sublinksPersonService; + private final SublinksPersonSessionController sublinksPersonSessionController; @Operation(summary = "Get a list of persons") @GetMapping @@ -86,6 +88,17 @@ public LoginResponse login(final HttpServletRequest request, request.getHeader("User-Agent")); } + @Operation(summary = "Log out of a user ( Alias for invalidating the current session with /api/v1/session/invalidate )") + @PostMapping("/logout") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public RequestResponse logout(final SublinksJwtPerson principal) + { + + return sublinksPersonSessionController.invalidateCurrentSession(principal); + } + + @Operation(summary = "Update an person") @PostMapping("/{key}") @ApiResponses(value = { diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonSessionController.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonSessionController.java index 41635599..3731fc41 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonSessionController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/controllers/SublinksPersonSessionController.java @@ -2,8 +2,10 @@ import com.sublinks.sublinksapi.api.sublinks.v1.authentication.SublinksJwtPerson; import com.sublinks.sublinksapi.api.sublinks.v1.common.controllers.AbstractSublinksApiController; +import com.sublinks.sublinksapi.api.sublinks.v1.common.models.RequestResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.models.PersonSessionDataResponse; import com.sublinks.sublinksapi.api.sublinks.v1.person.services.SublinksPersonService; +import com.sublinks.sublinksapi.api.sublinks.v1.utils.PersonKeyUtils; import com.sublinks.sublinksapi.person.entities.Person; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; @@ -23,6 +25,7 @@ public class SublinksPersonSessionController extends AbstractSublinksApiController { private final SublinksPersonService sublinksPersonService; + private final PersonKeyUtils personKeyUtils; @Operation(summary = "Get metadata for a person ( requires permission to view other peoples sessions )") @GetMapping("/person/{personKey}") @@ -37,6 +40,18 @@ public PersonSessionDataResponse getMetaData(@PathVariable String personKey, return sublinksPersonService.getMetaData(personKey, person); } + @Operation(summary = "Get metadata for a person ( requires permission to view other peoples sessions )") + @GetMapping("/person") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public PersonSessionDataResponse getMetaData(final SublinksJwtPerson jwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(jwtPerson); + + return sublinksPersonService.getMetaData(personKeyUtils.getPersonKey(person), person); + } + @GetMapping("/data/{sessionKey}") @Operation(summary = "Get one metadata ( requires permission to view other peoples sessions )") @ApiResponses(value = { @@ -54,48 +69,83 @@ public PersonSessionDataResponse getOneMetaData(@PathVariable String sessionKey, @DeleteMapping("/person/invalidate/{sessionKey}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void invalidateOneMetaData(@PathVariable String sessionKey, + public RequestResponse invalidateOneMetaData(@PathVariable String sessionKey, final SublinksJwtPerson jwtPerson) { final Person person = getPersonOrThrowUnauthorized(jwtPerson); sublinksPersonService.invalidateUserData(sessionKey, person); + + return RequestResponse.builder() + .success(true) + .build(); } @Operation(summary = "Invalidate all metadata for a person ( requires permission to invalidate other peoples metadata )") @DeleteMapping("/person/{personKey}/invalidate") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void invalidateMetaData(@PathVariable String personKey, final SublinksJwtPerson jwtPerson) + public RequestResponse invalidateMetaData(@PathVariable String personKey, + final SublinksJwtPerson jwtPerson) { final Person person = getPersonOrThrowUnauthorized(jwtPerson); sublinksPersonService.invalidateAllUserData(personKey, person); + + return RequestResponse.builder() + .success(true) + .build(); } @Operation(summary = "Deletes all metadata for a person ( requires permission to delete other peoples metadata )") @DeleteMapping("/person/{personKey}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void deleteMetaData(@PathVariable String personKey, final SublinksJwtPerson jwtPerson) + public RequestResponse deleteMetaData(@PathVariable String personKey, + final SublinksJwtPerson jwtPerson) { final Person person = getPersonOrThrowUnauthorized(jwtPerson); sublinksPersonService.deleteAllUserData(personKey, person); + + return RequestResponse.builder() + .success(true) + .build(); } @Operation(summary = "Deletes one metadata for a person ( requires permission to delete other peoples metadata )") @DeleteMapping("/data/{sessionKey}") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) - public void deleteOneMetaData(@PathVariable String sessionKey, final SublinksJwtPerson jwtPerson) + public RequestResponse deleteOneMetaData(@PathVariable String sessionKey, + final SublinksJwtPerson jwtPerson) { final Person person = getPersonOrThrowUnauthorized(jwtPerson); sublinksPersonService.deleteUserData(sessionKey, person); + + return RequestResponse.builder() + .success(true) + .build(); + } + + @Operation(summary = "Invalidates your current session") + @DeleteMapping("/invalidate") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)}) + public RequestResponse invalidateCurrentSession(final SublinksJwtPerson jwtPerson) + { + + final Person person = getPersonOrThrowUnauthorized(jwtPerson); + + sublinksPersonService.invalidateUserDataByToken(jwtPerson.getToken(), person); + + return RequestResponse.builder() + .success(true) + .build(); } } diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index db3361cd..dfae37b5 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -730,4 +730,29 @@ public void deleteAllUserData(final String targetPersonKey, final Person person) } userDataRepository.deleteAll(userDataRepository.findAllByPerson(target)); } + + /** + * Invalidates the user data associated with a specific token. + * + * @param token The token associated with the user data. + * @param person The person performing the operation. + * @throws ResponseStatusException If the user data is not found or the person is not authorized + * to invalidate it. + */ + public void invalidateUserDataByToken(final String token, final Person person) { + + final PersonMetaData personMetaData = userDataRepository.findByToken(token) + .orElseThrow( + () -> new ResponseStatusException(HttpStatus.NOT_FOUND, "person_metadata_not_found")); + + if (!(rolePermissionService.isPermitted(person, + RolePermissionPersonTypes.INVALIDATE_USER_OWN_METADATA) && personMetaData.getPerson() + .equals(person)) && !rolePermissionService.isPermitted(person, + RolePermissionPersonTypes.INVALIDATE_USER_METADATA)) { + throw new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized"); + } + + userDataService.invalidate(personMetaData); + } + } diff --git a/src/main/java/com/sublinks/sublinksapi/person/entities/Person.java b/src/main/java/com/sublinks/sublinksapi/person/entities/Person.java index be17c99b..dffe6a84 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/entities/Person.java +++ b/src/main/java/com/sublinks/sublinksapi/person/entities/Person.java @@ -350,4 +350,9 @@ public final int hashCode() { .getPersistentClass() .hashCode() : getClass().hashCode(); } + + public String getKey() { + + return name + "@" + instance.getDomain(); + } } diff --git a/src/main/java/com/sublinks/sublinksapi/person/repositories/UserDataRepository.java b/src/main/java/com/sublinks/sublinksapi/person/repositories/UserDataRepository.java index 69c25a85..7c7d51a6 100644 --- a/src/main/java/com/sublinks/sublinksapi/person/repositories/UserDataRepository.java +++ b/src/main/java/com/sublinks/sublinksapi/person/repositories/UserDataRepository.java @@ -15,11 +15,10 @@ public interface UserDataRepository extends JpaRepository List findFirstByPersonAndIpAddress(Person person, String ipAddress); Optional findFirstByPersonAndIpAddressAndUserAgentAndActiveIsTrue(Person person, - String ipAddress, - String userAgent); + String ipAddress, String userAgent); - Optional findFirstByPersonAndTokenAndIpAddressAndUserAgentAndActiveIsTrue(Person person, - String token, String ipAddress, String userAgent); + Optional findFirstByPersonAndTokenAndIpAddressAndUserAgentAndActiveIsTrue( + Person person, String token, String ipAddress, String userAgent); Optional findFirstByPersonAndTokenAndIpAddressAndActiveIsTrue(Person person, String token, String ipAddress); @@ -36,4 +35,12 @@ Optional findFirstByPersonAndTokenAndIpAddressAndActiveIsTrue(Pe @Modifying @Query("update PersonMetaData u set u.active = false where u.person = :person") void updateAllByPersonSetActiveToFalse(@Param(value = "person") Person person); + + @Modifying + @Query( + "update PersonMetaData u set u.active = false where u.token = :token") + void updateAllByPersonAndIpAddressSetActiveToFalse(@Param(value = "token") String token); + + Optional findByToken(String token); } + From f0d250f30973e6f087ce9a07e83062482a3bcc4d Mon Sep 17 00:00:00 2001 From: rooki Date: Thu, 19 Sep 2024 15:50:13 +0200 Subject: [PATCH 109/115] Format code for consistency in SublinksAnnouncementService Standardized formatting in the SublinksAnnouncementService class to enhance readability and maintain consistency. Primarily adjusted spacing in conditional statements and response status exception lambdas. Signed-off-by: rooki --- .../services/SublinksAnnouncementService.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java index b8b2f41a..34ad0487 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/annoucement/services/SublinksAnnouncementService.java @@ -102,8 +102,7 @@ public AnnouncementResponse create(final CreateAnnouncement createAnnouncementFo rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_CREATE_ANNOUNCEMENT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final Announcement announcement = Announcement.builder() .content(createAnnouncementForm.content()) @@ -132,15 +131,14 @@ public AnnouncementResponse update(final Long id, final UpdateAnnouncement updat rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_UPDATE_ANNOUNCEMENT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final Announcement announcement = this.announcementRepository.getReferenceById(id); - if(updateAnnouncementForm.content() != null){ + if (updateAnnouncementForm.content() != null) { announcement.setContent(updateAnnouncementForm.content()); } - if(updateAnnouncementForm.active() != null){ + if (updateAnnouncementForm.active() != null) { announcement.setActive(updateAnnouncementForm.active()); } this.announcementRepository.save(announcement); @@ -161,8 +159,7 @@ public AnnouncementResponse remove(final Long id, final RemoveAnnouncement remov rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_DELETE_ANNOUNCEMENT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final Announcement announcement = this.announcementRepository.getReferenceById(id); @@ -184,8 +181,7 @@ public AnnouncementResponse delete(final Long id, final Person person) { rolePermissionService.isPermitted(person, RolePermissionInstanceTypes.INSTANCE_DELETE_ANNOUNCEMENT, - () -> new ResponseStatusException(HttpStatus.FORBIDDEN, - "unauthorized")); + () -> new ResponseStatusException(HttpStatus.FORBIDDEN, "unauthorized")); final Announcement announcement = this.announcementRepository.getReferenceById(id); From 1c0587a99212ed12adb4d8efebdf98dd409c3cd3 Mon Sep 17 00:00:00 2001 From: rooki Date: Sat, 12 Oct 2024 16:20:14 +0200 Subject: [PATCH 110/115] Add JWT token to SublinksJwtPerson object Included the JWT token in the SublinksJwtPerson object to ensure the token is accessible during authentication. This improvement enhances the security context by embedding the token with user authorities. Signed-off-by: rooki --- .../api/sublinks/v1/authentication/SublinksJwtFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java index 36fe2a80..864d6cf7 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java @@ -78,7 +78,7 @@ protected void doFilterInternal(final HttpServletRequest request, request.getHeader("User-Agent")); final SublinksJwtPerson authenticationToken = new SublinksJwtPerson(person.get(), person.get() - .getAuthorities()); + .getAuthorities(), token); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext() .setAuthentication(authenticationToken); From caba87b13bb678f2c3c441128035689fb4313992 Mon Sep 17 00:00:00 2001 From: rooki Date: Fri, 18 Oct 2024 10:22:03 +0200 Subject: [PATCH 111/115] LINT Signed-off-by: rooki --- .../api/lemmy/v3/admin/controllers/AdminController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/admin/controllers/AdminController.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/admin/controllers/AdminController.java index 954215d3..6a6a62f9 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/admin/controllers/AdminController.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/admin/controllers/AdminController.java @@ -153,7 +153,6 @@ GetUnreadRegistrationApplicationCountResponse registrationApplicationCount( aclService.canPerson(person) .performTheAction(RolePermissionInstanceTypes.INSTANCE_REMOVE_ADMIN) - .orElseThrow(() -> new ResponseStatusException(HttpStatus.UNAUTHORIZED, "unauthorized")); return GetUnreadRegistrationApplicationCountResponse.builder() From b855280f428282bce13fda5760c5d87756237971 Mon Sep 17 00:00:00 2001 From: rooki Date: Fri, 18 Oct 2024 11:17:16 +0200 Subject: [PATCH 112/115] Change submodule URL for 'docker' repository Updated the URL of the 'docker' submodule to use HTTPS instead of SSH, pointing to a new repository. This change affects the '.gitmodules' file to reference the new repository location. Signed-off-by: rooki --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index bd3c2b01..c82df2f5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "docker"] path = docker - url = git@github.com:sublinks/sublinks-docker.git + url = https://github.com/Pdzly/clubsall-docker.git From 8de8fcdcf4e685a57d921af88cb6014ed7cee265 Mon Sep 17 00:00:00 2001 From: rooki Date: Fri, 18 Oct 2024 11:21:26 +0200 Subject: [PATCH 113/115] Change submodule URL for 'docker' repository Updated the URL of the 'docker' submodule to use HTTPS instead of SSH, pointing to a new repository. This change affects the '.gitmodules' file to reference the new repository location. Signed-off-by: rooki --- .gitmodules | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitmodules b/.gitmodules index c82df2f5..bd3c2b01 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "docker"] path = docker - url = https://github.com/Pdzly/clubsall-docker.git + url = git@github.com:sublinks/sublinks-docker.git From 1da86ae98d33e9736d38612126c1d66eeb2e08a2 Mon Sep 17 00:00:00 2001 From: rooki Date: Fri, 18 Oct 2024 16:02:29 +0200 Subject: [PATCH 114/115] Add password encryption in SublinksPersonService The password field from createPersonForm is now included when building a new Person object. This ensures the password gets encrypted as part of the person creation process. Signed-off-by: rooki --- .../api/sublinks/v1/person/services/SublinksPersonService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java index dfae37b5..0ed81655 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/person/services/SublinksPersonService.java @@ -233,7 +233,9 @@ public LoginResponse registerPerson(final CreatePerson createPersonForm, final S } } + // Password gets encrypted inside .createPerson final Person.PersonBuilder personBuilder = Person.builder() + .password(createPersonForm.password()) .name(createPersonForm.name()) .displayName(createPersonForm.displayName()) .avatarImageUrl(createPersonForm.avatarImageUrl()) From 23f0e9209abd6d5d6f13867078547baa8cc2bd70 Mon Sep 17 00:00:00 2001 From: rooki Date: Sat, 16 Nov 2024 10:29:34 +0100 Subject: [PATCH 115/115] Handle missing tokens in JwtFilters Added checks to return "invalid_token" error if JWT validation fails in both JwtFilter and SublinksJwtFilter classes. Simplified AddAdmin model by adjusting constructor format. Signed-off-by: rooki --- .../api/lemmy/v3/admin/models/AddAdmin.java | 3 +-- .../lemmy/v3/authentication/JwtFilter.java | 19 ++++++++++++------- .../v1/authentication/SublinksJwtFilter.java | 3 ++- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/admin/models/AddAdmin.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/admin/models/AddAdmin.java index 2bc78bf8..99d3f9bd 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/admin/models/AddAdmin.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/admin/models/AddAdmin.java @@ -9,7 +9,6 @@ @SuppressWarnings("RecordComponentName") public record AddAdmin( Integer person_id, - Boolean added -) { + Boolean added) { } \ No newline at end of file diff --git a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/JwtFilter.java b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/JwtFilter.java index bf096606..8e9358f2 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/JwtFilter.java +++ b/src/main/java/com/sublinks/sublinksapi/api/lemmy/v3/authentication/JwtFilter.java @@ -39,13 +39,15 @@ public class JwtFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(final HttpServletRequest request, @NonNull final HttpServletResponse response, @NonNull final FilterChain filterChain) - throws ServletException, IOException { + throws ServletException, IOException + { String authorizingToken = request.getHeader("Authorization"); if (authorizingToken == null && request.getCookies() != null) { for (Cookie cookie : request.getCookies()) { - if (cookie.getName().equals("jwt")) { + if (cookie.getName() + .equals("jwt")) { authorizingToken = cookie.getValue(); break; } @@ -69,21 +71,24 @@ protected void doFilterInternal(final HttpServletRequest request, response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "invalid_token"); } - if (userName != null && SecurityContextHolder.getContext().getAuthentication() == null) { + if (userName != null && SecurityContextHolder.getContext() + .getAuthentication() == null) { final Optional person = personRepository.findOneByNameIgnoreCase(userName); if (person.isEmpty()) { throw new UsernameNotFoundException("Invalid name"); } if (jwtUtil.validateToken(token, person.get())) { - // Add a check if token and ip was changed? To give like a "warning" to the user that he has a new ip logged into his account userDataService.checkAndAddIpRelation(person.get(), request.getRemoteAddr(), token, request.getHeader("User-Agent")); - final JwtPerson authenticationToken = new JwtPerson(person.get(), - person.get().getAuthorities()); + final JwtPerson authenticationToken = new JwtPerson(person.get(), person.get() + .getAuthorities()); authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); - SecurityContextHolder.getContext().setAuthentication(authenticationToken); + SecurityContextHolder.getContext() + .setAuthentication(authenticationToken); + } else { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "invalid_token"); } } filterChain.doFilter(request, response); diff --git a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java index 864d6cf7..a22fce59 100644 --- a/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java +++ b/src/main/java/com/sublinks/sublinksapi/api/sublinks/v1/authentication/SublinksJwtFilter.java @@ -72,7 +72,6 @@ protected void doFilterInternal(final HttpServletRequest request, } if (sublinksJwtUtil.validateToken(token, person.get())) { - // Add a check if token and ip was changed? To give like a "warning" to the user that he has a new ip logged into his account userDataService.checkAndAddIpRelation(person.get(), request.getRemoteAddr(), token, request.getHeader("User-Agent")); @@ -82,6 +81,8 @@ protected void doFilterInternal(final HttpServletRequest request, authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext() .setAuthentication(authenticationToken); + } else { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "invalid_token"); } } filterChain.doFilter(request, response);