From 7d1234af6c0285bd3313e137d980228b8a237b3f Mon Sep 17 00:00:00 2001 From: Bhoopesh Date: Fri, 2 Feb 2024 20:14:46 +0530 Subject: [PATCH] update: pagination for get user-matches --- docs/spec/CodeCharacter-API.yml | 121 +++++++++++++++++- .../codecharacter/core/DailyChallengesApi.kt | 20 +++ .../delta/codecharacter/core/MatchApi.kt | 29 ++++- .../DailyChallengeController.kt | 7 + .../server/match/MatchController.kt | 17 ++- .../server/match/MatchRepository.kt | 3 +- .../server/match/MatchService.kt | 109 ++++++++-------- .../server/match/PvPMatchRepository.kt | 3 +- 8 files changed, 246 insertions(+), 63 deletions(-) diff --git a/docs/spec/CodeCharacter-API.yml b/docs/spec/CodeCharacter-API.yml index f1c4088..6774c65 100644 --- a/docs/spec/CodeCharacter-API.yml +++ b/docs/spec/CodeCharacter-API.yml @@ -989,7 +989,7 @@ paths: schema: type: array items: - type: object + $ref: '#/components/schemas/Match' examples: Example: value: @@ -1016,8 +1016,19 @@ paths: avatarId: 0 '401': description: Unauthorized - operationId: getUserMatches - description: Get matches played by authenticated user + operationId: getUserNormalMatches + description: Get normal matches played by authenticated user + parameters: + - schema: + type: integer + in: query + name: page + description: Index of the page + - schema: + type: integer + in: query + name: size + description: Size of the page parameters: [] post: summary: Create match @@ -1054,6 +1065,60 @@ paths: opponentId: 0a4b34b0-6057-4b82-ae27-a59c36eab667 mapRevisionId: f52ddf9e-e933-471a-9250-41078cc39f80 codeRevisionId: d9eb9923-651b-4ec4-b6f1-b7625b2a9392 + /user/pvpmatches: + get: + summary: Get user pvp matches + tags: + - match + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/PvPMatch' + examples: + Example: + value: + - id: 497f6eca-6276-4993-bfeb-53cbbbba6f08 + - game: + - id: 497f6eca-6276-4993-bfeb-53cbbbba6f08 + scorePlayer1: 100 + scorePlayer2: 45 + status: IDLE + - matchMode: PVPSELF + - matchVerdict: PLAYER1 + - createdAt: '2019-08-24T14:15:22Z' + - user1: + username: testUser + name: Test User + country: IN + college: NIT Trichy + avatarId: 0 + - user2: + username: testUser + name: Test User + country: IN + college: NIT Trichy + avatarId: 0 + '401': + description: Unauthorized + operationId: getUserPvPMatches + description: Get pvp matches played by authenticated user + parameters: + - schema: + type: integer + in: query + name: page + description: Index of the page + - schema: + type: integer + in: query + name: size + description: Size of the page + parameters: [] /user/notifications: get: summary: Get all notifications @@ -1311,7 +1376,55 @@ paths: value: "[[0,0,0]]" tags: - Daily Challenges - + /dc/matches: + get: + summary: Get user daily challenge matches + tags: + - Daily Challenges + responses: + '200': + description: OK + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Match' + examples: + Example: + value: + - id: 497f6eca-6276-4993-bfeb-53cbbbba6f08 + games: + - id: 497f6eca-6276-4993-bfeb-53cbbbba6f08 + coinsUsed: 100 + destruction: 45 + status: IDLE + matchMode: SELF + matchVerdict: SUCCESS + createdAt: '2019-08-24T14:15:22Z' + user1: + username: testUser + name: Test User + country: IN + college: NIT Trichy + avatarId: 0 + user2: null + '401': + description: Unauthorized + operationId: getUserDCMatches + description: Get daily-challenge matches played by authenticated user + parameters: + - schema: + type: integer + in: query + name: page + description: Index of the page + - schema: + type: integer + in: query + name: size + description: Size of the page + parameters: [] components: schemas: diff --git a/library/src/main/kotlin/delta/codecharacter/core/DailyChallengesApi.kt b/library/src/main/kotlin/delta/codecharacter/core/DailyChallengesApi.kt index 01e519c..383e989 100644 --- a/library/src/main/kotlin/delta/codecharacter/core/DailyChallengesApi.kt +++ b/library/src/main/kotlin/delta/codecharacter/core/DailyChallengesApi.kt @@ -9,6 +9,7 @@ import delta.codecharacter.dtos.DailyChallengeGetRequestDto import delta.codecharacter.dtos.DailyChallengeLeaderBoardResponseDto import delta.codecharacter.dtos.DailyChallengeMatchRequestDto import delta.codecharacter.dtos.GenericErrorDto +import delta.codecharacter.dtos.MatchDto import io.swagger.v3.oas.annotations.* import io.swagger.v3.oas.annotations.enums.* import io.swagger.v3.oas.annotations.media.* @@ -102,4 +103,23 @@ interface DailyChallengesApi { fun getDailyChallengeLeaderBoard(@Parameter(description = "Index of the page") @Valid @RequestParam(value = "page", required = false) page: kotlin.Int?,@Parameter(description = "Size of the page") @Valid @RequestParam(value = "size", required = false) size: kotlin.Int?): ResponseEntity> { return ResponseEntity(HttpStatus.NOT_IMPLEMENTED) } + + @Operation( + summary = "Get user daily challenge matches", + operationId = "getUserDCMatches", + description = """Get daily-challenge matches played by authenticated user""", + responses = [ + ApiResponse(responseCode = "200", description = "OK", content = [Content(array = ArraySchema(schema = Schema(implementation = MatchDto::class)))]), + ApiResponse(responseCode = "401", description = "Unauthorized") + ], + security = [ SecurityRequirement(name = "http-bearer") ] + ) + @RequestMapping( + method = [RequestMethod.GET], + value = ["/dc/matches"], + produces = ["application/json"] + ) + fun getUserDCMatches(@Parameter(description = "Index of the page") @Valid @RequestParam(value = "page", required = false) page: kotlin.Int?,@Parameter(description = "Size of the page") @Valid @RequestParam(value = "size", required = false) size: kotlin.Int?): ResponseEntity> { + return ResponseEntity(HttpStatus.NOT_IMPLEMENTED) + } } diff --git a/library/src/main/kotlin/delta/codecharacter/core/MatchApi.kt b/library/src/main/kotlin/delta/codecharacter/core/MatchApi.kt index 80255bf..033cfff 100644 --- a/library/src/main/kotlin/delta/codecharacter/core/MatchApi.kt +++ b/library/src/main/kotlin/delta/codecharacter/core/MatchApi.kt @@ -7,6 +7,8 @@ package delta.codecharacter.core import delta.codecharacter.dtos.CreateMatchRequestDto import delta.codecharacter.dtos.GenericErrorDto +import delta.codecharacter.dtos.MatchDto +import delta.codecharacter.dtos.PvPMatchDto import io.swagger.v3.oas.annotations.* import io.swagger.v3.oas.annotations.enums.* import io.swagger.v3.oas.annotations.media.* @@ -80,10 +82,10 @@ interface MatchApi { @Operation( summary = "Get user matches", - operationId = "getUserMatches", - description = """Get matches played by authenticated user""", + operationId = "getUserNormalMatches", + description = """Get normal matches played by authenticated user""", responses = [ - ApiResponse(responseCode = "200", description = "OK", content = [Content(array = ArraySchema(schema = Schema(implementation = kotlin.Any::class)))]), + ApiResponse(responseCode = "200", description = "OK", content = [Content(array = ArraySchema(schema = Schema(implementation = MatchDto::class)))]), ApiResponse(responseCode = "401", description = "Unauthorized") ], security = [ SecurityRequirement(name = "http-bearer") ] @@ -93,7 +95,26 @@ interface MatchApi { value = ["/user/matches"], produces = ["application/json"] ) - fun getUserMatches(): ResponseEntity> { + fun getUserNormalMatches(@Parameter(description = "Index of the page") @Valid @RequestParam(value = "page", required = false) page: kotlin.Int?,@Parameter(description = "Size of the page") @Valid @RequestParam(value = "size", required = false) size: kotlin.Int?): ResponseEntity> { + return ResponseEntity(HttpStatus.NOT_IMPLEMENTED) + } + + @Operation( + summary = "Get user pvp matches", + operationId = "getUserPvPMatches", + description = """Get pvp matches played by authenticated user""", + responses = [ + ApiResponse(responseCode = "200", description = "OK", content = [Content(array = ArraySchema(schema = Schema(implementation = PvPMatchDto::class)))]), + ApiResponse(responseCode = "401", description = "Unauthorized") + ], + security = [ SecurityRequirement(name = "http-bearer") ] + ) + @RequestMapping( + method = [RequestMethod.GET], + value = ["/user/pvpmatches"], + produces = ["application/json"] + ) + fun getUserPvPMatches(@Parameter(description = "Index of the page") @Valid @RequestParam(value = "page", required = false) page: kotlin.Int?,@Parameter(description = "Size of the page") @Valid @RequestParam(value = "size", required = false) size: kotlin.Int?): ResponseEntity> { return ResponseEntity(HttpStatus.NOT_IMPLEMENTED) } } diff --git a/server/src/main/kotlin/delta/codecharacter/server/daily_challenge/DailyChallengeController.kt b/server/src/main/kotlin/delta/codecharacter/server/daily_challenge/DailyChallengeController.kt index 1dc1b95..2195278 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/daily_challenge/DailyChallengeController.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/daily_challenge/DailyChallengeController.kt @@ -4,6 +4,7 @@ import delta.codecharacter.core.DailyChallengesApi import delta.codecharacter.dtos.DailyChallengeGetRequestDto import delta.codecharacter.dtos.DailyChallengeLeaderBoardResponseDto import delta.codecharacter.dtos.DailyChallengeMatchRequestDto +import delta.codecharacter.dtos.MatchDto import delta.codecharacter.server.match.MatchService import delta.codecharacter.server.user.UserEntity import delta.codecharacter.server.user.public_user.PublicUserService @@ -38,4 +39,10 @@ class DailyChallengeController( val user = SecurityContextHolder.getContext().authentication.principal as UserEntity return ResponseEntity.ok(matchService.createDCMatch(user.id, dailyChallengeMatchRequestDto)) } + + @Secured(value = ["ROLE_USER"]) + override fun getUserDCMatches(page: Int?, size: Int?): ResponseEntity> { + val user = SecurityContextHolder.getContext().authentication.principal as UserEntity + return ResponseEntity.ok(matchService.getUserDCMatches(user.id, page, size)) + } } diff --git a/server/src/main/kotlin/delta/codecharacter/server/match/MatchController.kt b/server/src/main/kotlin/delta/codecharacter/server/match/MatchController.kt index 4d6e7ee..cc5e9e7 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/match/MatchController.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/match/MatchController.kt @@ -4,6 +4,7 @@ import delta.codecharacter.core.MatchApi import delta.codecharacter.dtos.CreateMatchRequestDto import delta.codecharacter.dtos.MatchDto import delta.codecharacter.dtos.MatchModeDto +import delta.codecharacter.dtos.PvPMatchDto import delta.codecharacter.server.exception.CustomException import delta.codecharacter.server.user.UserEntity import org.springframework.beans.factory.annotation.Autowired @@ -32,8 +33,20 @@ class MatchController(@Autowired private val matchService: MatchService) : Match } @Secured("ROLE_USER") - override fun getUserMatches(): ResponseEntity> { + override fun getUserNormalMatches( + page: Int?, + size: Int?, + ): ResponseEntity> { val user = SecurityContextHolder.getContext().authentication.principal as UserEntity - return ResponseEntity.ok(matchService.getUserMatches(user.id)) + return ResponseEntity.ok(matchService.getUserNormalMatches(user.id, page, size)) + } + + @Secured("ROLE_USER") + override fun getUserPvPMatches( + page: Int?, + size: Int?, + ): ResponseEntity> { + val user = SecurityContextHolder.getContext().authentication.principal as UserEntity + return ResponseEntity.ok(matchService.getUserPvPMatches(user.id, page, size)) } } diff --git a/server/src/main/kotlin/delta/codecharacter/server/match/MatchRepository.kt b/server/src/main/kotlin/delta/codecharacter/server/match/MatchRepository.kt index a5d94a5..9e84fc2 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/match/MatchRepository.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/match/MatchRepository.kt @@ -1,6 +1,7 @@ package delta.codecharacter.server.match import delta.codecharacter.server.user.public_user.PublicUserEntity +import org.springframework.data.domain.PageRequest import org.springframework.data.mongodb.repository.MongoRepository import org.springframework.stereotype.Repository import java.util.UUID @@ -8,6 +9,6 @@ import java.util.UUID @Repository interface MatchRepository : MongoRepository { fun findTop10ByOrderByTotalPointsDesc(): List - fun findByPlayer1OrderByCreatedAtDesc(player1: PublicUserEntity): List + fun findByPlayer1OrderByCreatedAtDesc(player1: PublicUserEntity, pageRequest: PageRequest): List fun findByIdIn(matchIds: List): List } diff --git a/server/src/main/kotlin/delta/codecharacter/server/match/MatchService.kt b/server/src/main/kotlin/delta/codecharacter/server/match/MatchService.kt index 951f864..1d015a9 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/match/MatchService.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/match/MatchService.kt @@ -2,6 +2,7 @@ package delta.codecharacter.server.match import com.fasterxml.jackson.databind.ObjectMapper import delta.codecharacter.dtos.* +import delta.codecharacter.server.code.Code import delta.codecharacter.server.code.LanguageEnum import delta.codecharacter.server.code.code_revision.CodeRevisionService import delta.codecharacter.server.code.latest_code.LatestCodeService @@ -32,6 +33,8 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import org.springframework.amqp.rabbit.annotation.RabbitListener import org.springframework.beans.factory.annotation.Autowired +import org.springframework.data.domain.PageRequest +import org.springframework.data.domain.Sort import org.springframework.http.HttpStatus import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder import org.springframework.messaging.simp.SimpMessagingTemplate @@ -69,21 +72,23 @@ class MatchService( private var mapper: ObjectMapper = jackson2ObjectMapperBuilder.build() private val logger: Logger = LoggerFactory.getLogger(MatchService::class.java) - private fun createNormalSelfMatch(userId: UUID, codeRevisionId: UUID?, mapRevisionId: UUID?) { - val code: String - val language: LanguageEnum - if (codeRevisionId == null) { - val latestCode = latestCodeService.getLatestCode(userId, CodeTypeDto.NORMAL) - code = latestCode.code - language = LanguageEnum.valueOf(latestCode.language.name) - } else { - val codeRevision = - codeRevisionService.getCodeRevisions(userId, CodeTypeDto.NORMAL).find { it.id == codeRevisionId } - ?: throw CustomException(HttpStatus.BAD_REQUEST, "Invalid revision ID") - code = codeRevision.code - language = LanguageEnum.valueOf(codeRevision.language.name) + private fun getCodeFromRevision(userId: UUID, codeRevisionId: UUID?, codeType: CodeTypeDto): Pair { + when (codeRevisionId) { + null -> { + val latestCode = latestCodeService.getLatestCode(userId, codeType) + return Pair(latestCode.code, LanguageEnum.valueOf(latestCode.language.name)) + } + else -> { + val codeRevision = + codeRevisionService.getCodeRevisions(userId, codeType).find { it.id == codeRevisionId} + ?: throw CustomException(HttpStatus.BAD_REQUEST, "Invalid revision ID") + return Pair(codeRevision.code, LanguageEnum.valueOf(codeRevision.language.name)) + } } + } + private fun createNormalSelfMatch(userId: UUID, codeRevisionId: UUID?, mapRevisionId: UUID?) { + val (code, language) = getCodeFromRevision(userId, codeRevisionId, CodeTypeDto.NORMAL) val map: String = if (mapRevisionId == null) { val latestMap = latestMapService.getLatestMap(userId) @@ -114,40 +119,11 @@ class MatchService( } private fun createPvPSelfMatch(userId: UUID, codeRevisionId1: UUID?, codeRevisionId2: UUID?) { - val code1: String - val code2: String - val language1: LanguageEnum - val language2: LanguageEnum - - if (codeRevisionId1 == null && codeRevisionId2 == null) { - throw CustomException(HttpStatus.BAD_REQUEST, "At least one revision ID is required") - } - - if (codeRevisionId1 == null) { - val latestCode = latestCodeService.getLatestCode(userId, CodeTypeDto.PVP) - code1 = latestCode.code - language1 = LanguageEnum.valueOf(latestCode.language.name) - } else { - val codeRevision = - codeRevisionService.getCodeRevisions(userId, CodeTypeDto.PVP).find { it.id == codeRevisionId1 } - ?: throw CustomException(HttpStatus.BAD_REQUEST, "Invalid revision ID") - code1 = codeRevision.code - language1 = LanguageEnum.valueOf(codeRevision.language.name) + if (codeRevisionId1==codeRevisionId2) { + throw CustomException(HttpStatus.BAD_REQUEST, "Codes must be different") } - - if (codeRevisionId2 == null) { - val latestCode = latestCodeService.getLatestCode(userId, CodeTypeDto.PVP) - code2 = latestCode.code - language2 = LanguageEnum.valueOf(latestCode.language.name) - } - else { - val codeRevision = - codeRevisionService.getCodeRevisions(userId, CodeTypeDto.PVP).find { it.id == codeRevisionId2 } - ?: throw CustomException(HttpStatus.BAD_REQUEST, "Invalid revision ID") - code2 = codeRevision.code - language2 = LanguageEnum.valueOf(codeRevision.language.name) - } - + val (code1, language1) = getCodeFromRevision(userId, codeRevisionId1, CodeTypeDto.PVP) + val (code2, language2) = getCodeFromRevision(userId, codeRevisionId2, CodeTypeDto.PVP) val matchId = UUID.randomUUID() val game = pvPGameService.createPvPGame(matchId) val publicUser = publicUserService.getPublicUser(userId) @@ -449,16 +425,47 @@ class MatchService( return listOf(mapMatchEntitiesToDtos(matches) + mapPvPMatchEntitiesToDtos(pvPMatches)) } - fun getUserMatches(userId: UUID): List { + fun getUserNormalMatches(userId: UUID, page: Int?, size: Int?): List { + val publicUser = publicUserService.getPublicUser(userId) + val pageRequest = + PageRequest.of( + page ?: 0, + size ?: 10, + Sort.by(Sort.Order.desc("createdAt")), + ) + + val matches = matchRepository.findByPlayer1OrderByCreatedAtDesc(publicUser, pageRequest) + + return mapMatchEntitiesToDtos(matches) + } + + fun getUserDCMatches(userId: UUID, page: Int?, size: Int?): List { val publicUser = publicUserService.getPublicUser(userId) - val matches = matchRepository.findByPlayer1OrderByCreatedAtDesc(publicUser) - val pvPMatches = pvPMatchRepository.findByPlayer1OrderByCreatedAtDesc(publicUser) + val pageRequest = + PageRequest.of( + page ?: 0, + size ?: 10, + Sort.by(Sort.Order.desc("createdAt")), + ) + val dcMatches = dailyChallengeMatchRepository.findByUserOrderByCreatedAtDesc(publicUser).takeWhile { Duration.between(it.createdAt, Instant.now()).toHours() < 24 && - it.verdict != DailyChallengeMatchVerdictEnum.STARTED + it.verdict != DailyChallengeMatchVerdictEnum.STARTED } - return mapDailyChallengeMatchEntitiesToDtos(dcMatches) + mapMatchEntitiesToDtos(matches) + mapPvPMatchEntitiesToDtos(pvPMatches) + return mapDailyChallengeMatchEntitiesToDtos(dcMatches) + } + + fun getUserPvPMatches(userId: UUID, page: Int?, size: Int?): List { + val publicUser = publicUserService.getPublicUser(userId) + val pageRequest = + PageRequest.of( + page ?: 0, + size ?: 10, + Sort.by(Sort.Order.desc("createdAt")), + ) + val pvPMatches = pvPMatchRepository.findByPlayer1OrderByCreatedAtDesc(publicUser, pageRequest) + return mapPvPMatchEntitiesToDtos(pvPMatches) } @RabbitListener(queues = ["gameStatusUpdateQueue"], ackMode = "AUTO") diff --git a/server/src/main/kotlin/delta/codecharacter/server/match/PvPMatchRepository.kt b/server/src/main/kotlin/delta/codecharacter/server/match/PvPMatchRepository.kt index 5afa259..cc0aad8 100644 --- a/server/src/main/kotlin/delta/codecharacter/server/match/PvPMatchRepository.kt +++ b/server/src/main/kotlin/delta/codecharacter/server/match/PvPMatchRepository.kt @@ -1,6 +1,7 @@ package delta.codecharacter.server.match import delta.codecharacter.server.user.public_user.PublicUserEntity +import org.springframework.data.domain.PageRequest import org.springframework.data.mongodb.repository.MongoRepository import org.springframework.stereotype.Repository import java.util.UUID @@ -8,5 +9,5 @@ import java.util.UUID @Repository interface PvPMatchRepository : MongoRepository { fun findTop10ByOrderByTotalPointsDesc(): List - fun findByPlayer1OrderByCreatedAtDesc(player1: PublicUserEntity): List + fun findByPlayer1OrderByCreatedAtDesc(player1: PublicUserEntity, pageRequest: PageRequest): List }