From 8ca2dd995d30a9ffed3622cf54226d6581785466 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 2 Oct 2023 11:59:46 +0700 Subject: [PATCH 1/9] Implemented search by name (#3) --- .../scarletredman/gd2spring/controller/LevelController.java | 6 ++++-- .../repository/impl/CustomLevelRepositoryImpl.java | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java b/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java index c5f0280..cbbf78f 100644 --- a/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java +++ b/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java @@ -68,8 +68,10 @@ GetLevelsResponse getLevels( @RequestParam(name = "song", required = false, defaultValue = "0") int song, @RequestParam(name = "customSong", required = false, defaultValue = "0") int customSong) { - var levels = levelService.getLevels(new LevelListPage.Filters( - "", null, null, 0, false, false, false, false, false, false, false, false, 0, 0, 0)); + var filters = new LevelListPage.Filters( + levelName, null, null, page, false, false, false, false, false, false, false, false, 0, 0, 0); + + var levels = levelService.getLevels(filters); return responseLogger.result(new GetLevelsResponse(levels)); } diff --git a/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java b/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java index 4399e5d..9229b8f 100644 --- a/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java +++ b/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java @@ -78,8 +78,9 @@ private Predicate[] applyFilters( @Nullable String levelName, LevelListPage.Filters filters) { var criteriaFilters = new ArrayList(); + criteriaFilters.add(criteria.isFalse(rootLevel.get("unlisted"))); if (levelName != null) { - criteriaFilters.add(criteria.like(rootLevel.get("name"), levelName + "%")); + criteriaFilters.add(criteria.like(criteria.lower(rootLevel.get("name")), levelName.toLowerCase() + "%")); } // todo: implement filters // todo: fix filters From bc09cb3881ee99b0516a74e486d5884b97ed627c Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 9 Oct 2023 11:26:45 +0700 Subject: [PATCH 2/9] Prepared controller for messaging --- .../controller/MessageController.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/main/java/ru/scarletredman/gd2spring/controller/MessageController.java diff --git a/src/main/java/ru/scarletredman/gd2spring/controller/MessageController.java b/src/main/java/ru/scarletredman/gd2spring/controller/MessageController.java new file mode 100644 index 0000000..291b74c --- /dev/null +++ b/src/main/java/ru/scarletredman/gd2spring/controller/MessageController.java @@ -0,0 +1,57 @@ +package ru.scarletredman.gd2spring.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import ru.scarletredman.gd2spring.controller.annotation.GeometryDashAPI; +import ru.scarletredman.gd2spring.security.annotation.GDAuthorizedOnly; +import ru.scarletredman.gd2spring.service.UserService; +import ru.scarletredman.gd2spring.util.ResponseLogger; + +@GeometryDashAPI +@RestController +@RequiredArgsConstructor +public class MessageController { + + private final ResponseLogger responseLogger; + + @GDAuthorizedOnly + @PostMapping("/getGJMessages20.php") + String messages(@RequestParam(name = "page") int page, @RequestParam(name = "total") long total) { + var user = UserService.getCurrentUserFromSecurityContextHolder(); + + return "-1"; + } + + @GDAuthorizedOnly + @PostMapping("/downloadGJMessage20.php") + String download(@RequestParam(name = "messageID") long messageId, @RequestParam(name = "isSender") int isSender) { + var user = UserService.getCurrentUserFromSecurityContextHolder(); + + return "-1"; + } + + @GDAuthorizedOnly + @PostMapping("/uploadGJMessage20.php") + String upload( + @RequestParam(name = "toAccountID") long receiverUserId, + @RequestParam(name = "subject") String subject, + @RequestParam(name = "body") String body) { + + var user = UserService.getCurrentUserFromSecurityContextHolder(); + + return "-1"; + } + + @GDAuthorizedOnly + @PostMapping("/deleteGJMessages.php") + String delete( + @RequestParam(name = "messageID", defaultValue = "-1", required = false) long messageId, + @RequestParam(name = "messages", defaultValue = "", required = false) String messagesList) { + + var user = UserService.getCurrentUserFromSecurityContextHolder(); + + return "-1"; + } +} From 10101efa5addb4d7765b4ea42fa26b66ef97e033 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Thu, 12 Oct 2023 10:49:47 +0700 Subject: [PATCH 3/9] Prepared response for GetMessages method --- .../response/GetMessagesResponse.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 src/main/java/ru/scarletredman/gd2spring/controller/response/GetMessagesResponse.java diff --git a/src/main/java/ru/scarletredman/gd2spring/controller/response/GetMessagesResponse.java b/src/main/java/ru/scarletredman/gd2spring/controller/response/GetMessagesResponse.java new file mode 100644 index 0000000..a40d16f --- /dev/null +++ b/src/main/java/ru/scarletredman/gd2spring/controller/response/GetMessagesResponse.java @@ -0,0 +1,51 @@ +package ru.scarletredman.gd2spring.controller.response; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import java.util.EnumMap; +import java.util.Map; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import ru.scarletredman.gd2spring.controller.response.json.ResponseSerializer; +import ru.scarletredman.gd2spring.util.JoinResponseUtil; + +@Getter +@Setter +@JsonSerialize(using = ResponseSerializer.class) +public class GetMessagesResponse implements ResponseSerializer.Response { + + private long messagesCount; + private long offset; + private final Map elements = new EnumMap<>(Key.class); + + public GetMessagesResponse(long messagesCount, long offset) { + this.messagesCount = messagesCount; + this.offset = offset; + init(); + } + + private void init() { + // todo + } + + @Override + public String getResponse() { + return JoinResponseUtil.join(elements, "|") + "#" + messagesCount + ":" + offset + ":10"; + } + + @Getter + @RequiredArgsConstructor + public enum Key implements JoinResponseUtil.Key { + MESSAGE_ID("1"), + USER_ID1("2"), + USER_ID2("3"), + SUBJECT("4"), + USERNAME("6"), + UPLOAD_TIME("7"), + IS_NEW("8"), + GET_SENT("9"), + ; + + private final String code; + } +} From 0ea1c6ac629c8daec4aef48db699d1382ab452c5 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Thu, 12 Oct 2023 11:14:28 +0700 Subject: [PATCH 4/9] Prepared response for GetMessage method --- .../response/GetMessagesResponse.java | 28 +++-------- .../controller/response/MessageResponse.java | 47 +++++++++++++++++++ 2 files changed, 53 insertions(+), 22 deletions(-) create mode 100644 src/main/java/ru/scarletredman/gd2spring/controller/response/MessageResponse.java diff --git a/src/main/java/ru/scarletredman/gd2spring/controller/response/GetMessagesResponse.java b/src/main/java/ru/scarletredman/gd2spring/controller/response/GetMessagesResponse.java index a40d16f..975465c 100644 --- a/src/main/java/ru/scarletredman/gd2spring/controller/response/GetMessagesResponse.java +++ b/src/main/java/ru/scarletredman/gd2spring/controller/response/GetMessagesResponse.java @@ -1,13 +1,11 @@ package ru.scarletredman.gd2spring.controller.response; import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import java.util.EnumMap; -import java.util.Map; +import java.util.LinkedList; +import java.util.List; import lombok.Getter; -import lombok.RequiredArgsConstructor; import lombok.Setter; import ru.scarletredman.gd2spring.controller.response.json.ResponseSerializer; -import ru.scarletredman.gd2spring.util.JoinResponseUtil; @Getter @Setter @@ -16,7 +14,7 @@ public class GetMessagesResponse implements ResponseSerializer.Response { private long messagesCount; private long offset; - private final Map elements = new EnumMap<>(Key.class); + private final List messages = new LinkedList<>(); public GetMessagesResponse(long messagesCount, long offset) { this.messagesCount = messagesCount; @@ -30,22 +28,8 @@ private void init() { @Override public String getResponse() { - return JoinResponseUtil.join(elements, "|") + "#" + messagesCount + ":" + offset + ":10"; - } - - @Getter - @RequiredArgsConstructor - public enum Key implements JoinResponseUtil.Key { - MESSAGE_ID("1"), - USER_ID1("2"), - USER_ID2("3"), - SUBJECT("4"), - USERNAME("6"), - UPLOAD_TIME("7"), - IS_NEW("8"), - GET_SENT("9"), - ; - - private final String code; + var msgList = String.join( + "|", messages.stream().map(MessageResponse::getResponse).toList()); + return msgList + "#" + messagesCount + ":" + offset + ":10"; } } diff --git a/src/main/java/ru/scarletredman/gd2spring/controller/response/MessageResponse.java b/src/main/java/ru/scarletredman/gd2spring/controller/response/MessageResponse.java new file mode 100644 index 0000000..0fe90ba --- /dev/null +++ b/src/main/java/ru/scarletredman/gd2spring/controller/response/MessageResponse.java @@ -0,0 +1,47 @@ +package ru.scarletredman.gd2spring.controller.response; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import java.util.EnumMap; +import java.util.Map; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.Setter; +import ru.scarletredman.gd2spring.controller.response.json.ResponseSerializer; +import ru.scarletredman.gd2spring.util.JoinResponseUtil; + +@Getter +@Setter +@JsonSerialize(using = ResponseSerializer.class) +public class MessageResponse implements ResponseSerializer.Response { + + private final Map elements = new EnumMap<>(Key.class); + + public MessageResponse() { + init(); + } + + private void init() { + // todo + } + + @Override + public String getResponse() { + return JoinResponseUtil.join(elements, ":"); + } + + @Getter + @RequiredArgsConstructor + public enum Key implements JoinResponseUtil.Key { + MESSAGE_ID("1"), + USER_ID1("2"), + USER_ID2("3"), + SUBJECT("4"), + USERNAME("6"), + UPLOAD_TIME("7"), + IS_NEW("8"), + GET_SENT("9"), + ; + + private final String code; + } +} From 85d44af5f81545dba00f830ca391502cb01cab4c Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 16 Oct 2023 13:07:36 +0700 Subject: [PATCH 5/9] Implemented messages repository, service, controller (#4) --- .../controller/MessageController.java | 87 ++++++++++++++++-- .../response/GetMessagesResponse.java | 81 +++++++++++++++-- .../controller/response/MessageResponse.java | 53 +++++++++-- .../response/RemoveMessageResponse.java | 27 ++++++ .../response/UploadMessageResponse.java | 27 ++++++ .../gd2spring/model/Message.java | 49 ++++++++++ .../repository/MessageRepository.java | 20 +++++ .../gd2spring/service/MessageService.java | 23 +++++ .../service/exception/MessageError.java | 6 ++ .../service/impl/MessageServiceImpl.java | 90 +++++++++++++++++++ .../service/type/MessageListPage.java | 6 ++ 11 files changed, 446 insertions(+), 23 deletions(-) create mode 100644 src/main/java/ru/scarletredman/gd2spring/controller/response/RemoveMessageResponse.java create mode 100644 src/main/java/ru/scarletredman/gd2spring/controller/response/UploadMessageResponse.java create mode 100644 src/main/java/ru/scarletredman/gd2spring/model/Message.java create mode 100644 src/main/java/ru/scarletredman/gd2spring/repository/MessageRepository.java create mode 100644 src/main/java/ru/scarletredman/gd2spring/service/MessageService.java create mode 100644 src/main/java/ru/scarletredman/gd2spring/service/exception/MessageError.java create mode 100644 src/main/java/ru/scarletredman/gd2spring/service/impl/MessageServiceImpl.java create mode 100644 src/main/java/ru/scarletredman/gd2spring/service/type/MessageListPage.java diff --git a/src/main/java/ru/scarletredman/gd2spring/controller/MessageController.java b/src/main/java/ru/scarletredman/gd2spring/controller/MessageController.java index 291b74c..5abc9b8 100644 --- a/src/main/java/ru/scarletredman/gd2spring/controller/MessageController.java +++ b/src/main/java/ru/scarletredman/gd2spring/controller/MessageController.java @@ -1,11 +1,21 @@ package ru.scarletredman.gd2spring.controller; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Objects; import lombok.RequiredArgsConstructor; +import org.apache.commons.codec.binary.Base64; +import org.springframework.security.access.AccessDeniedException; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import ru.scarletredman.gd2spring.controller.annotation.GeometryDashAPI; +import ru.scarletredman.gd2spring.controller.response.GetMessagesResponse; +import ru.scarletredman.gd2spring.controller.response.MessageResponse; +import ru.scarletredman.gd2spring.controller.response.RemoveMessageResponse; +import ru.scarletredman.gd2spring.controller.response.UploadMessageResponse; import ru.scarletredman.gd2spring.security.annotation.GDAuthorizedOnly; +import ru.scarletredman.gd2spring.service.MessageService; import ru.scarletredman.gd2spring.service.UserService; import ru.scarletredman.gd2spring.util.ResponseLogger; @@ -14,44 +24,103 @@ @RequiredArgsConstructor public class MessageController { + private final MessageService messageService; + private final UserService userService; private final ResponseLogger responseLogger; @GDAuthorizedOnly @PostMapping("/getGJMessages20.php") - String messages(@RequestParam(name = "page") int page, @RequestParam(name = "total") long total) { + GetMessagesResponse messages( + @RequestParam(name = "page") int page, + @RequestParam(name = "total") long total, + @RequestParam(name = "getSent", required = false, defaultValue = "0") int isSent) { + + if (page < 0) return responseLogger.result(GetMessagesResponse.error()); + var user = UserService.getCurrentUserFromSecurityContextHolder(); + var sent = isSent == 1; + var messages = messageService.getMessages(user, isSent == 1, page, 10); - return "-1"; + return responseLogger.result( + new GetMessagesResponse(messages.messages(), sent, messages.total(), messages.page())); } @GDAuthorizedOnly @PostMapping("/downloadGJMessage20.php") - String download(@RequestParam(name = "messageID") long messageId, @RequestParam(name = "isSender") int isSender) { + MessageResponse download( + @RequestParam(name = "messageID") long messageId, @RequestParam(name = "isSender") int isSender) { + var user = UserService.getCurrentUserFromSecurityContextHolder(); + var message = messageService.getMessageById(messageId); + + if (message.isEmpty()) { + return responseLogger.result(MessageResponse.error()); + } + var sender = isSender == 1; + var msg = message.get(); + + if ((sender && !Objects.equals(msg.getSender().getId(), user.getId())) + || (!sender && !Objects.equals(msg.getReceiver().getId(), user.getId()))) { + return responseLogger.result(MessageResponse.error()); + } - return "-1"; + return responseLogger.result(new MessageResponse(msg, sender)); } @GDAuthorizedOnly @PostMapping("/uploadGJMessage20.php") - String upload( + UploadMessageResponse upload( @RequestParam(name = "toAccountID") long receiverUserId, @RequestParam(name = "subject") String subject, @RequestParam(name = "body") String body) { var user = UserService.getCurrentUserFromSecurityContextHolder(); + var receiver = userService.findUserById(receiverUserId); - return "-1"; + if (receiver.isEmpty()) { + return responseLogger.result(UploadMessageResponse.error()); + } + + var decodedSubject = new String(Base64.decodeBase64(subject), StandardCharsets.UTF_8).replace('\0', ' '); + var decodedText = new String(Base64.decodeBase64(body), StandardCharsets.UTF_8).replace('\0', ' '); + + messageService.sendMessage(user, receiver.get(), decodedSubject, decodedText); + return responseLogger.result(UploadMessageResponse.success()); } @GDAuthorizedOnly - @PostMapping("/deleteGJMessages.php") - String delete( + @PostMapping("/deleteGJMessages20.php") + RemoveMessageResponse delete( @RequestParam(name = "messageID", defaultValue = "-1", required = false) long messageId, @RequestParam(name = "messages", defaultValue = "", required = false) String messagesList) { var user = UserService.getCurrentUserFromSecurityContextHolder(); + if (messageId == -1 && messagesList.isEmpty()) return responseLogger.result(RemoveMessageResponse.success()); + + try { + if (messageId > 0) { + var message = messageService.getMessageById(messageId); + message.ifPresent(msg -> messageService.deleteMessage(user, msg)); + } + + if (!messagesList.isEmpty()) { + var ids = Arrays.stream(messagesList.split(",")) + .map(num -> { + try { + return Long.parseLong(num); + } catch (NumberFormatException ex) { + return -1L; + } + }) + .filter(num -> num != -1) + .toList(); + + messageService.deleteMessagesById(user, ids); + } + } catch (AccessDeniedException ex) { + return responseLogger.result(RemoveMessageResponse.error()); + } - return "-1"; + return responseLogger.result(RemoveMessageResponse.success()); } } diff --git a/src/main/java/ru/scarletredman/gd2spring/controller/response/GetMessagesResponse.java b/src/main/java/ru/scarletredman/gd2spring/controller/response/GetMessagesResponse.java index 975465c..a19666b 100644 --- a/src/main/java/ru/scarletredman/gd2spring/controller/response/GetMessagesResponse.java +++ b/src/main/java/ru/scarletredman/gd2spring/controller/response/GetMessagesResponse.java @@ -1,35 +1,106 @@ package ru.scarletredman.gd2spring.controller.response; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import java.nio.charset.StandardCharsets; +import java.util.EnumMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import lombok.Getter; +import lombok.RequiredArgsConstructor; import lombok.Setter; +import org.apache.commons.codec.binary.Base64; import ru.scarletredman.gd2spring.controller.response.json.ResponseSerializer; +import ru.scarletredman.gd2spring.model.Message; +import ru.scarletredman.gd2spring.util.JoinResponseUtil; +import ru.scarletredman.gd2spring.util.TimeFormatUtil; @Getter @Setter @JsonSerialize(using = ResponseSerializer.class) -public class GetMessagesResponse implements ResponseSerializer.Response { +public final class GetMessagesResponse implements ResponseSerializer.Response { private long messagesCount; private long offset; private final List messages = new LinkedList<>(); + private final boolean error; - public GetMessagesResponse(long messagesCount, long offset) { + private GetMessagesResponse() { + error = true; + } + + public GetMessagesResponse(List messages, boolean sent, long messagesCount, long offset) { this.messagesCount = messagesCount; this.offset = offset; - init(); + init(messages, sent); + error = false; + } + + public static GetMessagesResponse error() { + return new GetMessagesResponse(); } - private void init() { - // todo + private void init(List messages, boolean sent) { + this.messages.addAll(messages.stream() + .map(message -> new MessageResponse(message, sent)) + .toList()); } @Override public String getResponse() { + if (error) return "-1"; + if (messages.isEmpty()) return "-2"; + var msgList = String.join( "|", messages.stream().map(MessageResponse::getResponse).toList()); return msgList + "#" + messagesCount + ":" + offset + ":10"; } + + public static class MessageResponse implements ResponseSerializer.Response { + + private final Map elements = new EnumMap<>(MessageResponse.Key.class); + + public MessageResponse(Message message, boolean isSender) { + init(message, isSender); + } + + private void init(Message message, boolean isSender) { + var target = isSender ? message.getReceiver() : message.getSender(); + var targetId = target.getId(); + + elements.put(MessageResponse.Key.MESSAGE_ID, message.getId()); + elements.put(MessageResponse.Key.TARGET_USER_ID1, targetId); + elements.put(MessageResponse.Key.TARGET_USER_ID2, targetId); + setSubject(message.getSubject()); + elements.put(Key.TARGET_USERNAME, target.getUsername()); + elements.put(Key.UPLOAD_TIME, TimeFormatUtil.formatBetween(message.getTime())); + elements.put(Key.IS_NEW, message.isNew() ? 1 : 0); + elements.put(Key.IS_SENDER, isSender ? 1 : 0); + } + + public void setSubject(String subject) { + elements.put(Key.SUBJECT, Base64.encodeBase64String(subject.getBytes(StandardCharsets.UTF_8))); + } + + @Override + public String getResponse() { + return JoinResponseUtil.join(elements, ":"); + } + + @Getter + @RequiredArgsConstructor + public enum Key implements JoinResponseUtil.Key { + MESSAGE_ID("1"), + TARGET_USER_ID1("2"), + TARGET_USER_ID2("3"), + SUBJECT("4"), + TARGET_USERNAME("6"), + UPLOAD_TIME("7"), + IS_NEW("8"), + IS_SENDER("9"), + ; + + private final String code; + } + } } diff --git a/src/main/java/ru/scarletredman/gd2spring/controller/response/MessageResponse.java b/src/main/java/ru/scarletredman/gd2spring/controller/response/MessageResponse.java index 0fe90ba..3e1bbbb 100644 --- a/src/main/java/ru/scarletredman/gd2spring/controller/response/MessageResponse.java +++ b/src/main/java/ru/scarletredman/gd2spring/controller/response/MessageResponse.java @@ -1,31 +1,65 @@ package ru.scarletredman.gd2spring.controller.response; import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import java.nio.charset.StandardCharsets; import java.util.EnumMap; import java.util.Map; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; +import org.apache.commons.codec.binary.Base64; import ru.scarletredman.gd2spring.controller.response.json.ResponseSerializer; +import ru.scarletredman.gd2spring.model.Message; import ru.scarletredman.gd2spring.util.JoinResponseUtil; +import ru.scarletredman.gd2spring.util.TimeFormatUtil; @Getter @Setter @JsonSerialize(using = ResponseSerializer.class) -public class MessageResponse implements ResponseSerializer.Response { +public final class MessageResponse implements ResponseSerializer.Response { private final Map elements = new EnumMap<>(Key.class); + private final boolean error; - public MessageResponse() { - init(); + private MessageResponse() { + error = true; } - private void init() { - // todo + public MessageResponse(Message message, boolean isSender) { + init(message, isSender); + error = false; + } + + public static MessageResponse error() { + return new MessageResponse(); + } + + private void init(Message message, boolean isSender) { + var target = isSender ? message.getReceiver() : message.getSender(); + var targetId = target.getId(); + + elements.put(Key.MESSAGE_ID, message.getId()); + elements.put(Key.TARGET_USER_ID1, targetId); + elements.put(Key.TARGET_USER_ID2, targetId); + setSubject(message.getSubject()); + setText(message.getText()); + elements.put(Key.TARGET_USERNAME, target.getUsername()); + elements.put(Key.UPLOAD_TIME, TimeFormatUtil.formatBetween(message.getTime())); + elements.put(Key.IS_SENDER, isSender ? 1 : 0); + elements.put(Key.IS_NEW, message.isNew() ? 1 : 0); + } + + public void setSubject(String subject) { + elements.put(Key.SUBJECT, Base64.encodeBase64String(subject.getBytes(StandardCharsets.UTF_8))); + } + + public void setText(String message) { + elements.put(Key.BODY, Base64.encodeBase64String(message.getBytes(StandardCharsets.UTF_8))); } @Override public String getResponse() { + if (error) return "-1"; return JoinResponseUtil.join(elements, ":"); } @@ -33,13 +67,14 @@ public String getResponse() { @RequiredArgsConstructor public enum Key implements JoinResponseUtil.Key { MESSAGE_ID("1"), - USER_ID1("2"), - USER_ID2("3"), + TARGET_USER_ID1("2"), + TARGET_USER_ID2("3"), SUBJECT("4"), - USERNAME("6"), + BODY("5"), + TARGET_USERNAME("6"), UPLOAD_TIME("7"), IS_NEW("8"), - GET_SENT("9"), + IS_SENDER("9"), ; private final String code; diff --git a/src/main/java/ru/scarletredman/gd2spring/controller/response/RemoveMessageResponse.java b/src/main/java/ru/scarletredman/gd2spring/controller/response/RemoveMessageResponse.java new file mode 100644 index 0000000..a2bca97 --- /dev/null +++ b/src/main/java/ru/scarletredman/gd2spring/controller/response/RemoveMessageResponse.java @@ -0,0 +1,27 @@ +package ru.scarletredman.gd2spring.controller.response; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import ru.scarletredman.gd2spring.controller.response.json.ResponseSerializer; + +@JsonSerialize(using = ResponseSerializer.class) +public final class RemoveMessageResponse implements ResponseSerializer.Response { + + private final boolean error; + + private RemoveMessageResponse(boolean error) { + this.error = error; + } + + public static RemoveMessageResponse success() { + return new RemoveMessageResponse(false); + } + + public static RemoveMessageResponse error() { + return new RemoveMessageResponse(true); + } + + @Override + public String getResponse() { + return error ? "-1" : "1"; + } +} diff --git a/src/main/java/ru/scarletredman/gd2spring/controller/response/UploadMessageResponse.java b/src/main/java/ru/scarletredman/gd2spring/controller/response/UploadMessageResponse.java new file mode 100644 index 0000000..1a1382e --- /dev/null +++ b/src/main/java/ru/scarletredman/gd2spring/controller/response/UploadMessageResponse.java @@ -0,0 +1,27 @@ +package ru.scarletredman.gd2spring.controller.response; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import ru.scarletredman.gd2spring.controller.response.json.ResponseSerializer; + +@JsonSerialize(using = ResponseSerializer.class) +public class UploadMessageResponse implements ResponseSerializer.Response { + + private final boolean error; + + private UploadMessageResponse(boolean error) { + this.error = error; + } + + public static UploadMessageResponse success() { + return new UploadMessageResponse(false); + } + + public static UploadMessageResponse error() { + return new UploadMessageResponse(true); + } + + @Override + public String getResponse() { + return error ? "-1" : "1"; + } +} diff --git a/src/main/java/ru/scarletredman/gd2spring/model/Message.java b/src/main/java/ru/scarletredman/gd2spring/model/Message.java new file mode 100644 index 0000000..d4e9662 --- /dev/null +++ b/src/main/java/ru/scarletredman/gd2spring/model/Message.java @@ -0,0 +1,49 @@ +package ru.scarletredman.gd2spring.model; + +import jakarta.persistence.*; +import java.sql.Timestamp; +import java.time.Instant; +import lombok.Getter; +import lombok.Setter; +import org.hibernate.validator.constraints.Length; + +@Getter +@Setter +@Entity +@Table(name = "messages") +public class Message { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", nullable = false) + private Long id; + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumn(name = "sender", nullable = false) + private User sender; + + @ManyToOne(optional = false, fetch = FetchType.EAGER) + @JoinColumn(name = "receiver", nullable = false) + private User receiver; + + @Length(max = 35) @Column(name = "subject", nullable = false) + private String subject; + + @Length(max = 200) @Column(name = "text", nullable = false) + private String text; + + @Column(name = "time", nullable = false) + private Timestamp time = Timestamp.from(Instant.now()); + + @Column(name = "is_new") + private boolean isNew = true; + + public Message() {} + + public Message(User sender, User receiver, String subject, String text) { + this.sender = sender; + this.receiver = receiver; + this.subject = subject; + this.text = text; + } +} diff --git a/src/main/java/ru/scarletredman/gd2spring/repository/MessageRepository.java b/src/main/java/ru/scarletredman/gd2spring/repository/MessageRepository.java new file mode 100644 index 0000000..28046f8 --- /dev/null +++ b/src/main/java/ru/scarletredman/gd2spring/repository/MessageRepository.java @@ -0,0 +1,20 @@ +package ru.scarletredman.gd2spring.repository; + +import java.util.List; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import ru.scarletredman.gd2spring.model.Message; +import ru.scarletredman.gd2spring.model.User; + +@Repository +public interface MessageRepository extends JpaRepository { + + List findBySenderOrderByIdDesc(User sender, Pageable pageable); + + List findByReceiverOrderByIdDesc(User receiver, Pageable pageable); + + long countBySender(User sender); + + long countByReceiver(User receiver); +} diff --git a/src/main/java/ru/scarletredman/gd2spring/service/MessageService.java b/src/main/java/ru/scarletredman/gd2spring/service/MessageService.java new file mode 100644 index 0000000..f3b4036 --- /dev/null +++ b/src/main/java/ru/scarletredman/gd2spring/service/MessageService.java @@ -0,0 +1,23 @@ +package ru.scarletredman.gd2spring.service; + +import java.util.Collection; +import java.util.Optional; +import org.springframework.security.access.AccessDeniedException; +import ru.scarletredman.gd2spring.model.Message; +import ru.scarletredman.gd2spring.model.User; +import ru.scarletredman.gd2spring.service.type.MessageListPage; + +public interface MessageService { + + Optional getMessageById(long id); + + MessageListPage getMessages(User user, boolean sent, int page, int limit); + + Message sendMessage(User sender, User receiver, String subject, String text); + + void deleteMessage(User user, Message message) throws AccessDeniedException; + + void deleteMessagesById(User user, Collection messageIds) throws AccessDeniedException; + + void deleteMessages(User user, Collection messages) throws AccessDeniedException; +} diff --git a/src/main/java/ru/scarletredman/gd2spring/service/exception/MessageError.java b/src/main/java/ru/scarletredman/gd2spring/service/exception/MessageError.java new file mode 100644 index 0000000..b8d85d1 --- /dev/null +++ b/src/main/java/ru/scarletredman/gd2spring/service/exception/MessageError.java @@ -0,0 +1,6 @@ +package ru.scarletredman.gd2spring.service.exception; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class MessageError extends Error {} diff --git a/src/main/java/ru/scarletredman/gd2spring/service/impl/MessageServiceImpl.java b/src/main/java/ru/scarletredman/gd2spring/service/impl/MessageServiceImpl.java new file mode 100644 index 0000000..ba37739 --- /dev/null +++ b/src/main/java/ru/scarletredman/gd2spring/service/impl/MessageServiceImpl.java @@ -0,0 +1,90 @@ +package ru.scarletredman.gd2spring.service.impl; + +import java.util.Collection; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.PageRequest; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Isolation; +import org.springframework.transaction.annotation.Transactional; +import ru.scarletredman.gd2spring.model.Message; +import ru.scarletredman.gd2spring.model.User; +import ru.scarletredman.gd2spring.rabbit.MQEventPublisher; +import ru.scarletredman.gd2spring.repository.MessageRepository; +import ru.scarletredman.gd2spring.service.MessageService; +import ru.scarletredman.gd2spring.service.type.MessageListPage; + +@RequiredArgsConstructor +@Service +public class MessageServiceImpl implements MessageService { + + private final MessageRepository repository; + private final MQEventPublisher eventPublisher; + + @Transactional(readOnly = true, isolation = Isolation.READ_COMMITTED) + @Override + public Optional getMessageById(long id) { + return repository.findById(id); + } + + @Transactional(readOnly = true, isolation = Isolation.REPEATABLE_READ) + @Override + public MessageListPage getMessages(User user, boolean sent, int page, int limit) { + List messages; + long total; + + if (sent) { + messages = repository.findBySenderOrderByIdDesc(user, PageRequest.of(page, limit)); + total = repository.countBySender(user); + } else { + messages = repository.findByReceiverOrderByIdDesc(user, PageRequest.of(page, limit)); + total = repository.countByReceiver(user); + } + + return new MessageListPage(messages, page, total); + } + + @Transactional + @Override + public Message sendMessage(User sender, User receiver, String subject, String text) { + var message = new Message(sender, receiver, subject, text); + repository.save(message); + // todo: rabbitmq + return message; + } + + @Transactional(rollbackFor = AccessDeniedException.class) + @Override + public void deleteMessage(User user, Message message) throws AccessDeniedException { + if (!Objects.equals(message.getReceiver().getId(), user.getId()) + && !Objects.equals(message.getSender().getId(), user.getId())) { + throw new AccessDeniedException("User is not sender or receiver"); + } + + repository.delete(message); + } + + @Transactional(rollbackFor = AccessDeniedException.class) + @Override + public void deleteMessagesById(User user, Collection messageIds) throws AccessDeniedException { + deleteMessages(user, repository.findAllById(messageIds)); + } + + @Transactional(rollbackFor = AccessDeniedException.class) + @Override + public void deleteMessages(User user, Collection messages) throws AccessDeniedException { + var newList = messages.stream() + .peek(message -> { + if (!Objects.equals(message.getReceiver().getId(), user.getId()) + && !Objects.equals(message.getSender().getId(), user.getId())) { + throw new AccessDeniedException("User is not sender or receiver"); + } + }) + .toList(); + + repository.deleteAll(newList); + } +} diff --git a/src/main/java/ru/scarletredman/gd2spring/service/type/MessageListPage.java b/src/main/java/ru/scarletredman/gd2spring/service/type/MessageListPage.java new file mode 100644 index 0000000..a90c3b7 --- /dev/null +++ b/src/main/java/ru/scarletredman/gd2spring/service/type/MessageListPage.java @@ -0,0 +1,6 @@ +package ru.scarletredman.gd2spring.service.type; + +import java.util.List; +import ru.scarletredman.gd2spring.model.Message; + +public record MessageListPage(List messages, int page, long total) {} From 2b8e983b824759614115b759d96929dbc6e8004e Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 16 Oct 2023 13:10:50 +0700 Subject: [PATCH 6/9] Added profile for test config --- src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java index 25eac42..47573a8 100644 --- a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java +++ b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java @@ -3,6 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; import ru.scarletredman.gd2spring.model.Level; import ru.scarletredman.gd2spring.model.User; import ru.scarletredman.gd2spring.model.UserComment; @@ -10,6 +11,7 @@ import ru.scarletredman.gd2spring.service.UserCommentService; import ru.scarletredman.gd2spring.service.UserService; +@Profile("test") @Configuration @RequiredArgsConstructor public class TestConfig { From f65d2c2bdfab77961208c419bd0e6b431d4fc5a9 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 16 Oct 2023 13:23:11 +0700 Subject: [PATCH 7/9] Fixed in-game pagination issue --- .../gd2spring/controller/response/GetMessagesResponse.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ru/scarletredman/gd2spring/controller/response/GetMessagesResponse.java b/src/main/java/ru/scarletredman/gd2spring/controller/response/GetMessagesResponse.java index a19666b..88b8a3b 100644 --- a/src/main/java/ru/scarletredman/gd2spring/controller/response/GetMessagesResponse.java +++ b/src/main/java/ru/scarletredman/gd2spring/controller/response/GetMessagesResponse.java @@ -53,7 +53,7 @@ public String getResponse() { var msgList = String.join( "|", messages.stream().map(MessageResponse::getResponse).toList()); - return msgList + "#" + messagesCount + ":" + offset + ":10"; + return msgList + "#" + messagesCount + ":" + (offset * 10) + ":10"; } public static class MessageResponse implements ResponseSerializer.Response { From a92a27ec8dc530bf92e38ce3c19272cc651b4510 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 16 Oct 2023 13:23:26 +0700 Subject: [PATCH 8/9] Added test messages --- .../ru/scarletredman/gd2spring/config/TestConfig.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java index 47573a8..1fd9281 100644 --- a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java +++ b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java @@ -8,6 +8,7 @@ import ru.scarletredman.gd2spring.model.User; import ru.scarletredman.gd2spring.model.UserComment; import ru.scarletredman.gd2spring.service.LevelService; +import ru.scarletredman.gd2spring.service.MessageService; import ru.scarletredman.gd2spring.service.UserCommentService; import ru.scarletredman.gd2spring.service.UserService; @@ -19,6 +20,7 @@ public class TestConfig { private final UserService userService; private final UserCommentService userCommentService; private final LevelService levelService; + private final MessageService messageService; @Autowired void createTestUser(boolean debugMode) { @@ -44,6 +46,12 @@ void createTestUser(boolean debugMode) { var level = createTestLevel(user, "Test level"); levelService.uploadLevel(level); + + var user2 = userService.findUserById(2).get(); + for (int i = 0; i < 30; i++) { + messageService.sendMessage(user, user2, "Sent " + i, "Hello my dear friend, Test2!"); + messageService.sendMessage(user2, user, "Received " + i, "Hello my dear friend, Test!"); + } } Level createTestLevel(User owner, String name) { From 68c04f5adf8002e2d9f14218d85fb48c2582bb96 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 16 Oct 2023 13:29:24 +0700 Subject: [PATCH 9/9] Added event publishing for sending messages --- .../message/MessageSendMQResponse.java | 35 +++++++++++++++++++ .../service/impl/MessageServiceImpl.java | 6 ++-- 2 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 src/main/java/ru/scarletredman/gd2spring/rabbit/response/message/MessageSendMQResponse.java diff --git a/src/main/java/ru/scarletredman/gd2spring/rabbit/response/message/MessageSendMQResponse.java b/src/main/java/ru/scarletredman/gd2spring/rabbit/response/message/MessageSendMQResponse.java new file mode 100644 index 0000000..ff4127a --- /dev/null +++ b/src/main/java/ru/scarletredman/gd2spring/rabbit/response/message/MessageSendMQResponse.java @@ -0,0 +1,35 @@ +package ru.scarletredman.gd2spring.rabbit.response.message; + +import java.sql.Timestamp; +import lombok.Getter; +import ru.scarletredman.gd2spring.model.Message; +import ru.scarletredman.gd2spring.rabbit.response.EventMQResponse; + +@Getter +public class MessageSendMQResponse extends EventMQResponse { + + private final long id; + private final long senderUserId; + private final String senderUsername; + private final long receiverUserId; + private final String receiverUsername; + private final String subject; + private final String body; + private final Timestamp timestamp; + + public MessageSendMQResponse(Message message) { + super("message.send"); + id = message.getId(); + subject = message.getSubject(); + body = message.getText(); + timestamp = message.getTime(); + + var sender = message.getSender(); + senderUserId = sender.getId(); + senderUsername = sender.getUsername(); + + var receiver = message.getReceiver(); + receiverUserId = receiver.getId(); + receiverUsername = receiver.getUsername(); + } +} diff --git a/src/main/java/ru/scarletredman/gd2spring/service/impl/MessageServiceImpl.java b/src/main/java/ru/scarletredman/gd2spring/service/impl/MessageServiceImpl.java index ba37739..1f90eeb 100644 --- a/src/main/java/ru/scarletredman/gd2spring/service/impl/MessageServiceImpl.java +++ b/src/main/java/ru/scarletredman/gd2spring/service/impl/MessageServiceImpl.java @@ -13,6 +13,7 @@ import ru.scarletredman.gd2spring.model.Message; import ru.scarletredman.gd2spring.model.User; import ru.scarletredman.gd2spring.rabbit.MQEventPublisher; +import ru.scarletredman.gd2spring.rabbit.response.message.MessageSendMQResponse; import ru.scarletredman.gd2spring.repository.MessageRepository; import ru.scarletredman.gd2spring.service.MessageService; import ru.scarletredman.gd2spring.service.type.MessageListPage; @@ -50,9 +51,8 @@ public MessageListPage getMessages(User user, boolean sent, int page, int limit) @Transactional @Override public Message sendMessage(User sender, User receiver, String subject, String text) { - var message = new Message(sender, receiver, subject, text); - repository.save(message); - // todo: rabbitmq + var message = repository.save(new Message(sender, receiver, subject, text)); + eventPublisher.publish(new MessageSendMQResponse(message)); return message; }