diff --git a/src/main/java/icu/samnyan/aqua/api/controller/sega/manage/ApiAmazonManageController.java b/src/main/java/icu/samnyan/aqua/api/controller/sega/manage/ApiAmazonManageController.java new file mode 100644 index 00000000..8312a888 --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/api/controller/sega/manage/ApiAmazonManageController.java @@ -0,0 +1,85 @@ +package icu.samnyan.aqua.api.controller.sega.manage; + +import icu.samnyan.aqua.sega.chunithm.model.gamedata.Level; +import icu.samnyan.aqua.sega.chunithm.model.gamedata.Music; +import icu.samnyan.aqua.sega.chunithm.model.userdata.UserData; +import icu.samnyan.aqua.sega.chunithm.model.userdata.UserMusicDetail; +import icu.samnyan.aqua.sega.chunithm.service.GameMusicService; +import icu.samnyan.aqua.sega.chunithm.service.UserDataService; +import icu.samnyan.aqua.sega.chunithm.service.UserMusicDetailService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Optional; + +/** + * @author samnyan (privateamusement@protonmail.com) + */ +@RestController +@RequestMapping("api/manage/chuni/amazon") +public class ApiAmazonManageController { + + private static final Logger logger = LoggerFactory.getLogger(ApiAmazonManageController.class); + + private final UserDataService userDataService; + + private final UserMusicDetailService userMusicDetailService; + + private final GameMusicService gameMusicService; + + public ApiAmazonManageController(UserDataService userDataService, UserMusicDetailService userMusicDetailService, GameMusicService gameMusicService) { + this.userDataService = userDataService; + this.userMusicDetailService = userMusicDetailService; + this.gameMusicService = gameMusicService; + } + + /** + * A request to fill fake score to all chart. only use for testing + * @param aimeId The internal id of a card + * @return Run result status + */ +// @PostMapping("fill") + public ResponseEntity fillMockData(@RequestParam String aimeId) { + UserData profile = userDataService.getUserByExtId(aimeId).orElseThrow(); + List musicList = gameMusicService.getAll(); + List detailList = new ArrayList<>(); + musicList.forEach(x -> { + Collection levels = x.getLevels().values(); + levels.forEach(l -> { + Optional userMusicDetailOptional = userMusicDetailService.getByUserAndMusicIdAndLevel(profile, x.getMusicId(), l.getDiff()); + if (userMusicDetailOptional.isEmpty()) { + UserMusicDetail temp = new UserMusicDetail( + x.getMusicId(), + l.getDiff(), + 1, + 980000, + 0, + 0, + 0, + 5, + 0, + false, + false, + false, + 0, + 0, + 8, + false + ); + temp.setUser(profile); + detailList.add(temp); + } + }); + }); + userMusicDetailService.saveAll(detailList); + return ResponseEntity.ok("OK"); + } + +} diff --git a/src/main/java/icu/samnyan/aqua/sega/aimedb/AimeDbRequestHandler.java b/src/main/java/icu/samnyan/aqua/sega/aimedb/AimeDbRequestHandler.java index d1f163e8..0edef3ba 100644 --- a/src/main/java/icu/samnyan/aqua/sega/aimedb/AimeDbRequestHandler.java +++ b/src/main/java/icu/samnyan/aqua/sega/aimedb/AimeDbRequestHandler.java @@ -91,6 +91,6 @@ public class AimeDbRequestHandler extends ChannelInboundHandlerAdapter { @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { super.channelInactive(ctx); - logger.info("Connection closed"); + logger.debug("Connection closed"); } } diff --git a/src/main/java/icu/samnyan/aqua/sega/chunithm/handler/impl/GetUserMusicHandler.java b/src/main/java/icu/samnyan/aqua/sega/chunithm/handler/impl/GetUserMusicHandler.java index a36bb02f..adda9f85 100644 --- a/src/main/java/icu/samnyan/aqua/sega/chunithm/handler/impl/GetUserMusicHandler.java +++ b/src/main/java/icu/samnyan/aqua/sega/chunithm/handler/impl/GetUserMusicHandler.java @@ -7,10 +7,12 @@ import icu.samnyan.aqua.sega.chunithm.model.userdata.UserMusicDetail; import icu.samnyan.aqua.sega.chunithm.service.GameMusicService; import icu.samnyan.aqua.sega.chunithm.service.UserMusicDetailService; import icu.samnyan.aqua.sega.util.jackson.StringMapper; +import icu.samnyan.aqua.spring.data.OffsetPageRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Component; import java.util.*; @@ -41,11 +43,14 @@ public class GetUserMusicHandler implements BaseHandler { @Override public String handle(Map request) throws JsonProcessingException { String userId = (String) request.get("userId"); - int nextIndex = Integer.parseInt((String) request.get("nextIndex")); + int currentIndex = Integer.parseInt((String) request.get("nextIndex")); int maxCount = Integer.parseInt((String) request.get("maxCount")); - int pageNum = nextIndex / maxCount; + if(currentIndex < 0) { + currentIndex = 0; + } - Page dbPage = userMusicDetailService.getByUser(userId,pageNum,maxCount); + Page dbPage = userMusicDetailService + .getByUser(userId, OffsetPageRequest.of(currentIndex, maxCount, Sort.by("musicId"))); // Convert to result format @@ -64,13 +69,25 @@ public class GetUserMusicHandler implements BaseHandler { list.setLength(list.getUserMusicDetailList().size()); }); + // Remove the last music id if the result length is the same as maxCount, + // to prevent a music id split across multiple page, which will cause some + // problem with the game. + int lastListSize = 0; + if(dbPage.getNumberOfElements() >= maxCount) { + // Get last key + int lastMusicId = userMusicMap.keySet().stream().reduce((a, b) -> b).orElseThrow(); + List lastList = userMusicMap.get(lastMusicId).getUserMusicDetailList(); + lastListSize = lastList.size(); + // Remove last one from map + userMusicMap.remove(lastMusicId); + } - long currentIndex = maxCount * pageNum + dbPage.getNumberOfElements(); + long nextIndex = currentIndex + dbPage.getNumberOfElements() - lastListSize; Map resultMap = new LinkedHashMap<>(); resultMap.put("userId", userId); resultMap.put("length", userMusicMap.size()); - resultMap.put("nextIndex", dbPage.getNumberOfElements() < maxCount ? -1 : currentIndex); + resultMap.put("nextIndex", dbPage.getNumberOfElements() < maxCount ? -1 : nextIndex); resultMap.put("userMusicList", userMusicMap.values()); String json = mapper.write(resultMap); diff --git a/src/main/java/icu/samnyan/aqua/sega/chunithm/service/UserMusicDetailService.java b/src/main/java/icu/samnyan/aqua/sega/chunithm/service/UserMusicDetailService.java index 0acd7348..e2bb3006 100644 --- a/src/main/java/icu/samnyan/aqua/sega/chunithm/service/UserMusicDetailService.java +++ b/src/main/java/icu/samnyan/aqua/sega/chunithm/service/UserMusicDetailService.java @@ -37,8 +37,7 @@ public class UserMusicDetailService { return userMusicDetailRepository.findByUser_Card_ExtId(Integer.parseInt(userId)); } - public Page getByUser(String userId, int pageNum, int maxCount) { - Pageable page = PageRequest.of(pageNum, maxCount); + public Page getByUser(String userId, Pageable page) { return userMusicDetailRepository.findByUser_Card_ExtId(Integer.parseInt(userId), page); } diff --git a/src/main/java/icu/samnyan/aqua/sega/ongeki/handler/impl/GetUserMusicHandler.java b/src/main/java/icu/samnyan/aqua/sega/ongeki/handler/impl/GetUserMusicHandler.java index 4741b74b..1a8f2c23 100644 --- a/src/main/java/icu/samnyan/aqua/sega/ongeki/handler/impl/GetUserMusicHandler.java +++ b/src/main/java/icu/samnyan/aqua/sega/ongeki/handler/impl/GetUserMusicHandler.java @@ -6,15 +6,17 @@ import icu.samnyan.aqua.sega.ongeki.handler.BaseHandler; import icu.samnyan.aqua.sega.ongeki.model.response.data.UserMusicListItem; import icu.samnyan.aqua.sega.ongeki.model.userdata.UserMusicDetail; import icu.samnyan.aqua.sega.util.jackson.BasicMapper; +import icu.samnyan.aqua.spring.data.OffsetPageRequest; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; /** @@ -40,10 +42,13 @@ public class GetUserMusicHandler implements BaseHandler { public String handle(Map request) throws JsonProcessingException { Integer userId = (Integer) request.get("userId"); Integer maxCount = (Integer) request.get("maxCount"); - Integer nextIndex = (Integer) request.get("nextIndex"); - int pageNum = nextIndex / maxCount; + Integer currentIndex = (Integer) request.get("nextIndex"); + if(currentIndex < 0) { + currentIndex = 0; + } - Page dbPage = userMusicDetailRepository.findByUser_Card_ExtId(userId, PageRequest.of(pageNum, maxCount)); + Page dbPage = userMusicDetailRepository + .findByUser_Card_ExtId(userId, OffsetPageRequest.of(currentIndex, maxCount, Sort.by("musicId"))); Map userMusicMap = new LinkedHashMap<>(); dbPage.getContent().forEach(userMusicDetail -> { @@ -58,12 +63,23 @@ public class GetUserMusicHandler implements BaseHandler { list.setLength(list.getUserMusicDetailList().size()); }); - long currentIndex = maxCount * pageNum + dbPage.getNumberOfElements(); + // Someone report that ongeki also has the score missing problem + int lastListSize = 0; + if(dbPage.getNumberOfElements() >= maxCount) { + // Get last key + int lastMusicId = userMusicMap.keySet().stream().reduce((a, b) -> b).orElseThrow(); + List lastList = userMusicMap.get(lastMusicId).getUserMusicDetailList(); + lastListSize = lastList.size(); + // Remove last one from map + userMusicMap.remove(lastMusicId); + } + + long nextIndex = currentIndex + dbPage.getNumberOfElements() - lastListSize; Map resultMap = new LinkedHashMap<>(); resultMap.put("userId", userId); resultMap.put("length", userMusicMap.size()); - resultMap.put("nextIndex", dbPage.getNumberOfElements() < maxCount ? -1 : currentIndex); + resultMap.put("nextIndex", dbPage.getNumberOfElements() < maxCount ? -1 : nextIndex); resultMap.put("userMusicList", userMusicMap.values()); String json = mapper.write(resultMap); diff --git a/src/main/java/icu/samnyan/aqua/spring/data/OffsetPageRequest.java b/src/main/java/icu/samnyan/aqua/spring/data/OffsetPageRequest.java new file mode 100644 index 00000000..ad34e317 --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/spring/data/OffsetPageRequest.java @@ -0,0 +1,156 @@ +package icu.samnyan.aqua.spring.data; + +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; + +import java.io.Serializable; + +/** + * Somehow spring boot jpa doesn't provide a class to use offset to to the pagination + * @author samnyan (privateamusement@protonmail.com) + */ +public class OffsetPageRequest implements Pageable, Serializable { + + private static final long serialVersionUID = 1L; + private final int offset; + private final int limit; + private final Sort sort; + + /** + * Creates a new {@link OffsetPageRequest} with sort parameters applied. + * + * @param offset offset of the request index, must not be negative. + * @param limit the size of the page to be returned, must be greater than 0. + * @param sort must not be {@literal null}, use {@link Sort#unsorted()} instead. + */ + public OffsetPageRequest(int offset, int limit, Sort sort) { + + if (offset < 0) { + throw new IllegalArgumentException("Offset must not be less than zero!"); + } + + if (limit < 1) { + throw new IllegalArgumentException("Limit must not be less than one!"); + } + + Assert.notNull(sort, "Sort must not be null!"); + + this.offset = offset; + this.limit = limit; + this.sort = sort; + } + + public static OffsetPageRequest of(int page, int size) { + return of(page, size, Sort.unsorted()); + } + + public static OffsetPageRequest of(int page, int size, Sort sort) { + return new OffsetPageRequest(page, size, sort); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.domain.Pageable#getPageNumber() + */ + @Override + public int getPageNumber() { + return offset / limit; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.domain.Pageable#getPageSize() + */ + @Override + public int getPageSize() { + return limit; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.domain.Pageable#getOffset() + */ + @Override + public long getOffset() { + return offset; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.domain.Pageable#getSort() + */ + @Override + public Sort getSort() { + return sort; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.domain.Pageable#next() + */ + @Override + public Pageable next() { + return new OffsetPageRequest(Math.toIntExact(getOffset() + getPageSize()), getPageSize(), getSort()); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.domain.Pageable#previousOrFirst() + */ + @Override + public Pageable previousOrFirst() { + return hasPrevious() ? new OffsetPageRequest(Math.toIntExact(getOffset() - getPageSize()), getPageSize(), getSort()) : this; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.domain.Pageable#first() + */ + @Override + public Pageable first() { + return new OffsetPageRequest(0, getPageSize(), getSort()); + } + + @Override + public boolean hasPrevious() { + return offset > limit; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + + final int prime = 31; + int result = 1; + + result = prime * result + offset; + result = prime * result + limit; + + return result; + } + + /* + * (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(@Nullable Object obj) { + + if (this == obj) { + return true; + } + + if (obj == null || getClass() != obj.getClass()) { + return false; + } + + OffsetPageRequest other = (OffsetPageRequest) obj; + return this.offset == other.offset && this.limit == other.limit; + } + +}