From f96be23d3a1551afb061d8e6dc3ad8f544dadb04 Mon Sep 17 00:00:00 2001 From: Bohdan Koval Date: Sun, 28 Apr 2024 18:01:02 +0300 Subject: [PATCH 01/11] adding message endpoints --- .../message/controller/MessageController.java | 107 ++++++++++++++++++ .../message/dto/MessageRequestDto.java | 25 ++++ .../message/dto/MessageResponseDto.java | 26 +++++ .../message/dto/MessageUpdateDto.java | 16 +++ .../org/petmarket/message/entity/Message.java | 64 +++++++++++ .../message/entity/MessageStatus.java | 5 + .../message/mapper/MessageMapper.java | 18 +++ .../message/repository/MessageRepository.java | 16 +++ .../service/MessageAccessCheckerService.java | 80 +++++++++++++ .../message/service/MessageService.java | 90 +++++++++++++++ .../V2024.04.24.001__add_message.sql | 11 ++ 11 files changed, 458 insertions(+) create mode 100644 src/main/java/org/petmarket/message/controller/MessageController.java create mode 100644 src/main/java/org/petmarket/message/dto/MessageRequestDto.java create mode 100644 src/main/java/org/petmarket/message/dto/MessageResponseDto.java create mode 100644 src/main/java/org/petmarket/message/dto/MessageUpdateDto.java create mode 100644 src/main/java/org/petmarket/message/entity/Message.java create mode 100644 src/main/java/org/petmarket/message/entity/MessageStatus.java create mode 100644 src/main/java/org/petmarket/message/mapper/MessageMapper.java create mode 100644 src/main/java/org/petmarket/message/repository/MessageRepository.java create mode 100644 src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java create mode 100644 src/main/java/org/petmarket/message/service/MessageService.java create mode 100644 src/main/resources/db/migration/V2024.04.24.001__add_message.sql diff --git a/src/main/java/org/petmarket/message/controller/MessageController.java b/src/main/java/org/petmarket/message/controller/MessageController.java new file mode 100644 index 00000000..411af8b9 --- /dev/null +++ b/src/main/java/org/petmarket/message/controller/MessageController.java @@ -0,0 +1,107 @@ +package org.petmarket.message.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import org.petmarket.message.dto.MessageRequestDto; +import org.petmarket.message.dto.MessageResponseDto; +import org.petmarket.message.dto.MessageUpdateDto; +import org.petmarket.message.mapper.MessageMapper; +import org.petmarket.message.service.MessageAccessCheckerService; +import org.petmarket.message.service.MessageService; +import org.petmarket.utils.annotations.parametrs.ParameterPageNumber; +import org.petmarket.utils.annotations.parametrs.ParameterPageSize; +import org.petmarket.utils.annotations.responses.ApiResponseBadRequest; +import org.petmarket.utils.annotations.responses.ApiResponseForbidden; +import org.petmarket.utils.annotations.responses.ApiResponseSuccessful; +import org.petmarket.utils.annotations.responses.ApiResponseUnauthorized; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "Message") +@RestController +@RequiredArgsConstructor +@Validated +@RequestMapping(value = "/v1/messages") +public class MessageController { + private final MessageService messageService; + private final MessageAccessCheckerService messageAccessCheckerService; + private final MessageMapper messageMapper; + + @Operation(summary = "Create new message") + @PostMapping + @ApiResponseSuccessful + @ApiResponseBadRequest + @ApiResponseUnauthorized + @ApiResponseForbidden + @PreAuthorize("isAuthenticated()") + public void addMessage(@RequestBody MessageRequestDto messageRequestDto) { + messageAccessCheckerService.checkCreateAccess(messageRequestDto); + messageService.addMessage(messageRequestDto); + } + + @Operation(summary = "Delete message by id") + @DeleteMapping("/{id}") + @ApiResponseSuccessful + @ApiResponseUnauthorized + @ApiResponseForbidden + @PreAuthorize("isAuthenticated()") + public void deleteMessage(@PathVariable Long id) { + messageAccessCheckerService.checkDeleteAccess(id); + messageService.deleteMessage(id); + } + + @Operation(summary = "Update message by id") + @PutMapping("/{id}") + @ApiResponseSuccessful + @ApiResponseBadRequest + @ApiResponseUnauthorized + @ApiResponseForbidden + @PreAuthorize("isAuthenticated()") + public void updateMessage(@PathVariable Long id, @RequestBody MessageUpdateDto messageUpdateDto) { + messageAccessCheckerService.checkUpdateAccess(List.of(messageService.getMessageById(id))); + messageService.updateMessage(id, messageUpdateDto); + } + + @Operation(summary = "Get user sent messages") + @GetMapping("/sent") + @ApiResponseSuccessful + @ApiResponseUnauthorized + @PreAuthorize("isAuthenticated()") + public Page getSentMessages( + @ParameterPageNumber @RequestParam(defaultValue = "1") @Positive int page, + @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size) { + return messageService.getSentMessagesByUserId(PageRequest.of(page - 1, size)); + } + + @Operation(summary = "Get user received messages") + @GetMapping("/received") + @ApiResponseSuccessful + @ApiResponseUnauthorized + @PreAuthorize("isAuthenticated()") + public Page getReceivedMessages( + @ParameterPageNumber @RequestParam(defaultValue = "1") @Positive int page, + @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size) { + return messageService.getReceivedMessagesByUserId(PageRequest.of(page - 1, size)); + } + + @Operation(summary = "Get user messages") + @GetMapping + @ApiResponseSuccessful + @ApiResponseUnauthorized + @PreAuthorize("isAuthenticated()") + public Page getMessages( + @ParameterPageNumber @RequestParam(defaultValue = "1") @Positive int page, + @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size) { + return messageService.getMessagesByUserId(PageRequest.of(page - 1, size)); + } + + + //TODO: add mark as read +} diff --git a/src/main/java/org/petmarket/message/dto/MessageRequestDto.java b/src/main/java/org/petmarket/message/dto/MessageRequestDto.java new file mode 100644 index 00000000..f614c0cd --- /dev/null +++ b/src/main/java/org/petmarket/message/dto/MessageRequestDto.java @@ -0,0 +1,25 @@ +package org.petmarket.message.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.*; + +@Setter +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class MessageRequestDto { + @NotBlank(message = "The 'text' cannot be empty") + @Schema(example = "Hello, how are you?") + private String text; + + @Schema(example = "1") + @JsonProperty("author_id") + private Long authorId; + + @Schema(example = "1") + @JsonProperty("recipient_id") + private Long recipientId; +} diff --git a/src/main/java/org/petmarket/message/dto/MessageResponseDto.java b/src/main/java/org/petmarket/message/dto/MessageResponseDto.java new file mode 100644 index 00000000..069c3615 --- /dev/null +++ b/src/main/java/org/petmarket/message/dto/MessageResponseDto.java @@ -0,0 +1,26 @@ +package org.petmarket.message.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.*; +import org.petmarket.message.entity.MessageStatus; +import org.petmarket.users.dto.UserResponseDto; + +import java.time.LocalDateTime; + +@Setter +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class MessageResponseDto { + private Long id; + private String text; + private UserResponseDto author; + private UserResponseDto recipient; + @JsonProperty("message_status") + private MessageStatus messageStatus; + private LocalDateTime created; + private LocalDateTime updated; + private boolean sender; + private boolean edited; +} diff --git a/src/main/java/org/petmarket/message/dto/MessageUpdateDto.java b/src/main/java/org/petmarket/message/dto/MessageUpdateDto.java new file mode 100644 index 00000000..91d1c611 --- /dev/null +++ b/src/main/java/org/petmarket/message/dto/MessageUpdateDto.java @@ -0,0 +1,16 @@ +package org.petmarket.message.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import lombok.*; + +@Setter +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class MessageUpdateDto { + @NotBlank(message = "The 'text' cannot be empty") + @Schema(example = "Hello, how are you?") + private String text; +} diff --git a/src/main/java/org/petmarket/message/entity/Message.java b/src/main/java/org/petmarket/message/entity/Message.java new file mode 100644 index 00000000..f213cc2a --- /dev/null +++ b/src/main/java/org/petmarket/message/entity/Message.java @@ -0,0 +1,64 @@ +package org.petmarket.message.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +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 lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.petmarket.users.entity.User; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Entity +@Table(name = "message") +@EqualsAndHashCode(of = {"id"}) +@Getter +@Setter +@Builder +@NoArgsConstructor +@AllArgsConstructor +@EntityListeners(AuditingEntityListener.class) +public class Message { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @Column(name = "text", nullable = false) + private String text; + + @Column(name = "message_status", nullable = false) + @Enumerated(EnumType.STRING) + private MessageStatus status; + + @CreatedDate + @Column(name = "created") + private LocalDateTime created; + + @LastModifiedDate + @Column(name = "updated") + private LocalDateTime updated; + + @ManyToOne + @JoinColumn(name = "author_id") + private User author; + + @ManyToOne + @JoinColumn(name = "recipient_id") + private User recipient; +} diff --git a/src/main/java/org/petmarket/message/entity/MessageStatus.java b/src/main/java/org/petmarket/message/entity/MessageStatus.java new file mode 100644 index 00000000..0db2c8cb --- /dev/null +++ b/src/main/java/org/petmarket/message/entity/MessageStatus.java @@ -0,0 +1,5 @@ +package org.petmarket.message.entity; + +public enum MessageStatus { + UNREAD, READ +} diff --git a/src/main/java/org/petmarket/message/mapper/MessageMapper.java b/src/main/java/org/petmarket/message/mapper/MessageMapper.java new file mode 100644 index 00000000..5bc18489 --- /dev/null +++ b/src/main/java/org/petmarket/message/mapper/MessageMapper.java @@ -0,0 +1,18 @@ +package org.petmarket.message.mapper; + +import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.petmarket.config.MapperConfig; +import org.petmarket.message.dto.MessageResponseDto; +import org.petmarket.message.entity.Message; +import org.petmarket.message.dto.MessageRequestDto; +import org.petmarket.users.mapper.UserMapper; + +@Mapper(config = MapperConfig.class, uses = {UserMapper.class}) +public interface MessageMapper { + @Mapping(target = "author.id", source = "authorId") + @Mapping(target = "recipient.id", source = "recipientId") + Message messageRequestDtoToMessage(MessageRequestDto messageRequestDto); + + MessageResponseDto messageToMessageResponseDto(Message message); +} diff --git a/src/main/java/org/petmarket/message/repository/MessageRepository.java b/src/main/java/org/petmarket/message/repository/MessageRepository.java new file mode 100644 index 00000000..33250709 --- /dev/null +++ b/src/main/java/org/petmarket/message/repository/MessageRepository.java @@ -0,0 +1,16 @@ +package org.petmarket.message.repository; + +import org.petmarket.message.entity.Message; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface MessageRepository extends JpaRepository { + Page findByAuthorIdOrRecipientId(Long authorId, Long recipientId, Pageable pageable); + + Page findByAuthorId(Long authorId, Pageable pageable); + + Page findByRecipientId(Long recipientId, Pageable pageable); +} diff --git a/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java b/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java new file mode 100644 index 00000000..29f20682 --- /dev/null +++ b/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java @@ -0,0 +1,80 @@ +package org.petmarket.message.service; + +import lombok.RequiredArgsConstructor; +import org.petmarket.errorhandling.AccessDeniedException; +import org.petmarket.message.entity.Message; +import org.petmarket.message.dto.MessageRequestDto; +import org.petmarket.users.entity.User; +import org.petmarket.users.service.UserService; +import org.springframework.stereotype.Service; + +import java.util.List; + +@RequiredArgsConstructor +@Service +public class MessageAccessCheckerService { + private final UserService userService; + private final MessageService messageService; + + public void checkCreateAccess(MessageRequestDto message) { + if (!userService.isCurrentUserAdmin()) { + User user = userService.getCurrentUser(); + + if (!message.getAuthorId().equals(user.getId())) { + throw new AccessDeniedException("Access denied to create message"); + } + } + } + + public void checkUpdateAccess(List messages) { + if (!userService.isCurrentUserAdmin()) { + User user = userService.getCurrentUser(); + + for (Message message : messages) { + if (!message.getAuthor().equals(user)) { + throw new AccessDeniedException(String + .format("Access denied to update message with id %s", message.getId())); + } + } + } + } + + public void checkViewAccess(List messages) { + if (!userService.isCurrentUserAdmin()) { + User user = userService.getCurrentUser(); + + for (Message message : messages) { + if (!message.getAuthor().equals(user) && !message.getRecipient().equals(user)) { + throw new AccessDeniedException(String + .format("Access denied to view message with id %s", message.getId())); + } + } + } + } + + public void checkReadAccess(Long messageId) { + Message message = messageService.getMessageById(messageId); + + if (!userService.isCurrentUserAdmin()) { + User user = userService.getCurrentUser(); + + if (!message.getRecipient().equals(user)) { + throw new AccessDeniedException(String + .format("Access denied to read message with id %s", message.getId())); + } + } + } + + public void checkDeleteAccess(Long messageId) { + Message message = messageService.getMessageById(messageId); + + if (!userService.isCurrentUserAdmin()) { + User user = userService.getCurrentUser(); + + if (!message.getAuthor().equals(user)) { + throw new AccessDeniedException(String + .format("Access denied to delete message with id %s", message.getId())); + } + } + } +} diff --git a/src/main/java/org/petmarket/message/service/MessageService.java b/src/main/java/org/petmarket/message/service/MessageService.java new file mode 100644 index 00000000..d9a86678 --- /dev/null +++ b/src/main/java/org/petmarket/message/service/MessageService.java @@ -0,0 +1,90 @@ +package org.petmarket.message.service; + +import lombok.RequiredArgsConstructor; +import org.petmarket.errorhandling.ItemNotFoundException; +import org.petmarket.message.dto.MessageResponseDto; +import org.petmarket.message.dto.MessageUpdateDto; +import org.petmarket.message.entity.Message; +import org.petmarket.message.dto.MessageRequestDto; +import org.petmarket.message.entity.MessageStatus; +import org.petmarket.message.mapper.MessageMapper; +import org.petmarket.message.repository.MessageRepository; +import org.petmarket.users.service.UserService; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class MessageService { + private final MessageRepository messageRepository; + private final MessageMapper messageMapper; + private final UserService userService; + + public void addMessage(MessageRequestDto messageRequestDto) { + Message message = messageMapper.messageRequestDtoToMessage(messageRequestDto); + message.setStatus(MessageStatus.UNREAD); + messageRepository.save(message); + } + + public void updateMessage(Long id, MessageUpdateDto messageUpdateDto) { + Message message = getMessageById(id); + message.setText(messageUpdateDto.getText()); + messageRepository.save(message); + } + + public void markAsRead(Long id) { + Message message = getMessageById(id); + message.setStatus(MessageStatus.READ); + messageRepository.save(message); + } + + public Page getMessagesByUserId(Pageable pageable) { + Long userId = userService.getCurrentUser().getId(); + + return new PageImpl<>(messageRepository.findByAuthorIdOrRecipientId(userId, userId, pageable) + .map(messageMapper::messageToMessageResponseDto) + .stream().peek(messageResponseDto -> { + if (messageResponseDto.getAuthor().getId() == userId) { + messageResponseDto.setSender(true); + } + + if (!messageResponseDto.getCreated().equals(messageResponseDto.getUpdated())) { + messageResponseDto.setEdited(true); + } + }).toList()); + } + + public Page getSentMessagesByUserId(Pageable pageable) { + return new PageImpl<>(messageRepository.findByAuthorId(userService.getCurrentUser().getId(), pageable) + .map(messageMapper::messageToMessageResponseDto) + .stream().peek(messageResponseDto -> { + messageResponseDto.setSender(true); + + if (!messageResponseDto.getCreated().equals(messageResponseDto.getUpdated())) { + messageResponseDto.setEdited(true); + } + }).toList()); + } + + public Page getReceivedMessagesByUserId(Pageable pageable) { + return new PageImpl<>(messageRepository.findByRecipientId(userService.getCurrentUser().getId(), pageable) + .map(messageMapper::messageToMessageResponseDto) + .stream().peek(messageResponseDto -> { + if (!messageResponseDto.getCreated().equals(messageResponseDto.getUpdated())) { + messageResponseDto.setEdited(true); + } + }).toList()); + } + + public void deleteMessage(Long id) { + getMessageById(id); + messageRepository.deleteById(id); + } + + public Message getMessageById(Long id) { + return messageRepository.findById(id) + .orElseThrow(() -> new ItemNotFoundException(String.format("Message with id %s not found", id))); + } +} diff --git a/src/main/resources/db/migration/V2024.04.24.001__add_message.sql b/src/main/resources/db/migration/V2024.04.24.001__add_message.sql new file mode 100644 index 00000000..34131d71 --- /dev/null +++ b/src/main/resources/db/migration/V2024.04.24.001__add_message.sql @@ -0,0 +1,11 @@ +CREATE TABLE message ( + id BIGINT PRIMARY KEY AUTO_INCREMENT, + text TEXT NOT NULL, + message_status VARCHAR(255) NOT NULL DEFAULT 'UNREAD', + created TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + author_id BIGINT NOT NULL, + recipient_id BIGINT NOT NULL, + FOREIGN KEY (author_id) REFERENCES users (id), + FOREIGN KEY (recipient_id) REFERENCES users (id) +); From 5a1887b41bd4eb0cb79d57b152a86df250de12e8 Mon Sep 17 00:00:00 2001 From: Bohdan Koval Date: Sun, 28 Apr 2024 19:18:03 +0300 Subject: [PATCH 02/11] added read message endpoint and sorted retrieved messages by creation time --- .../message/controller/MessageController.java | 28 +++++++++++-------- .../service/MessageAccessCheckerService.java | 13 --------- .../message/service/MessageService.java | 14 ++++++---- 3 files changed, 25 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/petmarket/message/controller/MessageController.java b/src/main/java/org/petmarket/message/controller/MessageController.java index 411af8b9..d2bbff9b 100644 --- a/src/main/java/org/petmarket/message/controller/MessageController.java +++ b/src/main/java/org/petmarket/message/controller/MessageController.java @@ -7,15 +7,11 @@ import org.petmarket.message.dto.MessageRequestDto; import org.petmarket.message.dto.MessageResponseDto; import org.petmarket.message.dto.MessageUpdateDto; -import org.petmarket.message.mapper.MessageMapper; import org.petmarket.message.service.MessageAccessCheckerService; import org.petmarket.message.service.MessageService; import org.petmarket.utils.annotations.parametrs.ParameterPageNumber; import org.petmarket.utils.annotations.parametrs.ParameterPageSize; -import org.petmarket.utils.annotations.responses.ApiResponseBadRequest; -import org.petmarket.utils.annotations.responses.ApiResponseForbidden; -import org.petmarket.utils.annotations.responses.ApiResponseSuccessful; -import org.petmarket.utils.annotations.responses.ApiResponseUnauthorized; +import org.petmarket.utils.annotations.responses.*; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.security.access.prepost.PreAuthorize; @@ -32,7 +28,6 @@ public class MessageController { private final MessageService messageService; private final MessageAccessCheckerService messageAccessCheckerService; - private final MessageMapper messageMapper; @Operation(summary = "Create new message") @PostMapping @@ -46,11 +41,14 @@ public void addMessage(@RequestBody MessageRequestDto messageRequestDto) { messageService.addMessage(messageRequestDto); } + //TODO: add delete received messages, sent messages, and messages by user id + @Operation(summary = "Delete message by id") @DeleteMapping("/{id}") @ApiResponseSuccessful @ApiResponseUnauthorized @ApiResponseForbidden + @ApiResponseNotFound @PreAuthorize("isAuthenticated()") public void deleteMessage(@PathVariable Long id) { messageAccessCheckerService.checkDeleteAccess(id); @@ -77,7 +75,7 @@ public void updateMessage(@PathVariable Long id, @RequestBody MessageUpdateDto m public Page getSentMessages( @ParameterPageNumber @RequestParam(defaultValue = "1") @Positive int page, @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size) { - return messageService.getSentMessagesByUserId(PageRequest.of(page - 1, size)); + return messageService.getSentUserMessages(PageRequest.of(page - 1, size)); } @Operation(summary = "Get user received messages") @@ -88,7 +86,7 @@ public Page getSentMessages( public Page getReceivedMessages( @ParameterPageNumber @RequestParam(defaultValue = "1") @Positive int page, @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size) { - return messageService.getReceivedMessagesByUserId(PageRequest.of(page - 1, size)); + return messageService.getReceivedUserMessages(PageRequest.of(page - 1, size)); } @Operation(summary = "Get user messages") @@ -99,9 +97,17 @@ public Page getReceivedMessages( public Page getMessages( @ParameterPageNumber @RequestParam(defaultValue = "1") @Positive int page, @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size) { - return messageService.getMessagesByUserId(PageRequest.of(page - 1, size)); + return messageService.getUserMessages(PageRequest.of(page - 1, size)); } - - //TODO: add mark as read + @Operation(summary = "Mark message as read") + @PutMapping("/{id}/read") + @ApiResponseSuccessful + @ApiResponseUnauthorized + @ApiResponseForbidden + @PreAuthorize("isAuthenticated()") + public void markAsRead(@PathVariable Long id) { + messageAccessCheckerService.checkReadAccess(id); + messageService.markAsRead(id); + } } diff --git a/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java b/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java index 29f20682..d6b48616 100644 --- a/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java +++ b/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java @@ -39,19 +39,6 @@ public void checkUpdateAccess(List messages) { } } - public void checkViewAccess(List messages) { - if (!userService.isCurrentUserAdmin()) { - User user = userService.getCurrentUser(); - - for (Message message : messages) { - if (!message.getAuthor().equals(user) && !message.getRecipient().equals(user)) { - throw new AccessDeniedException(String - .format("Access denied to view message with id %s", message.getId())); - } - } - } - } - public void checkReadAccess(Long messageId) { Message message = messageService.getMessageById(messageId); diff --git a/src/main/java/org/petmarket/message/service/MessageService.java b/src/main/java/org/petmarket/message/service/MessageService.java index d9a86678..4d4c15c6 100644 --- a/src/main/java/org/petmarket/message/service/MessageService.java +++ b/src/main/java/org/petmarket/message/service/MessageService.java @@ -15,6 +15,8 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; +import java.util.Comparator; + @Service @RequiredArgsConstructor public class MessageService { @@ -40,7 +42,7 @@ public void markAsRead(Long id) { messageRepository.save(message); } - public Page getMessagesByUserId(Pageable pageable) { + public Page getUserMessages(Pageable pageable) { Long userId = userService.getCurrentUser().getId(); return new PageImpl<>(messageRepository.findByAuthorIdOrRecipientId(userId, userId, pageable) @@ -53,10 +55,10 @@ public Page getMessagesByUserId(Pageable pageable) { if (!messageResponseDto.getCreated().equals(messageResponseDto.getUpdated())) { messageResponseDto.setEdited(true); } - }).toList()); + }).sorted(Comparator.comparing(MessageResponseDto::getCreated).reversed()).toList()); } - public Page getSentMessagesByUserId(Pageable pageable) { + public Page getSentUserMessages(Pageable pageable) { return new PageImpl<>(messageRepository.findByAuthorId(userService.getCurrentUser().getId(), pageable) .map(messageMapper::messageToMessageResponseDto) .stream().peek(messageResponseDto -> { @@ -65,17 +67,17 @@ public Page getSentMessagesByUserId(Pageable pageable) { if (!messageResponseDto.getCreated().equals(messageResponseDto.getUpdated())) { messageResponseDto.setEdited(true); } - }).toList()); + }).sorted(Comparator.comparing(MessageResponseDto::getCreated).reversed()).toList()); } - public Page getReceivedMessagesByUserId(Pageable pageable) { + public Page getReceivedUserMessages(Pageable pageable) { return new PageImpl<>(messageRepository.findByRecipientId(userService.getCurrentUser().getId(), pageable) .map(messageMapper::messageToMessageResponseDto) .stream().peek(messageResponseDto -> { if (!messageResponseDto.getCreated().equals(messageResponseDto.getUpdated())) { messageResponseDto.setEdited(true); } - }).toList()); + }).sorted(Comparator.comparing(MessageResponseDto::getCreated).reversed()).toList()); } public void deleteMessage(Long id) { From df6fb6183e81c80937f3180f87197fa0ab09776f Mon Sep 17 00:00:00 2001 From: Bohdan Koval Date: Sun, 28 Apr 2024 19:31:10 +0300 Subject: [PATCH 03/11] fixed null message status --- .../org/petmarket/message/dto/MessageResponseDto.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/petmarket/message/dto/MessageResponseDto.java b/src/main/java/org/petmarket/message/dto/MessageResponseDto.java index 069c3615..3c31b3f0 100644 --- a/src/main/java/org/petmarket/message/dto/MessageResponseDto.java +++ b/src/main/java/org/petmarket/message/dto/MessageResponseDto.java @@ -1,6 +1,5 @@ package org.petmarket.message.dto; -import com.fasterxml.jackson.annotation.JsonProperty; import lombok.*; import org.petmarket.message.entity.MessageStatus; import org.petmarket.users.dto.UserResponseDto; @@ -15,12 +14,11 @@ public class MessageResponseDto { private Long id; private String text; - private UserResponseDto author; - private UserResponseDto recipient; - @JsonProperty("message_status") - private MessageStatus messageStatus; + private MessageStatus status; private LocalDateTime created; private LocalDateTime updated; private boolean sender; private boolean edited; + private UserResponseDto author; + private UserResponseDto recipient; } From 6b6d7378bf5a94b745a0bfb750b08148bc1c9428 Mon Sep 17 00:00:00 2001 From: Bohdan Koval Date: Thu, 9 May 2024 11:13:18 +0300 Subject: [PATCH 04/11] adding message controller chat endpoints --- .../message/controller/MessageController.java | 55 +++++++++---------- .../message/dto/ChatResponseDto.java | 15 +++++ .../message/dto/MessageResponseDto.java | 5 +- .../message/dto/UserChatsResponseDto.java | 15 +++++ .../message/mapper/MessageMapper.java | 5 +- .../message/repository/MessageRepository.java | 51 ++++++++++++++++- .../message/service/MessageService.java | 46 ++++++---------- 7 files changed, 127 insertions(+), 65 deletions(-) create mode 100644 src/main/java/org/petmarket/message/dto/ChatResponseDto.java create mode 100644 src/main/java/org/petmarket/message/dto/UserChatsResponseDto.java diff --git a/src/main/java/org/petmarket/message/controller/MessageController.java b/src/main/java/org/petmarket/message/controller/MessageController.java index d2bbff9b..b9f5cdef 100644 --- a/src/main/java/org/petmarket/message/controller/MessageController.java +++ b/src/main/java/org/petmarket/message/controller/MessageController.java @@ -4,16 +4,16 @@ import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; -import org.petmarket.message.dto.MessageRequestDto; -import org.petmarket.message.dto.MessageResponseDto; -import org.petmarket.message.dto.MessageUpdateDto; +import org.petmarket.message.dto.*; import org.petmarket.message.service.MessageAccessCheckerService; import org.petmarket.message.service.MessageService; +import org.petmarket.utils.annotations.parametrs.ParameterId; import org.petmarket.utils.annotations.parametrs.ParameterPageNumber; import org.petmarket.utils.annotations.parametrs.ParameterPageSize; import org.petmarket.utils.annotations.responses.*; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -41,8 +41,6 @@ public void addMessage(@RequestBody MessageRequestDto messageRequestDto) { messageService.addMessage(messageRequestDto); } - //TODO: add delete received messages, sent messages, and messages by user id - @Operation(summary = "Delete message by id") @DeleteMapping("/{id}") @ApiResponseSuccessful @@ -67,47 +65,48 @@ public void updateMessage(@PathVariable Long id, @RequestBody MessageUpdateDto m messageService.updateMessage(id, messageUpdateDto); } - @Operation(summary = "Get user sent messages") - @GetMapping("/sent") + @Operation(summary = "Get chat messages by chat user id") + @GetMapping("/chat/{chatUserId}") @ApiResponseSuccessful @ApiResponseUnauthorized + @ApiResponseForbidden @PreAuthorize("isAuthenticated()") - public Page getSentMessages( + public ChatResponseDto getChatMessages( @ParameterPageNumber @RequestParam(defaultValue = "1") @Positive int page, - @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size) { - return messageService.getSentUserMessages(PageRequest.of(page - 1, size)); + @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size, + @ParameterId @PathVariable Long chatUserId) { + return messageService.getChatWithUser(chatUserId, + PageRequest.of(page - 1, size).withSort(Sort.Direction.DESC, "created")); } - @Operation(summary = "Get user received messages") - @GetMapping("/received") + @Operation(summary = "Mark message as read") + @PutMapping("/{id}/read") @ApiResponseSuccessful @ApiResponseUnauthorized + @ApiResponseForbidden @PreAuthorize("isAuthenticated()") - public Page getReceivedMessages( - @ParameterPageNumber @RequestParam(defaultValue = "1") @Positive int page, - @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size) { - return messageService.getReceivedUserMessages(PageRequest.of(page - 1, size)); + public void markAsRead(@PathVariable Long id) { + messageAccessCheckerService.checkReadAccess(id); + messageService.markAsRead(id); } - @Operation(summary = "Get user messages") - @GetMapping + @Operation(summary = "Mark chat messages as read") + @PutMapping("/chat/{chatUserId}/read") @ApiResponseSuccessful @ApiResponseUnauthorized @PreAuthorize("isAuthenticated()") - public Page getMessages( - @ParameterPageNumber @RequestParam(defaultValue = "1") @Positive int page, - @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size) { - return messageService.getUserMessages(PageRequest.of(page - 1, size)); + public void markChatMessagesAsRead(@ParameterId @PathVariable Long chatUserId) { + messageService.markChatMessagesAsRead(chatUserId); } - @Operation(summary = "Mark message as read") - @PutMapping("/{id}/read") + @Operation(summary = "Get all chats of current user") + @GetMapping("/chats") @ApiResponseSuccessful @ApiResponseUnauthorized - @ApiResponseForbidden @PreAuthorize("isAuthenticated()") - public void markAsRead(@PathVariable Long id) { - messageAccessCheckerService.checkReadAccess(id); - messageService.markAsRead(id); + public Page getLatestChatMessages( + @ParameterPageNumber @RequestParam(defaultValue = "1") @Positive int page, + @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size) { + return messageService.getLatestChatMessages(PageRequest.of(page - 1, size)); } } diff --git a/src/main/java/org/petmarket/message/dto/ChatResponseDto.java b/src/main/java/org/petmarket/message/dto/ChatResponseDto.java new file mode 100644 index 00000000..05346715 --- /dev/null +++ b/src/main/java/org/petmarket/message/dto/ChatResponseDto.java @@ -0,0 +1,15 @@ +package org.petmarket.message.dto; + +import lombok.*; +import org.petmarket.users.dto.UserResponseDto; +import org.springframework.data.domain.Page; + +@Setter +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class ChatResponseDto { + private UserResponseDto chatUser; + private Page messages; +} diff --git a/src/main/java/org/petmarket/message/dto/MessageResponseDto.java b/src/main/java/org/petmarket/message/dto/MessageResponseDto.java index 3c31b3f0..0b43065b 100644 --- a/src/main/java/org/petmarket/message/dto/MessageResponseDto.java +++ b/src/main/java/org/petmarket/message/dto/MessageResponseDto.java @@ -2,7 +2,6 @@ import lombok.*; import org.petmarket.message.entity.MessageStatus; -import org.petmarket.users.dto.UserResponseDto; import java.time.LocalDateTime; @@ -19,6 +18,6 @@ public class MessageResponseDto { private LocalDateTime updated; private boolean sender; private boolean edited; - private UserResponseDto author; - private UserResponseDto recipient; + private Long authorId; + private Long recipientId; } diff --git a/src/main/java/org/petmarket/message/dto/UserChatsResponseDto.java b/src/main/java/org/petmarket/message/dto/UserChatsResponseDto.java new file mode 100644 index 00000000..f38f004e --- /dev/null +++ b/src/main/java/org/petmarket/message/dto/UserChatsResponseDto.java @@ -0,0 +1,15 @@ +package org.petmarket.message.dto; + +import java.time.LocalDateTime; + +public interface UserChatsResponseDto { + String getText(); + + LocalDateTime getCreated(); + + String getMessageStatus(); + + Long getChatUserId(); + + String getChatUserEmail(); +} diff --git a/src/main/java/org/petmarket/message/mapper/MessageMapper.java b/src/main/java/org/petmarket/message/mapper/MessageMapper.java index 5bc18489..d83b7c1b 100644 --- a/src/main/java/org/petmarket/message/mapper/MessageMapper.java +++ b/src/main/java/org/petmarket/message/mapper/MessageMapper.java @@ -6,13 +6,14 @@ import org.petmarket.message.dto.MessageResponseDto; import org.petmarket.message.entity.Message; import org.petmarket.message.dto.MessageRequestDto; -import org.petmarket.users.mapper.UserMapper; -@Mapper(config = MapperConfig.class, uses = {UserMapper.class}) +@Mapper(config = MapperConfig.class) public interface MessageMapper { @Mapping(target = "author.id", source = "authorId") @Mapping(target = "recipient.id", source = "recipientId") Message messageRequestDtoToMessage(MessageRequestDto messageRequestDto); + @Mapping(target = "authorId", source = "author.id") + @Mapping(target = "recipientId", source = "recipient.id") MessageResponseDto messageToMessageResponseDto(Message message); } diff --git a/src/main/java/org/petmarket/message/repository/MessageRepository.java b/src/main/java/org/petmarket/message/repository/MessageRepository.java index 33250709..dc938e2c 100644 --- a/src/main/java/org/petmarket/message/repository/MessageRepository.java +++ b/src/main/java/org/petmarket/message/repository/MessageRepository.java @@ -1,16 +1,61 @@ package org.petmarket.message.repository; +import org.petmarket.message.dto.UserChatsResponseDto; import org.petmarket.message.entity.Message; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; @Repository public interface MessageRepository extends JpaRepository { - Page findByAuthorIdOrRecipientId(Long authorId, Long recipientId, Pageable pageable); + @Query(value = """ + SELECT m + FROM Message m + WHERE (m.author.id = :chatUserId AND m.recipient.id = :userId) + OR (m.author.id = :userId AND m.recipient.id = :chatUserId) + """) + Page findAllByUserAndChatUserId(@Param("userId") Long userId, @Param("chatUserId") Long chatUserId, + Pageable pageable); - Page findByAuthorId(Long authorId, Pageable pageable); + @Modifying + @Transactional + @Query(value = """ + UPDATE Message m + SET m.message_status = 'READ' + WHERE m.author_id = :chatUserId AND m.recipient_id = :userId AND m.message_status = 'UNREAD' + """, nativeQuery = true) + void markChatMessagesAsRead(@Param("userId") Long userId, @Param("chatUserId") Long chatUserId); - Page findByRecipientId(Long recipientId, Pageable pageable); + @Query(value = """ + SELECT m.text, m.created, m.message_status AS messageStatus, u.id AS chatUserId, u.email AS chatUserEmail + FROM ( + SELECT IF(author_id = :userId, recipient_id, author_id) AS chat_user_id, + MAX(created) AS max_created + FROM message + WHERE author_id = :userId OR recipient_id = :userId + GROUP BY chat_user_id + ) AS latest_messages + INNER JOIN users u + ON latest_messages.chat_user_id = u.id + INNER JOIN message m + ON ( + (m.author_id = :userId AND m.recipient_id = latest_messages.chat_user_id) + OR + (m.recipient_id = :userId AND m.author_id = latest_messages.chat_user_id) + ) + AND latest_messages.max_created = m.created + """, countQuery = """ + SELECT COUNT(*) FROM ( + SELECT IF(author_id = :userId, recipient_id, author_id) AS chat_user_id + FROM message + WHERE author_id = :userId OR recipient_id = :userId + GROUP BY chat_user_id + ) AS subquery + """, nativeQuery = true) + Page findLatestChatMessagesByUserId(@Param("userId") Long userId, Pageable pageable); } diff --git a/src/main/java/org/petmarket/message/service/MessageService.java b/src/main/java/org/petmarket/message/service/MessageService.java index 4d4c15c6..4b067d7f 100644 --- a/src/main/java/org/petmarket/message/service/MessageService.java +++ b/src/main/java/org/petmarket/message/service/MessageService.java @@ -2,27 +2,25 @@ import lombok.RequiredArgsConstructor; import org.petmarket.errorhandling.ItemNotFoundException; -import org.petmarket.message.dto.MessageResponseDto; -import org.petmarket.message.dto.MessageUpdateDto; +import org.petmarket.message.dto.*; import org.petmarket.message.entity.Message; -import org.petmarket.message.dto.MessageRequestDto; import org.petmarket.message.entity.MessageStatus; import org.petmarket.message.mapper.MessageMapper; import org.petmarket.message.repository.MessageRepository; +import org.petmarket.users.mapper.UserMapper; import org.petmarket.users.service.UserService; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageImpl; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; -import java.util.Comparator; - @Service @RequiredArgsConstructor public class MessageService { private final MessageRepository messageRepository; private final MessageMapper messageMapper; private final UserService userService; + private final UserMapper userMapper; public void addMessage(MessageRequestDto messageRequestDto) { Message message = messageMapper.messageRequestDtoToMessage(messageRequestDto); @@ -42,42 +40,32 @@ public void markAsRead(Long id) { messageRepository.save(message); } - public Page getUserMessages(Pageable pageable) { - Long userId = userService.getCurrentUser().getId(); + public void markChatMessagesAsRead(Long chatUserId) { + messageRepository.markChatMessagesAsRead(UserService.getCurrentUserId(), chatUserId); + } + + public ChatResponseDto getChatWithUser(Long chatUserId, Pageable pageable) { + Long userId = UserService.getCurrentUserId(); - return new PageImpl<>(messageRepository.findByAuthorIdOrRecipientId(userId, userId, pageable) + Page message = new PageImpl<>(messageRepository + .findAllByUserAndChatUserId(userId, chatUserId, pageable) .map(messageMapper::messageToMessageResponseDto) .stream().peek(messageResponseDto -> { - if (messageResponseDto.getAuthor().getId() == userId) { + if (messageResponseDto.getAuthorId().equals(userId)) { messageResponseDto.setSender(true); } if (!messageResponseDto.getCreated().equals(messageResponseDto.getUpdated())) { messageResponseDto.setEdited(true); } - }).sorted(Comparator.comparing(MessageResponseDto::getCreated).reversed()).toList()); - } + }).toList()); - public Page getSentUserMessages(Pageable pageable) { - return new PageImpl<>(messageRepository.findByAuthorId(userService.getCurrentUser().getId(), pageable) - .map(messageMapper::messageToMessageResponseDto) - .stream().peek(messageResponseDto -> { - messageResponseDto.setSender(true); - - if (!messageResponseDto.getCreated().equals(messageResponseDto.getUpdated())) { - messageResponseDto.setEdited(true); - } - }).sorted(Comparator.comparing(MessageResponseDto::getCreated).reversed()).toList()); + return new ChatResponseDto(userMapper.mapEntityToDto(userService.findById(chatUserId)), message); } - public Page getReceivedUserMessages(Pageable pageable) { - return new PageImpl<>(messageRepository.findByRecipientId(userService.getCurrentUser().getId(), pageable) - .map(messageMapper::messageToMessageResponseDto) - .stream().peek(messageResponseDto -> { - if (!messageResponseDto.getCreated().equals(messageResponseDto.getUpdated())) { - messageResponseDto.setEdited(true); - } - }).sorted(Comparator.comparing(MessageResponseDto::getCreated).reversed()).toList()); + public Page getLatestChatMessages(Pageable pageable) { + Long userId = UserService.getCurrentUserId(); + return messageRepository.findLatestChatMessagesByUserId(userId, pageable); } public void deleteMessage(Long id) { From a8d7b4dea9802170c2e73588ff8ccf6ca54ea0e0 Mon Sep 17 00:00:00 2001 From: Bohdan Koval Date: Fri, 10 May 2024 16:59:22 +0300 Subject: [PATCH 05/11] adding refactoring --- .../message/controller/MessageController.java | 9 ++-- .../message/repository/MessageRepository.java | 8 +-- .../service/MessageAccessCheckerService.java | 52 +++++++------------ .../message/service/MessageService.java | 3 +- 4 files changed, 29 insertions(+), 43 deletions(-) diff --git a/src/main/java/org/petmarket/message/controller/MessageController.java b/src/main/java/org/petmarket/message/controller/MessageController.java index b9f5cdef..2bbd8023 100644 --- a/src/main/java/org/petmarket/message/controller/MessageController.java +++ b/src/main/java/org/petmarket/message/controller/MessageController.java @@ -48,7 +48,7 @@ public void addMessage(@RequestBody MessageRequestDto messageRequestDto) { @ApiResponseForbidden @ApiResponseNotFound @PreAuthorize("isAuthenticated()") - public void deleteMessage(@PathVariable Long id) { + public void deleteMessage(@ParameterId @PathVariable Long id) { messageAccessCheckerService.checkDeleteAccess(id); messageService.deleteMessage(id); } @@ -59,8 +59,9 @@ public void deleteMessage(@PathVariable Long id) { @ApiResponseBadRequest @ApiResponseUnauthorized @ApiResponseForbidden + @ApiResponseNotFound @PreAuthorize("isAuthenticated()") - public void updateMessage(@PathVariable Long id, @RequestBody MessageUpdateDto messageUpdateDto) { + public void updateMessage(@ParameterId @PathVariable Long id, @RequestBody MessageUpdateDto messageUpdateDto) { messageAccessCheckerService.checkUpdateAccess(List.of(messageService.getMessageById(id))); messageService.updateMessage(id, messageUpdateDto); } @@ -85,7 +86,7 @@ public ChatResponseDto getChatMessages( @ApiResponseUnauthorized @ApiResponseForbidden @PreAuthorize("isAuthenticated()") - public void markAsRead(@PathVariable Long id) { + public void markAsRead(@ParameterId @PathVariable Long id) { messageAccessCheckerService.checkReadAccess(id); messageService.markAsRead(id); } @@ -100,7 +101,7 @@ public void markChatMessagesAsRead(@ParameterId @PathVariable Long chatUserId) { } @Operation(summary = "Get all chats of current user") - @GetMapping("/chats") + @GetMapping("/chat") @ApiResponseSuccessful @ApiResponseUnauthorized @PreAuthorize("isAuthenticated()") diff --git a/src/main/java/org/petmarket/message/repository/MessageRepository.java b/src/main/java/org/petmarket/message/repository/MessageRepository.java index dc938e2c..5566a828 100644 --- a/src/main/java/org/petmarket/message/repository/MessageRepository.java +++ b/src/main/java/org/petmarket/message/repository/MessageRepository.java @@ -44,10 +44,10 @@ SELECT IF(author_id = :userId, recipient_id, author_id) AS chat_user_id, ON latest_messages.chat_user_id = u.id INNER JOIN message m ON ( - (m.author_id = :userId AND m.recipient_id = latest_messages.chat_user_id) - OR - (m.recipient_id = :userId AND m.author_id = latest_messages.chat_user_id) - ) + (m.author_id = :userId AND m.recipient_id = latest_messages.chat_user_id) + OR + (m.recipient_id = :userId AND m.author_id = latest_messages.chat_user_id) + ) AND latest_messages.max_created = m.created """, countQuery = """ SELECT COUNT(*) FROM ( diff --git a/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java b/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java index d6b48616..0b93eab2 100644 --- a/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java +++ b/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java @@ -9,6 +9,7 @@ import org.springframework.stereotype.Service; import java.util.List; +import java.util.Optional; @RequiredArgsConstructor @Service @@ -17,51 +18,36 @@ public class MessageAccessCheckerService { private final MessageService messageService; public void checkCreateAccess(MessageRequestDto message) { - if (!userService.isCurrentUserAdmin()) { - User user = userService.getCurrentUser(); - - if (!message.getAuthorId().equals(user.getId())) { - throw new AccessDeniedException("Access denied to create message"); - } - } + getNonAdminUser() + .filter(user -> message.getAuthorId().equals(user.getId())) + .orElseThrow(() -> new AccessDeniedException("Access denied to create message")); } public void checkUpdateAccess(List messages) { - if (!userService.isCurrentUserAdmin()) { - User user = userService.getCurrentUser(); - - for (Message message : messages) { - if (!message.getAuthor().equals(user)) { - throw new AccessDeniedException(String - .format("Access denied to update message with id %s", message.getId())); - } - } - } + getNonAdminUser().ifPresent(user -> messages.stream() + .filter(message -> message.getAuthor().equals(user)) + .findAny() + .orElseThrow(() -> new AccessDeniedException("Access denied to update message"))); } public void checkReadAccess(Long messageId) { Message message = messageService.getMessageById(messageId); - - if (!userService.isCurrentUserAdmin()) { - User user = userService.getCurrentUser(); - - if (!message.getRecipient().equals(user)) { - throw new AccessDeniedException(String - .format("Access denied to read message with id %s", message.getId())); - } - } + getNonAdminUser() + .filter(user -> message.getRecipient().equals(user)) + .orElseThrow(() -> new AccessDeniedException("Access denied to read message")); } public void checkDeleteAccess(Long messageId) { Message message = messageService.getMessageById(messageId); + getNonAdminUser() + .filter(user -> message.getAuthor().equals(user)) + .orElseThrow(() -> new AccessDeniedException("Access denied to delete message")); + } - if (!userService.isCurrentUserAdmin()) { - User user = userService.getCurrentUser(); - - if (!message.getAuthor().equals(user)) { - throw new AccessDeniedException(String - .format("Access denied to delete message with id %s", message.getId())); - } + private Optional getNonAdminUser() { + if (userService.isCurrentUserAdmin()) { + return Optional.empty(); } + return Optional.ofNullable(userService.getCurrentUser()); } } diff --git a/src/main/java/org/petmarket/message/service/MessageService.java b/src/main/java/org/petmarket/message/service/MessageService.java index 4b067d7f..5893c348 100644 --- a/src/main/java/org/petmarket/message/service/MessageService.java +++ b/src/main/java/org/petmarket/message/service/MessageService.java @@ -64,8 +64,7 @@ public ChatResponseDto getChatWithUser(Long chatUserId, Pageable pageable) { } public Page getLatestChatMessages(Pageable pageable) { - Long userId = UserService.getCurrentUserId(); - return messageRepository.findLatestChatMessagesByUserId(userId, pageable); + return messageRepository.findLatestChatMessagesByUserId(UserService.getCurrentUserId(), pageable); } public void deleteMessage(Long id) { From 752c7a053b07e1c28fbc5c98c56e443f82cae2cc Mon Sep 17 00:00:00 2001 From: Bohdan Koval Date: Sat, 11 May 2024 12:50:19 +0300 Subject: [PATCH 06/11] adding check for blacklisted user and check for maximum message text --- .../errorhandling/GlobalExceptionHandler.java | 5 +++++ .../errorhandling/UserBlackListedException.java | 11 +++++++++++ .../message/controller/MessageController.java | 9 +++++---- .../org/petmarket/message/dto/MessageRequestDto.java | 2 ++ .../org/petmarket/message/dto/MessageUpdateDto.java | 2 ++ .../message/repository/MessageRepository.java | 4 ++++ .../org/petmarket/message/service/MessageService.java | 5 +++++ src/main/java/org/petmarket/users/entity/User.java | 2 +- .../petmarket/users/repository/UserRepository.java | 7 +++++++ .../java/org/petmarket/users/service/UserService.java | 4 ++++ 10 files changed, 46 insertions(+), 5 deletions(-) create mode 100644 src/main/java/org/petmarket/errorhandling/UserBlackListedException.java diff --git a/src/main/java/org/petmarket/errorhandling/GlobalExceptionHandler.java b/src/main/java/org/petmarket/errorhandling/GlobalExceptionHandler.java index 593be230..08f0a807 100644 --- a/src/main/java/org/petmarket/errorhandling/GlobalExceptionHandler.java +++ b/src/main/java/org/petmarket/errorhandling/GlobalExceptionHandler.java @@ -105,6 +105,11 @@ private ResponseEntity handleException(BadFrontendTokenException excepti return buildExceptionBody(exception, HttpStatus.BAD_REQUEST); } + @ExceptionHandler(UserBlackListedException.class) + private ResponseEntity handleException(UserBlackListedException exception) { + return buildExceptionBody(exception, HttpStatus.BAD_REQUEST); + } + @Override protected ResponseEntity handleHttpMessageNotReadable( HttpMessageNotReadableException exception, diff --git a/src/main/java/org/petmarket/errorhandling/UserBlackListedException.java b/src/main/java/org/petmarket/errorhandling/UserBlackListedException.java new file mode 100644 index 00000000..7a0dd7c6 --- /dev/null +++ b/src/main/java/org/petmarket/errorhandling/UserBlackListedException.java @@ -0,0 +1,11 @@ +package org.petmarket.errorhandling; + +public class UserBlackListedException extends RuntimeException { + public UserBlackListedException(String message) { + super(message); + } + + public UserBlackListedException(Throwable cause) { + super(cause); + } +} diff --git a/src/main/java/org/petmarket/message/controller/MessageController.java b/src/main/java/org/petmarket/message/controller/MessageController.java index 2bbd8023..5b2bc0f5 100644 --- a/src/main/java/org/petmarket/message/controller/MessageController.java +++ b/src/main/java/org/petmarket/message/controller/MessageController.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import org.petmarket.message.dto.*; @@ -13,7 +14,6 @@ import org.petmarket.utils.annotations.responses.*; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -36,7 +36,7 @@ public class MessageController { @ApiResponseUnauthorized @ApiResponseForbidden @PreAuthorize("isAuthenticated()") - public void addMessage(@RequestBody MessageRequestDto messageRequestDto) { + public void addMessage(@Valid @RequestBody MessageRequestDto messageRequestDto) { messageAccessCheckerService.checkCreateAccess(messageRequestDto); messageService.addMessage(messageRequestDto); } @@ -61,7 +61,8 @@ public void deleteMessage(@ParameterId @PathVariable Long id) { @ApiResponseForbidden @ApiResponseNotFound @PreAuthorize("isAuthenticated()") - public void updateMessage(@ParameterId @PathVariable Long id, @RequestBody MessageUpdateDto messageUpdateDto) { + public void updateMessage(@ParameterId @PathVariable Long id, + @Valid @RequestBody MessageUpdateDto messageUpdateDto) { messageAccessCheckerService.checkUpdateAccess(List.of(messageService.getMessageById(id))); messageService.updateMessage(id, messageUpdateDto); } @@ -77,7 +78,7 @@ public ChatResponseDto getChatMessages( @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size, @ParameterId @PathVariable Long chatUserId) { return messageService.getChatWithUser(chatUserId, - PageRequest.of(page - 1, size).withSort(Sort.Direction.DESC, "created")); + PageRequest.of(page - 1, size)); } @Operation(summary = "Mark message as read") diff --git a/src/main/java/org/petmarket/message/dto/MessageRequestDto.java b/src/main/java/org/petmarket/message/dto/MessageRequestDto.java index f614c0cd..76038cb8 100644 --- a/src/main/java/org/petmarket/message/dto/MessageRequestDto.java +++ b/src/main/java/org/petmarket/message/dto/MessageRequestDto.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; import lombok.*; @Setter @@ -13,6 +14,7 @@ public class MessageRequestDto { @NotBlank(message = "The 'text' cannot be empty") @Schema(example = "Hello, how are you?") + @Size(max = 10000, message = "The 'text' length must be less than or equal to 10000") private String text; @Schema(example = "1") diff --git a/src/main/java/org/petmarket/message/dto/MessageUpdateDto.java b/src/main/java/org/petmarket/message/dto/MessageUpdateDto.java index 91d1c611..35a4c7db 100644 --- a/src/main/java/org/petmarket/message/dto/MessageUpdateDto.java +++ b/src/main/java/org/petmarket/message/dto/MessageUpdateDto.java @@ -2,6 +2,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; import lombok.*; @Setter @@ -12,5 +13,6 @@ public class MessageUpdateDto { @NotBlank(message = "The 'text' cannot be empty") @Schema(example = "Hello, how are you?") + @Size(max = 10000, message = "The 'text' length must be less than or equal to 10000") private String text; } diff --git a/src/main/java/org/petmarket/message/repository/MessageRepository.java b/src/main/java/org/petmarket/message/repository/MessageRepository.java index 5566a828..d10bc0ec 100644 --- a/src/main/java/org/petmarket/message/repository/MessageRepository.java +++ b/src/main/java/org/petmarket/message/repository/MessageRepository.java @@ -18,6 +18,7 @@ public interface MessageRepository extends JpaRepository { FROM Message m WHERE (m.author.id = :chatUserId AND m.recipient.id = :userId) OR (m.author.id = :userId AND m.recipient.id = :chatUserId) + ORDER BY m.created DESC """) Page findAllByUserAndChatUserId(@Param("userId") Long userId, @Param("chatUserId") Long chatUserId, Pageable pageable); @@ -49,6 +50,9 @@ SELECT IF(author_id = :userId, recipient_id, author_id) AS chat_user_id, (m.recipient_id = :userId AND m.author_id = latest_messages.chat_user_id) ) AND latest_messages.max_created = m.created + LEFT JOIN users_black_list bl + ON bl.owner_id = :userId AND bl.user_id = latest_messages.chat_user_id + WHERE bl.user_id IS NULL """, countQuery = """ SELECT COUNT(*) FROM ( SELECT IF(author_id = :userId, recipient_id, author_id) AS chat_user_id diff --git a/src/main/java/org/petmarket/message/service/MessageService.java b/src/main/java/org/petmarket/message/service/MessageService.java index 5893c348..166b6a2b 100644 --- a/src/main/java/org/petmarket/message/service/MessageService.java +++ b/src/main/java/org/petmarket/message/service/MessageService.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import org.petmarket.errorhandling.ItemNotFoundException; +import org.petmarket.errorhandling.UserBlackListedException; import org.petmarket.message.dto.*; import org.petmarket.message.entity.Message; import org.petmarket.message.entity.MessageStatus; @@ -47,6 +48,10 @@ public void markChatMessagesAsRead(Long chatUserId) { public ChatResponseDto getChatWithUser(Long chatUserId, Pageable pageable) { Long userId = UserService.getCurrentUserId(); + if (userService.isUserBlacklisted(userId, chatUserId)) { + throw new UserBlackListedException(String.format("User with id %s is blacklisted", chatUserId)); + } + Page message = new PageImpl<>(messageRepository .findAllByUserAndChatUserId(userId, chatUserId, pageable) .map(messageMapper::messageToMessageResponseDto) diff --git a/src/main/java/org/petmarket/users/entity/User.java b/src/main/java/org/petmarket/users/entity/User.java index 58171f8d..b71b4a76 100644 --- a/src/main/java/org/petmarket/users/entity/User.java +++ b/src/main/java/org/petmarket/users/entity/User.java @@ -126,7 +126,7 @@ public class User { @Column(name = "last_activity") private LocalDateTime lastActivity; - @OneToOne(mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true) + @OneToOne(mappedBy = "user", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private Cart cart; @OneToMany(cascade = CascadeType.ALL, mappedBy = "user", fetch = FetchType.LAZY, orphanRemoval = true) diff --git a/src/main/java/org/petmarket/users/repository/UserRepository.java b/src/main/java/org/petmarket/users/repository/UserRepository.java index 734c59f5..afb2dea0 100644 --- a/src/main/java/org/petmarket/users/repository/UserRepository.java +++ b/src/main/java/org/petmarket/users/repository/UserRepository.java @@ -21,4 +21,11 @@ public interface UserRepository extends UserRepositoryBasic { @Modifying @Query("UPDATE User u SET u.status = org.petmarket.users.entity.UserStatus.DELETED WHERE u.id = :id") void setUserStatusToDeleted(@Param("id") Long id); + + @Query(value = """ + SELECT COUNT(*) + FROM users_black_list + WHERE owner_id = :ownerId AND user_id = :userId + """, nativeQuery = true) + Long countBlacklistedUsers(@Param("ownerId") Long ownerId, @Param("userId") Long userId); } diff --git a/src/main/java/org/petmarket/users/service/UserService.java b/src/main/java/org/petmarket/users/service/UserService.java index 6702a0db..db0a8fd7 100644 --- a/src/main/java/org/petmarket/users/service/UserService.java +++ b/src/main/java/org/petmarket/users/service/UserService.java @@ -194,4 +194,8 @@ public UserReviewListResponseDto getReviewsByUser(User user, int size) { .build(); return userReviews; } + + public Boolean isUserBlacklisted(Long ownerId, Long userId) { + return userRepository.countBlacklistedUsers(ownerId, userId) > 0; + } } From d5fe3ae0442ff7dc15db84d0d0945d10e6e3082c Mon Sep 17 00:00:00 2001 From: Bohdan Koval Date: Sat, 11 May 2024 13:58:34 +0300 Subject: [PATCH 07/11] fixed access errors --- .../service/MessageAccessCheckerService.java | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java b/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java index 0b93eab2..0f4e21a7 100644 --- a/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java +++ b/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java @@ -18,36 +18,49 @@ public class MessageAccessCheckerService { private final MessageService messageService; public void checkCreateAccess(MessageRequestDto message) { - getNonAdminUser() - .filter(user -> message.getAuthorId().equals(user.getId())) - .orElseThrow(() -> new AccessDeniedException("Access denied to create message")); + getNonAdminUser().ifPresent(u -> { + if (!message.getAuthorId().equals(u.getId())) { + throw new AccessDeniedException("Access denied to create message"); + } + }); } public void checkUpdateAccess(List messages) { - getNonAdminUser().ifPresent(user -> messages.stream() - .filter(message -> message.getAuthor().equals(user)) - .findAny() - .orElseThrow(() -> new AccessDeniedException("Access denied to update message"))); + getNonAdminUser().ifPresent(u -> { + for (Message message : messages) { + if (!message.getAuthor().equals(u)) { + throw new AccessDeniedException(String + .format("Access denied to update message with id %s", message.getId())); + } + } + }); } public void checkReadAccess(Long messageId) { - Message message = messageService.getMessageById(messageId); - getNonAdminUser() - .filter(user -> message.getRecipient().equals(user)) - .orElseThrow(() -> new AccessDeniedException("Access denied to read message")); + getNonAdminUser().ifPresent(u -> { + Message message = messageService.getMessageById(messageId); + if (!message.getRecipient().equals(u)) { + throw new AccessDeniedException(String + .format("Access denied to read message with id %s", message.getId())); + } + }); } public void checkDeleteAccess(Long messageId) { - Message message = messageService.getMessageById(messageId); - getNonAdminUser() - .filter(user -> message.getAuthor().equals(user)) - .orElseThrow(() -> new AccessDeniedException("Access denied to delete message")); + getNonAdminUser().ifPresent(u -> { + Message message = messageService.getMessageById(messageId); + if (!message.getAuthor().equals(u)) { + throw new AccessDeniedException(String + .format("Access denied to delete message with id %s", message.getId())); + } + }); } private Optional getNonAdminUser() { if (userService.isCurrentUserAdmin()) { return Optional.empty(); + } else { + return Optional.of(userService.getCurrentUser()); } - return Optional.ofNullable(userService.getCurrentUser()); } } From 9b9c81de7eb0c213fdc9dfb6e19d88a1a97d5c89 Mon Sep 17 00:00:00 2001 From: Bohdan Koval Date: Sat, 11 May 2024 13:59:50 +0300 Subject: [PATCH 08/11] fixed hibernate http message --- .../petmarket/errorhandling/UserBlackListedException.java | 4 ---- .../org/petmarket/message/controller/MessageController.java | 5 +++-- .../java/org/petmarket/message/dto/MessageRequestDto.java | 2 ++ .../java/org/petmarket/message/dto/MessageUpdateDto.java | 2 ++ 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/petmarket/errorhandling/UserBlackListedException.java b/src/main/java/org/petmarket/errorhandling/UserBlackListedException.java index 7a0dd7c6..5cb0a987 100644 --- a/src/main/java/org/petmarket/errorhandling/UserBlackListedException.java +++ b/src/main/java/org/petmarket/errorhandling/UserBlackListedException.java @@ -4,8 +4,4 @@ public class UserBlackListedException extends RuntimeException { public UserBlackListedException(String message) { super(message); } - - public UserBlackListedException(Throwable cause) { - super(cause); - } } diff --git a/src/main/java/org/petmarket/message/controller/MessageController.java b/src/main/java/org/petmarket/message/controller/MessageController.java index 5b2bc0f5..3c9df4f9 100644 --- a/src/main/java/org/petmarket/message/controller/MessageController.java +++ b/src/main/java/org/petmarket/message/controller/MessageController.java @@ -15,6 +15,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; @@ -36,7 +37,7 @@ public class MessageController { @ApiResponseUnauthorized @ApiResponseForbidden @PreAuthorize("isAuthenticated()") - public void addMessage(@Valid @RequestBody MessageRequestDto messageRequestDto) { + public void addMessage(@Valid @RequestBody MessageRequestDto messageRequestDto, BindingResult bindingResult) { messageAccessCheckerService.checkCreateAccess(messageRequestDto); messageService.addMessage(messageRequestDto); } @@ -62,7 +63,7 @@ public void deleteMessage(@ParameterId @PathVariable Long id) { @ApiResponseNotFound @PreAuthorize("isAuthenticated()") public void updateMessage(@ParameterId @PathVariable Long id, - @Valid @RequestBody MessageUpdateDto messageUpdateDto) { + @Valid @RequestBody MessageUpdateDto messageUpdateDto, BindingResult bindingResult) { messageAccessCheckerService.checkUpdateAccess(List.of(messageService.getMessageById(id))); messageService.updateMessage(id, messageUpdateDto); } diff --git a/src/main/java/org/petmarket/message/dto/MessageRequestDto.java b/src/main/java/org/petmarket/message/dto/MessageRequestDto.java index 76038cb8..af4e57c9 100644 --- a/src/main/java/org/petmarket/message/dto/MessageRequestDto.java +++ b/src/main/java/org/petmarket/message/dto/MessageRequestDto.java @@ -5,12 +5,14 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.*; +import org.springframework.validation.annotation.Validated; @Setter @Getter @Builder @AllArgsConstructor @NoArgsConstructor +@Validated public class MessageRequestDto { @NotBlank(message = "The 'text' cannot be empty") @Schema(example = "Hello, how are you?") diff --git a/src/main/java/org/petmarket/message/dto/MessageUpdateDto.java b/src/main/java/org/petmarket/message/dto/MessageUpdateDto.java index 35a4c7db..c3be1ad0 100644 --- a/src/main/java/org/petmarket/message/dto/MessageUpdateDto.java +++ b/src/main/java/org/petmarket/message/dto/MessageUpdateDto.java @@ -4,12 +4,14 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; import lombok.*; +import org.springframework.validation.annotation.Validated; @Setter @Getter @Builder @AllArgsConstructor @NoArgsConstructor +@Validated public class MessageUpdateDto { @NotBlank(message = "The 'text' cannot be empty") @Schema(example = "Hello, how are you?") From 69472ff332fbbacac6b354f1c005c226c55e0caf Mon Sep 17 00:00:00 2001 From: Bohdan Koval Date: Sun, 12 May 2024 11:24:40 +0300 Subject: [PATCH 09/11] added check for blacklist when adding message --- .../petmarket/message/service/MessageService.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/org/petmarket/message/service/MessageService.java b/src/main/java/org/petmarket/message/service/MessageService.java index 166b6a2b..9702cd33 100644 --- a/src/main/java/org/petmarket/message/service/MessageService.java +++ b/src/main/java/org/petmarket/message/service/MessageService.java @@ -24,6 +24,8 @@ public class MessageService { private final UserMapper userMapper; public void addMessage(MessageRequestDto messageRequestDto) { + checkBlackList(messageRequestDto.getRecipientId(), messageRequestDto.getAuthorId()); + Message message = messageMapper.messageRequestDtoToMessage(messageRequestDto); message.setStatus(MessageStatus.UNREAD); messageRepository.save(message); @@ -47,10 +49,7 @@ public void markChatMessagesAsRead(Long chatUserId) { public ChatResponseDto getChatWithUser(Long chatUserId, Pageable pageable) { Long userId = UserService.getCurrentUserId(); - - if (userService.isUserBlacklisted(userId, chatUserId)) { - throw new UserBlackListedException(String.format("User with id %s is blacklisted", chatUserId)); - } + checkBlackList(userId, chatUserId); Page message = new PageImpl<>(messageRepository .findAllByUserAndChatUserId(userId, chatUserId, pageable) @@ -81,4 +80,10 @@ public Message getMessageById(Long id) { return messageRepository.findById(id) .orElseThrow(() -> new ItemNotFoundException(String.format("Message with id %s not found", id))); } + + private void checkBlackList(Long recipientId, Long authorId) { + if (userService.isUserBlacklisted(recipientId, authorId)) { + throw new UserBlackListedException(String.format("User with id %s is blacklisted", authorId)); + } + } } From 3cd2bcd769456ec5decb61c79d05fbfda1f86e1d Mon Sep 17 00:00:00 2001 From: Bohdan Koval Date: Sun, 12 May 2024 12:16:13 +0300 Subject: [PATCH 10/11] refactored chat endpoints to chat controller --- .../message/controller/ChatController.java | 63 +++++++++++++++++++ .../message/controller/MessageController.java | 39 ------------ .../message/service/MessageService.java | 2 +- 3 files changed, 64 insertions(+), 40 deletions(-) create mode 100644 src/main/java/org/petmarket/message/controller/ChatController.java diff --git a/src/main/java/org/petmarket/message/controller/ChatController.java b/src/main/java/org/petmarket/message/controller/ChatController.java new file mode 100644 index 00000000..d01e0233 --- /dev/null +++ b/src/main/java/org/petmarket/message/controller/ChatController.java @@ -0,0 +1,63 @@ +package org.petmarket.message.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.constraints.Positive; +import lombok.RequiredArgsConstructor; +import org.petmarket.message.dto.ChatResponseDto; +import org.petmarket.message.dto.UserChatsResponseDto; +import org.petmarket.message.service.MessageService; +import org.petmarket.utils.annotations.parametrs.ParameterId; +import org.petmarket.utils.annotations.parametrs.ParameterPageNumber; +import org.petmarket.utils.annotations.parametrs.ParameterPageSize; +import org.petmarket.utils.annotations.responses.ApiResponseForbidden; +import org.petmarket.utils.annotations.responses.ApiResponseSuccessful; +import org.petmarket.utils.annotations.responses.ApiResponseUnauthorized; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +@Tag(name = "Chat") +@RestController +@RequiredArgsConstructor +@Validated +@RequestMapping(value = "/v1/chat") +public class ChatController { + private final MessageService messageService; + + @Operation(summary = "Get all chats of current user") + @GetMapping + @ApiResponseSuccessful + @ApiResponseUnauthorized + @PreAuthorize("isAuthenticated()") + public Page getLatestChats( + @ParameterPageNumber @RequestParam(defaultValue = "1") @Positive int page, + @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size) { + return messageService.getLatestChats(PageRequest.of(page - 1, size)); + } + + @Operation(summary = "Get chat messages by chat user id") + @GetMapping("/{chatUserId}") + @ApiResponseSuccessful + @ApiResponseUnauthorized + @ApiResponseForbidden + @PreAuthorize("isAuthenticated()") + public ChatResponseDto getChatMessages( + @ParameterPageNumber @RequestParam(defaultValue = "1") @Positive int page, + @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size, + @ParameterId @PathVariable Long chatUserId) { + return messageService.getChatWithUser(chatUserId, + PageRequest.of(page - 1, size)); + } + + @Operation(summary = "Mark chat messages as read") + @PutMapping("/{chatUserId}/read") + @ApiResponseSuccessful + @ApiResponseUnauthorized + @PreAuthorize("isAuthenticated()") + public void markChatMessagesAsRead(@ParameterId @PathVariable Long chatUserId) { + messageService.markChatMessagesAsRead(chatUserId); + } +} diff --git a/src/main/java/org/petmarket/message/controller/MessageController.java b/src/main/java/org/petmarket/message/controller/MessageController.java index 3c9df4f9..9b3fee63 100644 --- a/src/main/java/org/petmarket/message/controller/MessageController.java +++ b/src/main/java/org/petmarket/message/controller/MessageController.java @@ -3,17 +3,12 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; -import jakarta.validation.constraints.Positive; import lombok.RequiredArgsConstructor; import org.petmarket.message.dto.*; import org.petmarket.message.service.MessageAccessCheckerService; import org.petmarket.message.service.MessageService; import org.petmarket.utils.annotations.parametrs.ParameterId; -import org.petmarket.utils.annotations.parametrs.ParameterPageNumber; -import org.petmarket.utils.annotations.parametrs.ParameterPageSize; import org.petmarket.utils.annotations.responses.*; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; @@ -68,20 +63,6 @@ public void updateMessage(@ParameterId @PathVariable Long id, messageService.updateMessage(id, messageUpdateDto); } - @Operation(summary = "Get chat messages by chat user id") - @GetMapping("/chat/{chatUserId}") - @ApiResponseSuccessful - @ApiResponseUnauthorized - @ApiResponseForbidden - @PreAuthorize("isAuthenticated()") - public ChatResponseDto getChatMessages( - @ParameterPageNumber @RequestParam(defaultValue = "1") @Positive int page, - @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size, - @ParameterId @PathVariable Long chatUserId) { - return messageService.getChatWithUser(chatUserId, - PageRequest.of(page - 1, size)); - } - @Operation(summary = "Mark message as read") @PutMapping("/{id}/read") @ApiResponseSuccessful @@ -92,24 +73,4 @@ public void markAsRead(@ParameterId @PathVariable Long id) { messageAccessCheckerService.checkReadAccess(id); messageService.markAsRead(id); } - - @Operation(summary = "Mark chat messages as read") - @PutMapping("/chat/{chatUserId}/read") - @ApiResponseSuccessful - @ApiResponseUnauthorized - @PreAuthorize("isAuthenticated()") - public void markChatMessagesAsRead(@ParameterId @PathVariable Long chatUserId) { - messageService.markChatMessagesAsRead(chatUserId); - } - - @Operation(summary = "Get all chats of current user") - @GetMapping("/chat") - @ApiResponseSuccessful - @ApiResponseUnauthorized - @PreAuthorize("isAuthenticated()") - public Page getLatestChatMessages( - @ParameterPageNumber @RequestParam(defaultValue = "1") @Positive int page, - @ParameterPageSize @RequestParam(defaultValue = "30") @Positive int size) { - return messageService.getLatestChatMessages(PageRequest.of(page - 1, size)); - } } diff --git a/src/main/java/org/petmarket/message/service/MessageService.java b/src/main/java/org/petmarket/message/service/MessageService.java index 9702cd33..c19a4d31 100644 --- a/src/main/java/org/petmarket/message/service/MessageService.java +++ b/src/main/java/org/petmarket/message/service/MessageService.java @@ -67,7 +67,7 @@ public ChatResponseDto getChatWithUser(Long chatUserId, Pageable pageable) { return new ChatResponseDto(userMapper.mapEntityToDto(userService.findById(chatUserId)), message); } - public Page getLatestChatMessages(Pageable pageable) { + public Page getLatestChats(Pageable pageable) { return messageRepository.findLatestChatMessagesByUserId(UserService.getCurrentUserId(), pageable); } From 1ca1b436d427086e38a37545033d4c28f2cef306 Mon Sep 17 00:00:00 2001 From: Bohdan Koval Date: Mon, 13 May 2024 18:52:16 +0300 Subject: [PATCH 11/11] removed ability of user to sent messages to himself and make create message work with current user --- .../service/AdvertisementAccessCheckerService.java | 3 ++- .../petmarket/message/controller/MessageController.java | 2 +- .../java/org/petmarket/message/dto/MessageRequestDto.java | 4 ---- .../java/org/petmarket/message/mapper/MessageMapper.java | 1 - .../message/service/MessageAccessCheckerService.java | 8 +++----- .../org/petmarket/message/service/MessageService.java | 3 ++- 6 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/petmarket/advertisements/advertisement/service/AdvertisementAccessCheckerService.java b/src/main/java/org/petmarket/advertisements/advertisement/service/AdvertisementAccessCheckerService.java index 1640c9ba..b77ff5d9 100644 --- a/src/main/java/org/petmarket/advertisements/advertisement/service/AdvertisementAccessCheckerService.java +++ b/src/main/java/org/petmarket/advertisements/advertisement/service/AdvertisementAccessCheckerService.java @@ -4,6 +4,7 @@ import lombok.extern.slf4j.Slf4j; import org.petmarket.advertisements.advertisement.entity.Advertisement; import org.petmarket.advertisements.advertisement.entity.AdvertisementStatus; +import org.petmarket.errorhandling.ItemNotFoundException; import org.petmarket.users.entity.User; import org.petmarket.users.service.UserService; import org.springframework.security.access.AccessDeniedException; @@ -36,7 +37,7 @@ public void checkViewAccess(Advertisement advertisement) { if (!userService.isCurrentUserAdmin()) { User user = userService.getCurrentUser(); if (user == null || !advertisement.getAuthor().equals(user)) { - throw new AccessDeniedException("Access denied to advertisement with id " + advertisement.getId()); + throw new ItemNotFoundException("Advertisement was not found for user with id " + advertisement.getId()); } } } diff --git a/src/main/java/org/petmarket/message/controller/MessageController.java b/src/main/java/org/petmarket/message/controller/MessageController.java index 9b3fee63..e8261994 100644 --- a/src/main/java/org/petmarket/message/controller/MessageController.java +++ b/src/main/java/org/petmarket/message/controller/MessageController.java @@ -16,7 +16,7 @@ import java.util.List; -@Tag(name = "Message") +@Tag(name = "Chat") @RestController @RequiredArgsConstructor @Validated diff --git a/src/main/java/org/petmarket/message/dto/MessageRequestDto.java b/src/main/java/org/petmarket/message/dto/MessageRequestDto.java index af4e57c9..742a22de 100644 --- a/src/main/java/org/petmarket/message/dto/MessageRequestDto.java +++ b/src/main/java/org/petmarket/message/dto/MessageRequestDto.java @@ -19,10 +19,6 @@ public class MessageRequestDto { @Size(max = 10000, message = "The 'text' length must be less than or equal to 10000") private String text; - @Schema(example = "1") - @JsonProperty("author_id") - private Long authorId; - @Schema(example = "1") @JsonProperty("recipient_id") private Long recipientId; diff --git a/src/main/java/org/petmarket/message/mapper/MessageMapper.java b/src/main/java/org/petmarket/message/mapper/MessageMapper.java index d83b7c1b..3dbc9cc3 100644 --- a/src/main/java/org/petmarket/message/mapper/MessageMapper.java +++ b/src/main/java/org/petmarket/message/mapper/MessageMapper.java @@ -9,7 +9,6 @@ @Mapper(config = MapperConfig.class) public interface MessageMapper { - @Mapping(target = "author.id", source = "authorId") @Mapping(target = "recipient.id", source = "recipientId") Message messageRequestDtoToMessage(MessageRequestDto messageRequestDto); diff --git a/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java b/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java index 0f4e21a7..a4734179 100644 --- a/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java +++ b/src/main/java/org/petmarket/message/service/MessageAccessCheckerService.java @@ -18,11 +18,9 @@ public class MessageAccessCheckerService { private final MessageService messageService; public void checkCreateAccess(MessageRequestDto message) { - getNonAdminUser().ifPresent(u -> { - if (!message.getAuthorId().equals(u.getId())) { - throw new AccessDeniedException("Access denied to create message"); - } - }); + if (message.getRecipientId().equals(userService.getCurrentUser().getId())) { + throw new AccessDeniedException("Access denied to create message to current user"); + } } public void checkUpdateAccess(List messages) { diff --git a/src/main/java/org/petmarket/message/service/MessageService.java b/src/main/java/org/petmarket/message/service/MessageService.java index c19a4d31..a2df0457 100644 --- a/src/main/java/org/petmarket/message/service/MessageService.java +++ b/src/main/java/org/petmarket/message/service/MessageService.java @@ -24,9 +24,10 @@ public class MessageService { private final UserMapper userMapper; public void addMessage(MessageRequestDto messageRequestDto) { - checkBlackList(messageRequestDto.getRecipientId(), messageRequestDto.getAuthorId()); + checkBlackList(messageRequestDto.getRecipientId(), UserService.getCurrentUserId()); Message message = messageMapper.messageRequestDtoToMessage(messageRequestDto); + message.setAuthor(userService.getCurrentUser()); message.setStatus(MessageStatus.UNREAD); messageRepository.save(message); }