From 803ba685c3d4ecb618223b6f3a6c1250a916d70e Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 2 Oct 2023 12:15:59 +0700 Subject: [PATCH 01/24] Fixed controversies filters (#3) --- .../gd2spring/controller/LevelController.java | 2 +- .../repository/impl/CustomLevelRepositoryImpl.java | 14 ++++++++++++++ .../gd2spring/service/type/LevelListPage.java | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java b/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java index cbbf78f..5a4138c 100644 --- a/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java +++ b/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java @@ -56,7 +56,7 @@ GetLevelsResponse getLevels( @RequestParam(name = "len") String length, // "-" or number @RequestParam(name = "page") int page, @RequestParam(name = "total") int total, - @RequestParam(name = "uncompleted", required = false, defaultValue = "0") int isUncompleted, + @RequestParam(name = "uncompleted", required = false, defaultValue = "0") int isOnlyUncompleted, @RequestParam(name = "onlyCompleted", required = false, defaultValue = "0") int isOnlyCompleted, @RequestParam(name = "featured", required = false, defaultValue = "0") int isFeatured, @RequestParam(name = "original", required = false, defaultValue = "0") int isOriginal, 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 9229b8f..2d47db7 100644 --- a/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java +++ b/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java @@ -22,6 +22,10 @@ public class CustomLevelRepositoryImpl implements CustomLevelRepository { @Override public LevelListPage getLevels(LevelListPage.Filters filters) { + if (checkControversies(filters)) { + return new LevelListPage(new ArrayList<>(), 0, 0); + } + final String searchLevelName; { var temp = filters.name().trim(); @@ -51,6 +55,16 @@ public LevelListPage getLevels(LevelListPage.Filters filters) { return new LevelListPage(levels, total, filters.page()); } + private boolean checkControversies(LevelListPage.Filters filters) { + if (filters.onlyCompleted() && filters.onlyUncompleted()) return true; + if ((filters.featured() || filters.epic()) && filters.noStar()) return true; + if (filters.demonFilter() != 0 + && filters.difficulty() != null + && !filters.difficulty().isDemon()) return true; + if (filters.song() != 0 && filters.customSong() != 0) return true; + return false; + } + private CriteriaQuery selectLevelsQuery( CriteriaBuilder criteria, @Nullable String levelName, LevelListPage.Filters filters) { var query = criteria.createQuery(GDLevelDTO.class); diff --git a/src/main/java/ru/scarletredman/gd2spring/service/type/LevelListPage.java b/src/main/java/ru/scarletredman/gd2spring/service/type/LevelListPage.java index 037d385..bd79334 100644 --- a/src/main/java/ru/scarletredman/gd2spring/service/type/LevelListPage.java +++ b/src/main/java/ru/scarletredman/gd2spring/service/type/LevelListPage.java @@ -13,7 +13,7 @@ public record Filters( @Nullable LevelRateInfo.Difficulty difficulty, @Nullable LevelFilters.Length length, int page, - boolean uncompleted, + boolean onlyCompleted, boolean onlyUncompleted, boolean featured, boolean original, From 58c4f56bdeffc3e8d004d23aebfc144b577614d1 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 2 Oct 2023 12:18:33 +0700 Subject: [PATCH 02/24] Updated checking controversies filters (#3) --- .../gd2spring/repository/impl/CustomLevelRepositoryImpl.java | 1 + 1 file changed, 1 insertion(+) 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 2d47db7..f46a772 100644 --- a/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java +++ b/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java @@ -62,6 +62,7 @@ private boolean checkControversies(LevelListPage.Filters filters) { && filters.difficulty() != null && !filters.difficulty().isDemon()) return true; if (filters.song() != 0 && filters.customSong() != 0) return true; + if (filters.song() < 0 && filters.customSong() < 0) return true; return false; } From ed8893d4f2b551332124efe135ae8743a9ab4cf8 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 2 Oct 2023 12:33:50 +0700 Subject: [PATCH 03/24] Added search level by id (#3) --- .../impl/CustomLevelRepositoryImpl.java | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) 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 f46a772..5a41127 100644 --- a/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java +++ b/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java @@ -1,9 +1,11 @@ package ru.scarletredman.gd2spring.repository.impl; import jakarta.persistence.EntityManager; +import jakarta.persistence.NoResultException; import jakarta.persistence.PersistenceContext; import jakarta.persistence.criteria.*; import java.util.ArrayList; +import java.util.LinkedList; import org.springframework.lang.Nullable; import ru.scarletredman.gd2spring.model.Level; import ru.scarletredman.gd2spring.model.User; @@ -41,12 +43,15 @@ public LevelListPage getLevels(LevelListPage.Filters filters) { var criteria = entityManager.getCriteriaBuilder(); + var selectById = selectById(searchLevelName, criteria); var selectQuery = selectLevelsQuery(criteria, searchLevelName, filters); - var levels = entityManager + var levels = new LinkedList(); + if (selectById != null) levels.add(selectById); + levels.addAll(entityManager .createQuery(selectQuery) .setFirstResult(10 * filters.page()) .setMaxResults(10) - .getResultList(); + .getResultList()); var total = entityManager .createQuery(countLevelsQuery(criteria, searchLevelName, filters)) @@ -66,6 +71,30 @@ private boolean checkControversies(LevelListPage.Filters filters) { return false; } + private @Nullable GDLevelDTO selectById(@Nullable String input, CriteriaBuilder criteria) { + if (input == null) return null; + + long levelId; + try { + levelId = Long.parseLong(input); + } catch (NumberFormatException ex) { + return null; + } + + var query = criteria.createQuery(GDLevelDTO.class); + var rootLevel = query.from(Level.class); + var joinUser = rootLevel.join("owner", JoinType.INNER); + + query.select(createLevelDTO(criteria, rootLevel, joinUser)); + query.where(criteria.equal(rootLevel.get("id"), levelId)); + + try { + return entityManager.createQuery(query).getSingleResult(); + } catch (NoResultException ex) { + return null; + } + } + private CriteriaQuery selectLevelsQuery( CriteriaBuilder criteria, @Nullable String levelName, LevelListPage.Filters filters) { var query = criteria.createQuery(GDLevelDTO.class); From a8409d4ef2836d2519394ae3e655e5132d0f004d Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 2 Oct 2023 19:42:33 +0700 Subject: [PATCH 04/24] Connected all filters in controller (#3) --- .../gd2spring/controller/LevelController.java | 23 +++++++++-- .../model/embedable/LevelFilters.java | 18 +++++++++ .../model/embedable/LevelRateInfo.java | 39 +++++++++++++++++++ .../impl/CustomLevelRepositoryImpl.java | 3 -- .../gd2spring/service/type/LevelListPage.java | 6 +-- 5 files changed, 78 insertions(+), 11 deletions(-) diff --git a/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java b/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java index 5a4138c..cc38bdc 100644 --- a/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java +++ b/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java @@ -6,6 +6,8 @@ import org.springframework.web.bind.annotation.RestController; import ru.scarletredman.gd2spring.controller.annotation.GeometryDashAPI; import ru.scarletredman.gd2spring.controller.response.GetLevelsResponse; +import ru.scarletredman.gd2spring.model.embedable.LevelFilters; +import ru.scarletredman.gd2spring.model.embedable.LevelRateInfo; import ru.scarletredman.gd2spring.security.annotation.GDAuthorizedOnly; import ru.scarletredman.gd2spring.service.LevelService; import ru.scarletredman.gd2spring.service.type.LevelListPage; @@ -52,8 +54,8 @@ String uploadLevel( GetLevelsResponse getLevels( @RequestParam(name = "type") int type, @RequestParam(name = "str") String levelName, - @RequestParam(name = "diff") String difficulty, // "-" or number - @RequestParam(name = "len") String length, // "-" or number + @RequestParam(name = "diff") String difficulty, // "-" or numbers + @RequestParam(name = "len") String length, // "-" or numbers @RequestParam(name = "page") int page, @RequestParam(name = "total") int total, @RequestParam(name = "uncompleted", required = false, defaultValue = "0") int isOnlyUncompleted, @@ -64,12 +66,25 @@ GetLevelsResponse getLevels( @RequestParam(name = "coins", required = false, defaultValue = "0") int hasCoins, @RequestParam(name = "epic", required = false, defaultValue = "0") int isEpic, @RequestParam(name = "noStar", required = false, defaultValue = "0") int noStar, - @RequestParam(name = "demonFilter", required = false, defaultValue = "-1") int demonFilter, + @RequestParam(name = "demonFilter", required = false, defaultValue = "0") int demonFilter, @RequestParam(name = "song", required = false, defaultValue = "0") int song, @RequestParam(name = "customSong", required = false, defaultValue = "0") int customSong) { var filters = new LevelListPage.Filters( - levelName, null, null, page, false, false, false, false, false, false, false, false, 0, 0, 0); + levelName, + LevelRateInfo.Difficulty.parseGDSearch(difficulty, demonFilter), + LevelFilters.Length.parseGDSearch(length), + page, + isOnlyCompleted == 1, + isOnlyUncompleted == 1, + isFeatured == 1, + isOriginal == 1, + isForTwoPlayers == 1, + hasCoins == 1, + isEpic == 1, + noStar == 1, + song, + customSong); var levels = levelService.getLevels(filters); return responseLogger.result(new GetLevelsResponse(levels)); diff --git a/src/main/java/ru/scarletredman/gd2spring/model/embedable/LevelFilters.java b/src/main/java/ru/scarletredman/gd2spring/model/embedable/LevelFilters.java index eb476b9..31fa3ca 100644 --- a/src/main/java/ru/scarletredman/gd2spring/model/embedable/LevelFilters.java +++ b/src/main/java/ru/scarletredman/gd2spring/model/embedable/LevelFilters.java @@ -2,6 +2,8 @@ import jakarta.persistence.Column; import jakarta.persistence.Embeddable; +import java.util.LinkedList; +import java.util.List; import lombok.*; @Getter @@ -30,5 +32,21 @@ public enum Length { XL(4); private final int code; + + public static List parseGDSearch(String input) { + var list = new LinkedList(); + if (input.equals("-")) return list; + + var nums = input.split(","); + for (var num : nums) { + try { + var len = values()[Integer.parseInt(num)]; + list.add(len); + } catch (NumberFormatException | IndexOutOfBoundsException ignore) { + } + } + + return list; + } } } diff --git a/src/main/java/ru/scarletredman/gd2spring/model/embedable/LevelRateInfo.java b/src/main/java/ru/scarletredman/gd2spring/model/embedable/LevelRateInfo.java index 9f45bbb..1ec3caf 100644 --- a/src/main/java/ru/scarletredman/gd2spring/model/embedable/LevelRateInfo.java +++ b/src/main/java/ru/scarletredman/gd2spring/model/embedable/LevelRateInfo.java @@ -3,6 +3,8 @@ import jakarta.persistence.Column; import jakarta.persistence.Embeddable; import java.sql.Timestamp; +import java.util.LinkedList; +import java.util.List; import lombok.*; @Getter @@ -71,5 +73,42 @@ public enum Difficulty { public boolean isAuto() { return this == AUTO; } + + public static List parseGDSearch(String input, int demonFilter) { + var list = new LinkedList(); + if (input.equals("-")) return list; + + var nums = input.split(","); + for (var num : nums) { + int diff; + try { + diff = Integer.parseInt(num); + } catch (NumberFormatException ignore) { + continue; + } + + switch (diff) { + case -1 -> list.add(NONE); + case 1 -> list.add(EASY); + case 2 -> list.add(NORMAL); + case 3 -> list.add(HARD); + case 4 -> list.add(HARDER); + case 5 -> list.add(INSANE); + case -3 -> list.add(AUTO); + case -2 -> { + switch (demonFilter) { + case 0 -> list.add(DEMON); + case 1 -> list.add(EASY_DEMON); + case 2 -> list.add(MEDIUM_DEMON); + case 3 -> list.add(HARD_DEMON); + case 4 -> list.add(INSANE_DEMON); + case 5 -> list.add(EXTREME_DEMON); + } + } + } + } + + return list; + } } } 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 5a41127..09b0fac 100644 --- a/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java +++ b/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java @@ -63,9 +63,6 @@ public LevelListPage getLevels(LevelListPage.Filters filters) { private boolean checkControversies(LevelListPage.Filters filters) { if (filters.onlyCompleted() && filters.onlyUncompleted()) return true; if ((filters.featured() || filters.epic()) && filters.noStar()) return true; - if (filters.demonFilter() != 0 - && filters.difficulty() != null - && !filters.difficulty().isDemon()) return true; if (filters.song() != 0 && filters.customSong() != 0) return true; if (filters.song() < 0 && filters.customSong() < 0) return true; return false; diff --git a/src/main/java/ru/scarletredman/gd2spring/service/type/LevelListPage.java b/src/main/java/ru/scarletredman/gd2spring/service/type/LevelListPage.java index bd79334..805f37c 100644 --- a/src/main/java/ru/scarletredman/gd2spring/service/type/LevelListPage.java +++ b/src/main/java/ru/scarletredman/gd2spring/service/type/LevelListPage.java @@ -1,7 +1,6 @@ package ru.scarletredman.gd2spring.service.type; import java.util.List; -import org.springframework.lang.Nullable; import ru.scarletredman.gd2spring.model.dto.GDLevelDTO; import ru.scarletredman.gd2spring.model.embedable.LevelFilters; import ru.scarletredman.gd2spring.model.embedable.LevelRateInfo; @@ -10,8 +9,8 @@ public record LevelListPage(List levels, long total, int offset) { public record Filters( String name, - @Nullable LevelRateInfo.Difficulty difficulty, - @Nullable LevelFilters.Length length, + List difficulty, + List length, int page, boolean onlyCompleted, boolean onlyUncompleted, @@ -21,7 +20,6 @@ public record Filters( boolean coins, boolean epic, boolean noStar, - int demonFilter, int song, int customSong) {} } From 8d11e9d601702f57d2cce72acc20d25e6e6334f9 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 2 Oct 2023 20:26:41 +0700 Subject: [PATCH 05/24] Added filters for difficulty and length (#3) --- .../repository/impl/CustomLevelRepositoryImpl.java | 6 ++++++ 1 file changed, 6 insertions(+) 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 09b0fac..46dc719 100644 --- a/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java +++ b/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java @@ -123,6 +123,12 @@ private Predicate[] applyFilters( if (levelName != null) { criteriaFilters.add(criteria.like(criteria.lower(rootLevel.get("name")), levelName.toLowerCase() + "%")); } + if (!filters.difficulty().isEmpty()) { + criteriaFilters.add(rootLevel.get("rate").get("difficulty").in(filters.difficulty())); + } + if (!filters.length().isEmpty()) { + criteriaFilters.add(rootLevel.get("filters").get("length").in(filters.length())); + } // todo: implement filters // todo: fix filters From 1cabd9150b53950a63f73d6416984ca74628c9b8 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 2 Oct 2023 21:07:00 +0700 Subject: [PATCH 06/24] Implemented all filters (#3) --- .../gd2spring/controller/LevelController.java | 8 ++-- .../scarletredman/gd2spring/model/Level.java | 5 +-- .../impl/CustomLevelRepositoryImpl.java | 37 ++++++++++++++++--- .../gd2spring/service/type/LevelListPage.java | 3 +- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java b/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java index cc38bdc..de52ade 100644 --- a/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java +++ b/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java @@ -66,9 +66,10 @@ GetLevelsResponse getLevels( @RequestParam(name = "coins", required = false, defaultValue = "0") int hasCoins, @RequestParam(name = "epic", required = false, defaultValue = "0") int isEpic, @RequestParam(name = "noStar", required = false, defaultValue = "0") int noStar, + @RequestParam(name = "star", required = false, defaultValue = "0") int hasStar, @RequestParam(name = "demonFilter", required = false, defaultValue = "0") int demonFilter, - @RequestParam(name = "song", required = false, defaultValue = "0") int song, - @RequestParam(name = "customSong", required = false, defaultValue = "0") int customSong) { + @RequestParam(name = "song", required = false, defaultValue = "-1") int song, + @RequestParam(name = "customSong", required = false, defaultValue = "0") int isCustomSong) { var filters = new LevelListPage.Filters( levelName, @@ -83,8 +84,9 @@ GetLevelsResponse getLevels( hasCoins == 1, isEpic == 1, noStar == 1, + hasStar == 1, song, - customSong); + isCustomSong == 1); var levels = levelService.getLevels(filters); return responseLogger.result(new GetLevelsResponse(levels)); diff --git a/src/main/java/ru/scarletredman/gd2spring/model/Level.java b/src/main/java/ru/scarletredman/gd2spring/model/Level.java index e8ecff2..31707d1 100644 --- a/src/main/java/ru/scarletredman/gd2spring/model/Level.java +++ b/src/main/java/ru/scarletredman/gd2spring/model/Level.java @@ -30,9 +30,8 @@ public class Level { @JoinColumn(name = "owner", nullable = false) private User owner; - @ManyToOne - @JoinColumn(name = "original") - private Level original = null; + @Column(name = "original") + private Long original = null; @Embedded private LevelRateInfo rate = new LevelRateInfo(); 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 46dc719..61728d8 100644 --- a/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java +++ b/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java @@ -61,10 +61,11 @@ public LevelListPage getLevels(LevelListPage.Filters filters) { } private boolean checkControversies(LevelListPage.Filters filters) { + if (filters.page() < 0) return true; if (filters.onlyCompleted() && filters.onlyUncompleted()) return true; if ((filters.featured() || filters.epic()) && filters.noStar()) return true; - if (filters.song() != 0 && filters.customSong() != 0) return true; - if (filters.song() < 0 && filters.customSong() < 0) return true; + if (filters.song() < -1) return true; + if (filters.hasStar() && filters.noStar()) return true; return false; } @@ -129,8 +130,34 @@ private Predicate[] applyFilters( if (!filters.length().isEmpty()) { criteriaFilters.add(rootLevel.get("filters").get("length").in(filters.length())); } - // todo: implement filters - // todo: fix filters + // todo: completed and uncompleted (need player leaderboards) + if (filters.featured()) { + criteriaFilters.add(criteria.isTrue(rootLevel.get("rate").get("featured"))); + } + if (filters.original()) { + criteriaFilters.add(rootLevel.get("original").isNull()); + } + if (filters.forTwoPlayers()) { + criteriaFilters.add(criteria.isTrue(rootLevel.get("filters").get("twoPlayers"))); + } + if (filters.coins()) { + criteriaFilters.add(criteria.gt(rootLevel.get("rate").get("coins"), 0)); + } + if (filters.epic()) { + criteriaFilters.add(criteria.isTrue(rootLevel.get("rate").get("epic"))); + } + if (filters.noStar()) { + criteriaFilters.add(criteria.equal(rootLevel.get("rate").get("stars"), 0)); + } + if (filters.hasStar()) { + criteriaFilters.add(criteria.gt(rootLevel.get("rate").get("stars"), 0)); + } + if (filters.song() != -1 && !filters.customSong()) { + criteriaFilters.add(criteria.equal(rootLevel.get("soundTrack"), filters.song())); + } + if (filters.song() != -1 && filters.customSong()) { + criteriaFilters.add(criteria.equal(rootLevel.get("songId"), filters.song())); + } return criteriaFilters.toArray(new Predicate[0]); } @@ -162,7 +189,7 @@ private CompoundSelection createLevelDTO( rootLevel.get("downloads"), rootLevel.get("soundTrack"), rootLevel.get("likes"), - rootLevel.get("original").get("id"), + rootLevel.get("original"), rootLevel.get("objects")); } } diff --git a/src/main/java/ru/scarletredman/gd2spring/service/type/LevelListPage.java b/src/main/java/ru/scarletredman/gd2spring/service/type/LevelListPage.java index 805f37c..7924bbd 100644 --- a/src/main/java/ru/scarletredman/gd2spring/service/type/LevelListPage.java +++ b/src/main/java/ru/scarletredman/gd2spring/service/type/LevelListPage.java @@ -20,6 +20,7 @@ public record Filters( boolean coins, boolean epic, boolean noStar, + boolean hasStar, int song, - int customSong) {} + boolean customSong) {} } From 10b189997b2647d7a5f0eacd738cd11ae73b4c70 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 2 Oct 2023 21:44:34 +0700 Subject: [PATCH 07/24] Added indexes for level entity (#3) --- src/main/java/ru/scarletredman/gd2spring/model/Level.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/ru/scarletredman/gd2spring/model/Level.java b/src/main/java/ru/scarletredman/gd2spring/model/Level.java index 31707d1..463c310 100644 --- a/src/main/java/ru/scarletredman/gd2spring/model/Level.java +++ b/src/main/java/ru/scarletredman/gd2spring/model/Level.java @@ -12,7 +12,11 @@ @Entity @Table( name = "levels", - indexes = {@Index(name = "level_name_index", columnList = "name")}) + indexes = { + @Index(name = "level_name_index", columnList = "name"), + @Index(name = "level_rate_time_index", columnList = "rate_time"), + @Index(name = "level_difficulty_index", columnList = "difficulty") + }) public class Level { @Id From 3722cdee2700ac33a990c39c3ff7cf743cf27e6a Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 2 Oct 2023 21:44:51 +0700 Subject: [PATCH 08/24] Added search type enum (#3) --- .../service/type/LevelSearchType.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/java/ru/scarletredman/gd2spring/service/type/LevelSearchType.java diff --git a/src/main/java/ru/scarletredman/gd2spring/service/type/LevelSearchType.java b/src/main/java/ru/scarletredman/gd2spring/service/type/LevelSearchType.java new file mode 100644 index 0000000..b620001 --- /dev/null +++ b/src/main/java/ru/scarletredman/gd2spring/service/type/LevelSearchType.java @@ -0,0 +1,33 @@ +package ru.scarletredman.gd2spring.service.type; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum LevelSearchType { + SEARCH_BY_NAME(0), + MOST_DOWNLOADS(1), + MOST_LIKED(2), + TRENDING(3), + FEATURED(6), + MAGIC(7), + MAP_PACK(10), + AWARDED(11), + FOLLOWED(12), + FRIENDS(13), + HALL_OF_FAME(16), + DAILY_SAFE(21), + WEEKLY_SAFE(22), + EVENT_SAFE(23), + ; + + private final int code; + + public static LevelSearchType find(int code) { + for (var type : values()) { + if (code == type.code) return type; + } + return MOST_LIKED; + } +} From 76f3b48649706d397ed3384b41aef043e0e4f11f Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 2 Oct 2023 21:54:38 +0700 Subject: [PATCH 09/24] Added more test levels (#3) --- .../gd2spring/config/TestConfig.java | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java index 25eac42..ad88743 100644 --- a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java +++ b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java @@ -6,10 +6,14 @@ import ru.scarletredman.gd2spring.model.Level; import ru.scarletredman.gd2spring.model.User; import ru.scarletredman.gd2spring.model.UserComment; +import ru.scarletredman.gd2spring.model.embedable.LevelRateInfo; import ru.scarletredman.gd2spring.service.LevelService; import ru.scarletredman.gd2spring.service.UserCommentService; import ru.scarletredman.gd2spring.service.UserService; +import java.sql.Timestamp; +import java.time.Instant; + @Configuration @RequiredArgsConstructor public class TestConfig { @@ -40,15 +44,20 @@ void createTestUser(boolean debugMode) { userCommentService.writeComment(new UserComment(u, "Hello world!!!")); } - var level = createTestLevel(user, "Test level"); - levelService.uploadLevel(level); + for (int i = 0; i < 30; i++) { + var level = createTestLevel(user, "Test level " + i, i, i * 2, i % 4, i % 3 == 0); + levelService.uploadLevel(level); + } } - Level createTestLevel(User owner, String name) { + Level createTestLevel(User owner, String name, int likes, int downloads, int stars, boolean featured) { var level = new Level(owner, name); level.setDescription("Hello world!"); level.setObjects(4); + level.setLikes(likes); + level.setDownloads(downloads); + var data = level.getData(); data.setExtra( "0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0"); @@ -58,6 +67,13 @@ Level createTestLevel(User owner, String name) { var rate = level.getRate(); rate.setRequestedStars(10); + rate.setStars(stars); + rate.setDifficulty(LevelRateInfo.Difficulty.values()[stars]); + rate.setFeatured(featured); + rate.setEpic(featured); + if (stars != 0 || featured) { + rate.setRateTime(Timestamp.from(Instant.now())); + } return level; } From f0fec633e22647c844e238065d83cc85d92c375f Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 2 Oct 2023 22:07:25 +0700 Subject: [PATCH 10/24] Fixed display of level pages info (#3) --- .../java/ru/scarletredman/gd2spring/config/TestConfig.java | 5 ++--- .../gd2spring/controller/response/GetLevelsResponse.java | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java index ad88743..60e0af4 100644 --- a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java +++ b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java @@ -1,5 +1,7 @@ package ru.scarletredman.gd2spring.config; +import java.sql.Timestamp; +import java.time.Instant; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; @@ -11,9 +13,6 @@ import ru.scarletredman.gd2spring.service.UserCommentService; import ru.scarletredman.gd2spring.service.UserService; -import java.sql.Timestamp; -import java.time.Instant; - @Configuration @RequiredArgsConstructor public class TestConfig { diff --git a/src/main/java/ru/scarletredman/gd2spring/controller/response/GetLevelsResponse.java b/src/main/java/ru/scarletredman/gd2spring/controller/response/GetLevelsResponse.java index c78551e..22da0da 100644 --- a/src/main/java/ru/scarletredman/gd2spring/controller/response/GetLevelsResponse.java +++ b/src/main/java/ru/scarletredman/gd2spring/controller/response/GetLevelsResponse.java @@ -56,7 +56,7 @@ public String getResponse() { String songs = String.join( DELIMITER_SONGS, songStats.stream().map(SongStat::getResponse).toList()); - String pageInfo = total + ":" + offset + ":10"; + String pageInfo = total + ":" + (offset * 10) + ":10"; String hash = generateHash(); return levels + "#" + users + "#" + songs + "#" + pageInfo + "#" + hash; From 20100b1a89ef7fcb84964a98f6e2774a84f433a8 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 9 Oct 2023 08:22:07 +0700 Subject: [PATCH 11/24] Implemented some filters (bug with hibernate) --- .../gd2spring/config/TestConfig.java | 8 ++--- .../gd2spring/controller/LevelController.java | 4 ++- .../impl/CustomLevelRepositoryImpl.java | 30 +++++++++++++++++++ .../gd2spring/service/type/LevelListPage.java | 3 +- 4 files changed, 39 insertions(+), 6 deletions(-) diff --git a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java index 60e0af4..87be0fd 100644 --- a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java +++ b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java @@ -66,12 +66,12 @@ Level createTestLevel(User owner, String name, int likes, int downloads, int sta var rate = level.getRate(); rate.setRequestedStars(10); - rate.setStars(stars); rate.setDifficulty(LevelRateInfo.Difficulty.values()[stars]); - rate.setFeatured(featured); - rate.setEpic(featured); - if (stars != 0 || featured) { + if (stars != 0) { + rate.setStars(stars); rate.setRateTime(Timestamp.from(Instant.now())); + rate.setFeatured(featured); + rate.setEpic(featured); } return level; diff --git a/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java b/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java index de52ade..4d31d4a 100644 --- a/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java +++ b/src/main/java/ru/scarletredman/gd2spring/controller/LevelController.java @@ -11,6 +11,7 @@ import ru.scarletredman.gd2spring.security.annotation.GDAuthorizedOnly; import ru.scarletredman.gd2spring.service.LevelService; import ru.scarletredman.gd2spring.service.type.LevelListPage; +import ru.scarletredman.gd2spring.service.type.LevelSearchType; import ru.scarletredman.gd2spring.util.ResponseLogger; @GeometryDashAPI @@ -86,7 +87,8 @@ GetLevelsResponse getLevels( noStar == 1, hasStar == 1, song, - isCustomSong == 1); + isCustomSong == 1, + LevelSearchType.find(type)); 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 61728d8..55cea8b 100644 --- a/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java +++ b/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java @@ -6,6 +6,7 @@ import jakarta.persistence.criteria.*; import java.util.ArrayList; import java.util.LinkedList; +import java.util.List; import org.springframework.lang.Nullable; import ru.scarletredman.gd2spring.model.Level; import ru.scarletredman.gd2spring.model.User; @@ -14,6 +15,7 @@ import ru.scarletredman.gd2spring.model.embedable.LevelRateInfo; import ru.scarletredman.gd2spring.repository.CustomLevelRepository; import ru.scarletredman.gd2spring.service.type.LevelListPage; +import ru.scarletredman.gd2spring.service.type.LevelSearchType; public class CustomLevelRepositoryImpl implements CustomLevelRepository { @@ -101,11 +103,13 @@ private CriteriaQuery selectLevelsQuery( query.select(createLevelDTO(criteria, rootLevel, joinUser)); query.where(applyFilters(criteria, rootLevel, levelName, filters)); + query.orderBy(applySorting(criteria, rootLevel, filters.type())); return query; } private CriteriaQuery countLevelsQuery( CriteriaBuilder criteria, @Nullable String levelName, LevelListPage.Filters filters) { + var query = criteria.createQuery(Long.class); var rootLevel = query.from(Level.class); @@ -114,11 +118,23 @@ private CriteriaQuery countLevelsQuery( return query; } + private List applySorting(CriteriaBuilder criteria, Root rootLevel, LevelSearchType type) { + return switch (type) { + case SEARCH_BY_NAME -> List.of(criteria.asc(rootLevel.get("name"))); + case MOST_DOWNLOADS -> List.of(criteria.desc(rootLevel.get("downloads"))); + case FEATURED, AWARDED, HALL_OF_FAME -> List.of( + criteria.desc(rootLevel.get("rate").get("rateTime"))); + case MAGIC -> List.of(criteria.desc(rootLevel.get("id"))); + default -> List.of(criteria.desc(rootLevel.get("likes"))); + }; + } + private Predicate[] applyFilters( CriteriaBuilder criteria, Root rootLevel, @Nullable String levelName, LevelListPage.Filters filters) { + var criteriaFilters = new ArrayList(); criteriaFilters.add(criteria.isFalse(rootLevel.get("unlisted"))); if (levelName != null) { @@ -158,6 +174,20 @@ private Predicate[] applyFilters( if (filters.song() != -1 && filters.customSong()) { criteriaFilters.add(criteria.equal(rootLevel.get("songId"), filters.song())); } + if (filters.type() == LevelSearchType.AWARDED + || filters.type() == LevelSearchType.FEATURED + || filters.type() == LevelSearchType.HALL_OF_FAME) { + criteriaFilters.add(criteria.isNotNull(rootLevel.get("rate").get("rateTime"))); + } + if (filters.type() == LevelSearchType.AWARDED) { + criteriaFilters.add(criteria.gt(rootLevel.get("rate").get("stars"), 0)); + } + if (filters.type() == LevelSearchType.FEATURED) { + criteriaFilters.add(criteria.isTrue(rootLevel.get("rate").get("featured"))); + } + if (filters.type() == LevelSearchType.HALL_OF_FAME) { + criteriaFilters.add(criteria.isTrue(rootLevel.get("rate").get("epic"))); + } return criteriaFilters.toArray(new Predicate[0]); } diff --git a/src/main/java/ru/scarletredman/gd2spring/service/type/LevelListPage.java b/src/main/java/ru/scarletredman/gd2spring/service/type/LevelListPage.java index 7924bbd..09da8c3 100644 --- a/src/main/java/ru/scarletredman/gd2spring/service/type/LevelListPage.java +++ b/src/main/java/ru/scarletredman/gd2spring/service/type/LevelListPage.java @@ -22,5 +22,6 @@ public record Filters( boolean noStar, boolean hasStar, int song, - boolean customSong) {} + boolean customSong, + LevelSearchType type) {} } From aeb78f4f46b8b8a89c2fcdca85c482bd4a071139 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Thu, 9 Nov 2023 11:34:38 +0700 Subject: [PATCH 12/24] Disabled sorting(enable after fix hibernate criteria bug) --- .../gd2spring/repository/impl/CustomLevelRepositoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 55cea8b..efb5031 100644 --- a/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java +++ b/src/main/java/ru/scarletredman/gd2spring/repository/impl/CustomLevelRepositoryImpl.java @@ -103,7 +103,7 @@ private CriteriaQuery selectLevelsQuery( query.select(createLevelDTO(criteria, rootLevel, joinUser)); query.where(applyFilters(criteria, rootLevel, levelName, filters)); - query.orderBy(applySorting(criteria, rootLevel, filters.type())); + // query.orderBy(applySorting(criteria, rootLevel, filters.type())); // TODO: FIX IT return query; } From f670abf293285f1b1d1ba80e4a07c77b0ebef760 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 9 Oct 2023 11:26:45 +0700 Subject: [PATCH 13/24] 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 869f024a9963dfe03b330115062942a97731aadc Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Thu, 12 Oct 2023 10:49:47 +0700 Subject: [PATCH 14/24] 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 2efed0d74d75bc9326b59530728c8514be36812a Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Thu, 12 Oct 2023 11:14:28 +0700 Subject: [PATCH 15/24] 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 282451a043124bf5d7d426b99005d28b84103917 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 16 Oct 2023 13:07:36 +0700 Subject: [PATCH 16/24] 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 c33b4ee3b4b7b6900db51d22082bada9e67f1a81 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 16 Oct 2023 13:10:50 +0700 Subject: [PATCH 17/24] 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 87be0fd..55effe8 100644 --- a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java +++ b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java @@ -5,6 +5,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; @@ -13,6 +14,7 @@ import ru.scarletredman.gd2spring.service.UserCommentService; import ru.scarletredman.gd2spring.service.UserService; +@Profile("test") @Configuration @RequiredArgsConstructor public class TestConfig { From f1d51f718378243c19eac34f113845ffcf786428 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 16 Oct 2023 13:23:11 +0700 Subject: [PATCH 18/24] 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 dfbe207f9dfaf08346af8f59e53a3cff71621ceb Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 16 Oct 2023 13:23:26 +0700 Subject: [PATCH 19/24] Added test messages --- .../gd2spring/config/TestConfig.java | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java index 55effe8..1fd9281 100644 --- a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java +++ b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java @@ -1,7 +1,5 @@ package ru.scarletredman.gd2spring.config; -import java.sql.Timestamp; -import java.time.Instant; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; @@ -9,8 +7,8 @@ import ru.scarletredman.gd2spring.model.Level; import ru.scarletredman.gd2spring.model.User; import ru.scarletredman.gd2spring.model.UserComment; -import ru.scarletredman.gd2spring.model.embedable.LevelRateInfo; import ru.scarletredman.gd2spring.service.LevelService; +import ru.scarletredman.gd2spring.service.MessageService; import ru.scarletredman.gd2spring.service.UserCommentService; import ru.scarletredman.gd2spring.service.UserService; @@ -22,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) { @@ -45,20 +44,21 @@ void createTestUser(boolean debugMode) { userCommentService.writeComment(new UserComment(u, "Hello world!!!")); } + var level = createTestLevel(user, "Test level"); + levelService.uploadLevel(level); + + var user2 = userService.findUserById(2).get(); for (int i = 0; i < 30; i++) { - var level = createTestLevel(user, "Test level " + i, i, i * 2, i % 4, i % 3 == 0); - levelService.uploadLevel(level); + 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, int likes, int downloads, int stars, boolean featured) { + Level createTestLevel(User owner, String name) { var level = new Level(owner, name); level.setDescription("Hello world!"); level.setObjects(4); - level.setLikes(likes); - level.setDownloads(downloads); - var data = level.getData(); data.setExtra( "0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0"); @@ -68,13 +68,6 @@ Level createTestLevel(User owner, String name, int likes, int downloads, int sta var rate = level.getRate(); rate.setRequestedStars(10); - rate.setDifficulty(LevelRateInfo.Difficulty.values()[stars]); - if (stars != 0) { - rate.setStars(stars); - rate.setRateTime(Timestamp.from(Instant.now())); - rate.setFeatured(featured); - rate.setEpic(featured); - } return level; } From aa823b0bed43861f9047f26fb48a3c1d0200ab35 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 16 Oct 2023 13:29:24 +0700 Subject: [PATCH 20/24] 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; } From 0b2b7e55463df3f7a214b85ccdb926318cced1cf Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Sun, 29 Oct 2023 20:17:32 +0700 Subject: [PATCH 21/24] used env in configs, updated docker-compose, added Dockerfile --- Dockerfile | 19 +++++++++++++++++ docker-compose.yml | 18 ++++++++++++++++ .../gd2spring/config/PostgresConfig.java | 13 ++++++++---- .../gd2spring/config/RabbitConfig.java | 21 ++++++++++++------- .../gd2spring/config/WebConfig.java | 2 ++ 5 files changed, 61 insertions(+), 12 deletions(-) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..15e1ad5 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM openjdk:17-alpine +LABEL authors="ScarletRedMan" + +RUN apk add git + +RUN mkdir /app +RUN mkdir /app/install + +WORKDIR /app/install +RUN git clone https://github.com/ScarletRedMan/GD2Spring.git +WORKDIR GD2Spring +RUN ./gradlew assemble +COPY ./build/libs/GD2Spring-0.0.1-SNAPSHOT.jar /app/server.jar +WORKDIR /app +RUN rm -rf install + +EXPOSE 8080 + +CMD java -jar server.jar diff --git a/docker-compose.yml b/docker-compose.yml index 6d3233a..60987da 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -23,3 +23,21 @@ services: - "5672:5672" volumes: - "./run/rabbitmq:/var/lib/rabbitmq" + + gd2spring: + build: + dockerfile: ./Dockerfile + ports: + - "8080:8080" + environment: + GD2SPRING_DATABASE_URL: jdbc:postgresql://postgres:5432/test + GD2SPRING_DATABASE_USER: test + GD2SPRING_DATABASE_PASSWORD: test + GD2SPRING_RABBITMQ_HOST: localhost + GD2SPRING_RABBITMQ_USER: test + GD2SPRING_RABBITMQ_PASSWORD: test + depends_on: + rabbitmq: + condition: service_started + postgres: + condition: service_started diff --git a/src/main/java/ru/scarletredman/gd2spring/config/PostgresConfig.java b/src/main/java/ru/scarletredman/gd2spring/config/PostgresConfig.java index 5d6b686..80b08e1 100644 --- a/src/main/java/ru/scarletredman/gd2spring/config/PostgresConfig.java +++ b/src/main/java/ru/scarletredman/gd2spring/config/PostgresConfig.java @@ -1,6 +1,7 @@ package ru.scarletredman.gd2spring.config; import javax.sql.DataSource; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.jdbc.DataSourceBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -9,11 +10,15 @@ public class PostgresConfig { @Bean - DataSource dataSource() { + DataSource dataSource( + @Value("${GD2SPRING_DATABASE_URL}") String url, + @Value("${GD2SPRING_DATABASE_USER}") String user, + @Value("${GD2SPRING_DATABASE_PASSWORD}") String password) { + return DataSourceBuilder.create() - .url("jdbc:postgresql://localhost:5432/test") - .username("test") - .password("test") + .url(url) + .username(user) + .password(password) .driverClassName("org.postgresql.Driver") .build(); } diff --git a/src/main/java/ru/scarletredman/gd2spring/config/RabbitConfig.java b/src/main/java/ru/scarletredman/gd2spring/config/RabbitConfig.java index 680501d..1d18644 100644 --- a/src/main/java/ru/scarletredman/gd2spring/config/RabbitConfig.java +++ b/src/main/java/ru/scarletredman/gd2spring/config/RabbitConfig.java @@ -9,6 +9,7 @@ import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import ru.scarletredman.gd2spring.rabbit.MQEventPublisher; @@ -19,11 +20,15 @@ public class RabbitConfig { @Bean - ConnectionFactory connectionFactory() { + ConnectionFactory connectionFactory( + @Value("${GD2SPRING_RABBITMQ_HOST}") String host, + @Value("${GD2SPRING_RABBITMQ_USER}") String user, + @Value("${GD2SPRING_RABBITMQ_PASSWORD}") String password) { + var factory = new CachingConnectionFactory(); - factory.setHost("localhost"); - factory.setUsername("test"); - factory.setPassword("test"); + factory.setHost(host); + factory.setUsername(user); + factory.setPassword(password); return factory; } @@ -33,13 +38,13 @@ Exchange eventExchange() { } @Bean - AmqpTemplate amqpTemplate() { - return new RabbitTemplate(connectionFactory()); + AmqpTemplate amqpTemplate(ConnectionFactory connectionFactory) { + return new RabbitTemplate(connectionFactory); } @Bean @Autowired - MQEventPublisher eventPublisher(ObjectMapper objectMapper) { - return new RabbitMQEventPublisher(amqpTemplate(), objectMapper, eventExchange()); + MQEventPublisher eventPublisher(AmqpTemplate amqpTemplate, ObjectMapper objectMapper) { + return new RabbitMQEventPublisher(amqpTemplate, objectMapper, eventExchange()); } } diff --git a/src/main/java/ru/scarletredman/gd2spring/config/WebConfig.java b/src/main/java/ru/scarletredman/gd2spring/config/WebConfig.java index b138bc1..ad4177e 100644 --- a/src/main/java/ru/scarletredman/gd2spring/config/WebConfig.java +++ b/src/main/java/ru/scarletredman/gd2spring/config/WebConfig.java @@ -6,12 +6,14 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.DependsOn; +import org.springframework.context.annotation.Profile; import org.springframework.lang.NonNull; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import ru.scarletredman.gd2spring.interceptor.DebugInterceptor; import ru.scarletredman.gd2spring.util.ResponseLogger; +@Profile("test") @Configuration @DependsOn("debugMode") @RequiredArgsConstructor From 849d52c284ef8a6bd25d779e9f9b00fa8dea55b7 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 6 Nov 2023 09:10:24 +0700 Subject: [PATCH 22/24] Updated README.md --- README.md | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/README.md b/README.md index 5596ee1..67c79df 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,67 @@ GeometryDash server emulator written in Java with using the Spring Framework. Supported version of Geometry Dash: 2.11 + +## Features + +- More vanilla server behavior than GMDPrivateServer. +- A stateless server with the ability to scale horizontally. +- RabbitMQ integration allows you to handle server events. + + +## Build and Run +For run this project you need Java 17. + +### Clone repository +```shell +git clone https://github.com/ScarletRedMan/GD2Spring.git + +cd ./GD2Spring/ +``` + +### Build +Windows: +```shell +gradlew assemble +``` + +Linux: +```shell +./gradlew assemble +``` + +Complied file located in `./build/libs/GD2Spring.jar` + + +### Run +Run Postgres and RabbitMQ servers. + +Set environments for GD2Spring: + +| Enviroment name | Description | +|-----------------------------|-----------------------| +| GD2SPRING_DATABASE_URL | Postgres database URL | +| GD2SPRING_DATABASE_USER | Database user | +| GD2SPRING_DATABASE_PASSWORD | Database password | +| GD2SPRING_RABBITMQ_HOST | RabbitMQ host | +| GD2SPRING_RABBITMQ_USER | RabbitMQ user | +| GD2SPRING_RABBITMQ_PASSWORD | RabbitMQ password | + +All these environments are required for start application. + +Run application: +```shell +java -jar GD2Spring.jar +``` + +Run with dev mode: +```shell +java -jar -Dspring.profiles.active=test GD2Spring.jar +``` + + ## Useful links + [GD-ServerSelector](https://github.com/ScarletRedMan/GD-ServerSelector) - The script for switching between servers (Windows Only) [GMDPrivateServer](https://github.com/Cvolton/GMDprivateServer) - Basically a Geometry Dash Server Emulator written in PHP From 72a53c2a64ff1fe156f7c062541314128402ce60 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 2 Oct 2023 21:54:38 +0700 Subject: [PATCH 23/24] Added more test levels (#3) --- .../gd2spring/config/TestConfig.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java index 1fd9281..bacc4d9 100644 --- a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java +++ b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java @@ -7,10 +7,13 @@ import ru.scarletredman.gd2spring.model.Level; import ru.scarletredman.gd2spring.model.User; import ru.scarletredman.gd2spring.model.UserComment; +import ru.scarletredman.gd2spring.model.embedable.LevelRateInfo; import ru.scarletredman.gd2spring.service.LevelService; import ru.scarletredman.gd2spring.service.MessageService; import ru.scarletredman.gd2spring.service.UserCommentService; import ru.scarletredman.gd2spring.service.UserService; +import java.sql.Timestamp; +import java.time.Instant; @Profile("test") @Configuration @@ -44,8 +47,10 @@ void createTestUser(boolean debugMode) { userCommentService.writeComment(new UserComment(u, "Hello world!!!")); } - var level = createTestLevel(user, "Test level"); - levelService.uploadLevel(level); + for (int i = 0; i < 30; i++) { + var level = createTestLevel(user, "Test level " + i, i, i * 2, i % 4, i % 3 == 0); + levelService.uploadLevel(level); + } var user2 = userService.findUserById(2).get(); for (int i = 0; i < 30; i++) { @@ -54,11 +59,14 @@ void createTestUser(boolean debugMode) { } } - Level createTestLevel(User owner, String name) { + Level createTestLevel(User owner, String name, int likes, int downloads, int stars, boolean featured) { var level = new Level(owner, name); level.setDescription("Hello world!"); level.setObjects(4); + level.setLikes(likes); + level.setDownloads(downloads); + var data = level.getData(); data.setExtra( "0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0_0"); @@ -68,6 +76,13 @@ Level createTestLevel(User owner, String name) { var rate = level.getRate(); rate.setRequestedStars(10); + rate.setStars(stars); + rate.setDifficulty(LevelRateInfo.Difficulty.values()[stars]); + rate.setFeatured(featured); + rate.setEpic(featured); + if (stars != 0 || featured) { + rate.setRateTime(Timestamp.from(Instant.now())); + } return level; } From 030f9c502c76e3d4816913d4ba0066dc8395d0e1 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 2 Oct 2023 22:07:25 +0700 Subject: [PATCH 24/24] Fixed display of level pages info (#3) --- .../java/ru/scarletredman/gd2spring/config/TestConfig.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java index bacc4d9..c63d117 100644 --- a/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java +++ b/src/main/java/ru/scarletredman/gd2spring/config/TestConfig.java @@ -1,5 +1,7 @@ package ru.scarletredman.gd2spring.config; +import java.sql.Timestamp; +import java.time.Instant; import lombok.RequiredArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; @@ -12,8 +14,6 @@ import ru.scarletredman.gd2spring.service.MessageService; import ru.scarletredman.gd2spring.service.UserCommentService; import ru.scarletredman.gd2spring.service.UserService; -import java.sql.Timestamp; -import java.time.Instant; @Profile("test") @Configuration