package org.example.realworldapi.infrastructure.web.resource; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.example.realworldapi.application.data.ArticleData; import org.example.realworldapi.application.data.ArticlesData; import org.example.realworldapi.application.data.CommentData; import org.example.realworldapi.domain.model.constants.ValidationMessages; import org.example.realworldapi.domain.service.ArticlesService; import org.example.realworldapi.infrastructure.web.model.request.NewArticleRequest; import org.example.realworldapi.infrastructure.web.model.request.NewCommentRequest; import org.example.realworldapi.infrastructure.web.model.request.UpdateArticleRequest; import org.example.realworldapi.infrastructure.web.model.response.ArticleResponse; import org.example.realworldapi.infrastructure.web.model.response.ArticlesResponse; import org.example.realworldapi.infrastructure.web.model.response.CommentResponse; import org.example.realworldapi.infrastructure.web.model.response.CommentsResponse; import org.example.realworldapi.infrastructure.web.qualifiers.NoWrapRootValueObjectMapper; import org.example.realworldapi.infrastructure.web.security.annotation.Secured; import org.example.realworldapi.infrastructure.web.security.profile.Role; import javax.validation.Valid; import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.ws.rs.*; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.SecurityContext; import java.security.Principal; import java.util.List; @Path("/articles") public class ArticlesResource { private ArticlesService articlesService; private ObjectMapper objectMapper; public ArticlesResource( ArticlesService articlesService, @NoWrapRootValueObjectMapper ObjectMapper objectMapper) { this.articlesService = articlesService; this.objectMapper = objectMapper; } @GET @Path("/feed") @Secured({Role.USER, Role.ADMIN}) @Produces(MediaType.APPLICATION_JSON) public Response feed( @QueryParam("offset") int offset, @QueryParam("limit") int limit, @Context SecurityContext securityContext) throws JsonProcessingException { Long loggedUserId = getLoggedUserId(securityContext); ArticlesData result = articlesService.findRecentArticles(loggedUserId, offset, limit); return Response.ok(objectMapper.writeValueAsString(new ArticlesResponse(result))) .status(Response.Status.OK) .build(); } @GET @Produces(MediaType.APPLICATION_JSON) @Secured(optional = true) public Response getArticles( @QueryParam("offset") int offset, @QueryParam("limit") int limit, @QueryParam("tag") List<String> tags, @QueryParam("author") List<String> authors, @QueryParam("favorited") List<String> favorited, @Context SecurityContext securityContext) throws JsonProcessingException { Long loggedUserId = getLoggedUserId(securityContext); ArticlesData result = articlesService.findArticles(offset, limit, loggedUserId, tags, authors, favorited); return Response.ok(objectMapper.writeValueAsString(new ArticlesResponse(result))) .status(Response.Status.OK) .build(); } @POST @Secured({Role.ADMIN, Role.USER}) @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response create( @Valid @NotNull(message = ValidationMessages.REQUEST_BODY_MUST_BE_NOT_NULL) NewArticleRequest newArticleRequest, @Context SecurityContext securityContext) { Long loggedUserId = getLoggedUserId(securityContext); ArticleData newArticleData = articlesService.create( newArticleRequest.getTitle(), newArticleRequest.getDescription(), newArticleRequest.getBody(), newArticleRequest.getTagList(), loggedUserId); return Response.ok(new ArticleResponse(newArticleData)).status(Response.Status.CREATED).build(); } @GET @Path("/{slug}") @Produces(MediaType.APPLICATION_JSON) public Response findBySlug( @PathParam("slug") @NotBlank(message = ValidationMessages.SLUG_MUST_BE_NOT_BLANK) String slug) { ArticleData articleData = articlesService.findBySlug(slug); return Response.ok(new ArticleResponse(articleData)).status(Response.Status.OK).build(); } @PUT @Path("/{slug}") @Secured({Role.ADMIN, Role.USER}) @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response update( @PathParam("slug") @NotBlank String slug, @Valid @NotNull UpdateArticleRequest updateArticleRequest, @Context SecurityContext securityContext) { Long loggedUserId = getLoggedUserId(securityContext); ArticleData updatedArticleData = articlesService.update( slug, updateArticleRequest.getTitle(), updateArticleRequest.getDescription(), updateArticleRequest.getBody(), loggedUserId); return Response.ok(new ArticleResponse(updatedArticleData)).status(Response.Status.OK).build(); } @DELETE @Path("/{slug}") @Secured({Role.ADMIN, Role.USER}) @Produces(MediaType.APPLICATION_JSON) public Response delete( @PathParam("slug") @NotBlank(message = ValidationMessages.SLUG_MUST_BE_NOT_BLANK) String slug, @Context SecurityContext securityContext) { Long loggedUserId = getLoggedUserId(securityContext); articlesService.delete(slug, loggedUserId); return Response.ok().build(); } @GET @Path("/{slug}/comments") @Secured(optional = true) @Produces(MediaType.APPLICATION_JSON) public Response getCommentsBySlug( @PathParam("slug") @NotBlank(message = ValidationMessages.SLUG_MUST_BE_NOT_BLANK) String slug, @Context SecurityContext securityContext) throws JsonProcessingException { Long loggedUserId = getLoggedUserId(securityContext); List<CommentData> comments = articlesService.findCommentsBySlug(slug, loggedUserId); return Response.ok(objectMapper.writeValueAsString(new CommentsResponse(comments))) .status(Response.Status.OK) .build(); } @POST @Path("/{slug}/comments") @Secured({Role.ADMIN, Role.USER}) @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public Response createComment( @PathParam("slug") @NotBlank(message = ValidationMessages.SLUG_MUST_BE_NOT_BLANK) String slug, @Valid NewCommentRequest newCommentRequest, @Context SecurityContext securityContext) { Long loggedUserId = getLoggedUserId(securityContext); CommentData commentData = articlesService.createComment(slug, newCommentRequest.getBody(), loggedUserId); return Response.ok(new CommentResponse(commentData)).status(Response.Status.OK).build(); } @DELETE @Path("/{slug}/comments/{id}") @Secured({Role.ADMIN, Role.USER}) @Produces(MediaType.APPLICATION_JSON) public Response deleteComment( @PathParam("slug") @NotBlank(message = ValidationMessages.SLUG_MUST_BE_NOT_BLANK) String slug, @PathParam("id") @NotNull(message = ValidationMessages.COMMENT_ID_MUST_BE_NOT_NULL) Long id, @Context SecurityContext securityContext) { Long loggedUserId = getLoggedUserId(securityContext); articlesService.deleteComment(slug, id, loggedUserId); return Response.ok().build(); } @POST @Path("/{slug}/favorite") @Secured({Role.ADMIN, Role.USER}) @Produces(MediaType.APPLICATION_JSON) public Response favoriteArticle( @PathParam("slug") @NotBlank(message = ValidationMessages.SLUG_MUST_BE_NOT_BLANK) String slug, @Context SecurityContext securityContext) { Long loggedUserId = getLoggedUserId(securityContext); ArticleData articleData = articlesService.favoriteArticle(slug, loggedUserId); return Response.ok(new ArticleResponse(articleData)).status(Response.Status.OK).build(); } @DELETE @Path("/{slug}/favorite") @Secured({Role.ADMIN, Role.USER}) @Produces(MediaType.APPLICATION_JSON) public Response unfavoriteArticle( @PathParam("slug") @NotBlank(message = ValidationMessages.SLUG_MUST_BE_NOT_BLANK) String slug, @Context SecurityContext securityContext) { Long loggedUserId = getLoggedUserId(securityContext); ArticleData articleData = articlesService.unfavoriteArticle(slug, loggedUserId); return Response.ok(new ArticleResponse(articleData)).status(Response.Status.OK).build(); } private Long getLoggedUserId(SecurityContext securityContext) { Principal principal = securityContext.getUserPrincipal(); return principal != null ? Long.valueOf(principal.getName()) : null; } }