Skip to content

Commit

Permalink
Feature/get room list (#72)
Browse files Browse the repository at this point in the history
實作查看房間列表(#72)
  • Loading branch information
m1a2st authored Jun 23, 2023
1 parent 139cc16 commit 52ccd33
Show file tree
Hide file tree
Showing 10 changed files with 235 additions and 10 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package tw.waterballsa.gaas.application.model

class Pagination<T>(
val page: Int,
val offset: Int,
val data: List<T> = emptyList()
)

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package tw.waterballsa.gaas.application.repositories

import tw.waterballsa.gaas.application.model.Pagination
import tw.waterballsa.gaas.domain.Room
import tw.waterballsa.gaas.domain.User

Expand All @@ -9,4 +10,5 @@ interface RoomRepository {
fun findById(roomId: Room.Id): Room?
fun existsByHostId(hostId: User.Id): Boolean
fun joinRoom(room: Room): Room
fun findByStatus(status: Room.Status, page: Pagination<Any>): Pagination<Room>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package tw.waterballsa.gaas.application.usecases

import tw.waterballsa.gaas.application.model.Pagination
import tw.waterballsa.gaas.application.repositories.RoomRepository
import tw.waterballsa.gaas.domain.Room
import javax.inject.Named

@Named
class GetRoomsUseCase(
private val roomRepository: RoomRepository,
) {

fun execute(request: Request, presenter: GetRoomsPresenter) =
roomRepository.findByStatus(request.status, request.toPagination())
.also { presenter.present(it) }

class Request(
val status: Room.Status,
val page: Int,
val offset: Int
)

interface GetRoomsPresenter {
fun present(rooms: Pagination<Room>)
}
}

private fun GetRoomsUseCase.Request.toPagination(): Pagination<Any> =
Pagination(page, offset)
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,28 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal
import org.springframework.security.oauth2.core.oidc.user.OidcUser
import org.springframework.web.bind.annotation.*
import tw.waterballsa.gaas.application.usecases.CreateRoomUsecase
import tw.waterballsa.gaas.application.usecases.GetRoomsUseCase
import tw.waterballsa.gaas.application.usecases.JoinRoomUsecase
import tw.waterballsa.gaas.application.usecases.Presenter
import tw.waterballsa.gaas.domain.GameRegistration
import tw.waterballsa.gaas.domain.Room
import tw.waterballsa.gaas.events.CreatedRoomEvent
import tw.waterballsa.gaas.events.DomainEvent
import tw.waterballsa.gaas.exceptions.PlatformException
import tw.waterballsa.gaas.spring.controllers.RoomController.CreateRoomViewModel
import tw.waterballsa.gaas.spring.controllers.presenter.GetRoomsPresenter
import tw.waterballsa.gaas.spring.controllers.viewmodel.GetRoomsViewModel
import tw.waterballsa.gaas.spring.extensions.getEvent
import javax.validation.Valid
import javax.validation.constraints.Pattern
import tw.waterballsa.gaas.application.usecases.JoinRoomUsecase
import tw.waterballsa.gaas.exceptions.PlatformException
import javax.validation.constraints.Positive

@RestController
@RequestMapping("/rooms")
class RoomController(
private val createRoomUsecase: CreateRoomUsecase,
private val joinRoomUsecase: JoinRoomUsecase
private val joinRoomUsecase: JoinRoomUsecase,
private val getRoomsUseCase: GetRoomsUseCase
) {
@PostMapping
fun createRoom(
Expand All @@ -47,6 +52,18 @@ class RoomController(
return JoinRoomViewModel("success")
}

@GetMapping
fun getRooms(
@RequestParam status: String,
@RequestParam page: Int,
@RequestParam offset: Int
): GetRoomsViewModel {
val request = GetRoomsRequest(status, page, offset)
val presenter = GetRoomsPresenter()
getRoomsUseCase.execute(request.toRequest(), presenter)
return presenter.viewModel
}

class CreateRoomRequest(
private val name: String,
private val gameId: String,
Expand Down Expand Up @@ -115,6 +132,25 @@ class RoomController(
data class JoinRoomViewModel(
val message: String
)

class GetRoomsRequest(
@field:Pattern(
regexp = """^(WAITING|PLAYING)$""",
message = "The status must be either WAITING or PLAYING."
)
val status: String,
@field:Positive(message = "The page must be a positive number.")
val page: Int,
@field:Positive(message = "The offset must be a positive number.")
val offset: Int
) {
fun toRequest(): GetRoomsUseCase.Request =
GetRoomsUseCase.Request(
status = Room.Status.valueOf(status),
page = page,
offset = offset
)
}
}

private fun GameRegistration.toView(): CreateRoomViewModel.Game =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package tw.waterballsa.gaas.spring.controllers.presenter

import tw.waterballsa.gaas.application.model.Pagination
import tw.waterballsa.gaas.application.usecases.GetRoomsUseCase
import tw.waterballsa.gaas.domain.GameRegistration
import tw.waterballsa.gaas.domain.Room
import tw.waterballsa.gaas.spring.controllers.viewmodel.GetRoomsViewModel

class GetRoomsPresenter : GetRoomsUseCase.GetRoomsPresenter {
lateinit var viewModel: GetRoomsViewModel
private set

override fun present(rooms: Pagination<Room>) {
viewModel = rooms.toViewModel()
}

private fun Pagination<Room>.toViewModel(): GetRoomsViewModel =
GetRoomsViewModel(
rooms = data.map { it.toRoomsViewModel() },
page = toPage(data.size)
)
}


private fun Room.toRoomsViewModel(): GetRoomsViewModel.RoomViewModel =
GetRoomsViewModel.RoomViewModel(
id = roomId!!.value,
name = name,
game = game.toGetRoomsView(),
host = host.toGetRoomsView(),
minPlayers = minPlayers,
maxPlayers = maxPlayers,
currentPlayers = players.size,
isLocked = isLocked,
)

private fun GameRegistration.toGetRoomsView(): GetRoomsViewModel.RoomViewModel.Game =
GetRoomsViewModel.RoomViewModel.Game(id!!.value, displayName)

private fun Room.Player.toGetRoomsView(): GetRoomsViewModel.RoomViewModel.Player =
GetRoomsViewModel.RoomViewModel.Player(id.value, nickname)

private fun Pagination<Room>.toPage(size: Int): GetRoomsViewModel.Page =
GetRoomsViewModel.Page(
page = page,
offset = offset,
total = size
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package tw.waterballsa.gaas.spring.controllers.viewmodel

data class GetRoomsViewModel(
val rooms: List<RoomViewModel>,
val page: Page
) {
data class RoomViewModel(
val id: String,
val name: String,
val game: Game,
val host: Player,
val maxPlayers: Int,
val minPlayers: Int,
val currentPlayers: Int,
val isLocked: Boolean,
) {
data class Game(val id: String, val name: String)
data class Player(val id: String, val nickname: String)
}

data class Page(
val total: Int,
val page: Int,
val offset: Int
)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package tw.waterballsa.gaas.spring.repositories

import org.springframework.data.domain.Page
import org.springframework.data.domain.PageRequest
import org.springframework.stereotype.Component
import tw.waterballsa.gaas.application.model.Pagination
import tw.waterballsa.gaas.application.repositories.GameRegistrationRepository
import tw.waterballsa.gaas.application.repositories.RoomRepository
import tw.waterballsa.gaas.application.repositories.UserRepository
Expand Down Expand Up @@ -33,6 +36,12 @@ class SpringRoomRepository(

override fun joinRoom(room: Room): Room = roomDAO.save(room.toData()).toDomain(room.game, room.host, room.players)

override fun findByStatus(status: Room.Status, page: Pagination<Any>): Pagination<Room> {
return roomDAO.findByStatus(status, page.toPageable())
.map { it.toDomain() }
.toPagination()
}

private fun RoomData.toDomain(): Room =
Room(
roomId = Id(id!!),
Expand Down Expand Up @@ -65,3 +74,8 @@ private fun User.toRoomPlayer(): Player =
id = Player.Id(id!!.value),
nickname = nickname
)

private fun Pagination<Any>.toPageable() = PageRequest.of(page, offset)

private fun <T> Page<T>.toPagination(): Pagination<T> =
Pagination(pageable.pageNumber, pageable.pageSize, content)
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package tw.waterballsa.gaas.spring.repositories.dao

import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.data.mongodb.repository.MongoRepository
import org.springframework.stereotype.Repository
import tw.waterballsa.gaas.domain.Room
import tw.waterballsa.gaas.spring.repositories.data.RoomData

@Repository
interface RoomDAO : MongoRepository<RoomData, String> {
fun existsByHostId(hostId: String): Boolean
fun findByStatus(status: Room.Status, pageable: Pageable): Page<RoomData>
}

Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@ import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser
import org.springframework.security.oauth2.core.oidc.user.OidcUser
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.oidcLogin
import org.springframework.test.web.servlet.ResultActions
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import tw.waterballsa.gaas.application.model.Pagination
import tw.waterballsa.gaas.application.repositories.GameRegistrationRepository
import tw.waterballsa.gaas.application.repositories.RoomRepository
import tw.waterballsa.gaas.application.repositories.UserRepository
Expand All @@ -21,6 +23,7 @@ import tw.waterballsa.gaas.domain.Room.Player
import tw.waterballsa.gaas.domain.User
import tw.waterballsa.gaas.spring.it.AbstractSpringBootTest
import tw.waterballsa.gaas.spring.models.TestCreateRoomRequest
import tw.waterballsa.gaas.spring.models.TestGetRoomsRequest
import tw.waterballsa.gaas.spring.models.TestJoinRoomRequest
import java.time.Instant.now

Expand Down Expand Up @@ -81,7 +84,6 @@ class RoomControllerTest @Autowired constructor(
val request = createRoomRequest("1234")
createRoom(request)
.thenCreateRoomSuccessfully(request)

createRoom(request)
.andExpect(status().isBadRequest)
.andExpect(jsonPath("$.message").value("A user can only create one room at a time."))
Expand Down Expand Up @@ -117,6 +119,51 @@ class RoomControllerTest @Autowired constructor(
.thenJoinRoomSuccessfully()
}

@Test
fun givenWaitingRoomBAndWaitingRoomC_WhenUserAVisitLobby_ThenShouldHaveRoomBAndRoomC() {
val userA = testUser
val userB = createUser("2", "[email protected]", "winner1122")
val userC = createUser("3", "[email protected]", "winner1234")
val request = TestGetRoomsRequest("WAITING", 0, 10)

givenWaitingRooms(userB, userC)
request.whenUserAVisitLobby(userA)
.thenShouldHaveRooms(request)
}

private fun TestGetRoomsRequest.whenUserAVisitLobby(joinUser: User): ResultActions =
mockMvc.perform(
get("/rooms")
.with(oidcLogin().oidcUser(mockOidcUser(joinUser)))
.param("status", status)
.param("page", page.toString())
.param("offset", offset.toString())
)

private fun givenWaitingRooms(vararg users: User) =
users.forEach { givenTheHostCreatePublicRoom(it) }

private fun ResultActions.thenShouldHaveRooms(request: TestGetRoomsRequest) {
val rooms = roomRepository.findByStatus(request.toStatus(), request.toPagination())
andExpect(status().isOk)
.andExpect(jsonPath("$.rooms").isArray)
.andExpect(jsonPath("$.rooms.length()").value(rooms.data.size))
.roomExcept(rooms)
}

private fun ResultActions.roomExcept(rooms: Pagination<Room>) {
rooms.data.forEachIndexed() { index, room ->
andExpect(jsonPath("$.rooms[$index].id").value(room.roomId!!.value))
.andExpect(jsonPath("$.rooms[$index].name").value(room.name))
.andExpect(jsonPath("$.rooms[$index].game.id").value(room.game.id!!.value))
.andExpect(jsonPath("$.rooms[$index].host.id").value(room.host.id.value))
.andExpect(jsonPath("$.rooms[$index].isLocked").value(room.isLocked))
.andExpect(jsonPath("$.rooms[$index].currentPlayers").value(room.players.size))
.andExpect(jsonPath("$.rooms[$index].maxPlayers").value(room.maxPlayers))
.andExpect(jsonPath("$.rooms[$index].minPlayers").value(room.minPlayers))
}
}

private fun createRoom(request: TestCreateRoomRequest): ResultActions =
mockMvc.perform(
post("/rooms")
Expand All @@ -133,17 +180,16 @@ class RoomControllerTest @Autowired constructor(

private fun givenTheHostCreatePublicRoom(host: User): Room {
testRoom = createRoom(host)
return testRoom
return testRoom
}

private fun givenTheHostCreateRoomWithPassword(host: User, password: String): Room {
testRoom = createRoom(host, password)
return testRoom

}

private fun Room.whenUserJoinTheRoom(user: User, password: String? = null):ResultActions{
val request = joinRoomRequest(password);
private fun Room.whenUserJoinTheRoom(user: User, password: String? = null): ResultActions {
val request = joinRoomRequest(password)
val joinUser = mockOidcUser(user)
return joinRoom(request, joinUser)
}
Expand Down Expand Up @@ -191,7 +237,7 @@ class RoomControllerTest @Autowired constructor(
)
)

private fun createRoom(host : User, password: String? = null): Room = roomRepository.createRoom(
private fun createRoom(host: User, password: String? = null): Room = roomRepository.createRoom(
Room(
game = testGame,
host = Player(Player.Id(host.id!!.value), host.nickname),
Expand Down Expand Up @@ -229,5 +275,4 @@ class RoomControllerTest @Autowired constructor(
TestJoinRoomRequest(
password = password
)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package tw.waterballsa.gaas.spring.models

import tw.waterballsa.gaas.application.model.Pagination
import tw.waterballsa.gaas.domain.Room

class TestGetRoomsRequest(
val status: String,
val page: Int,
val offset: Int
) {
fun toStatus() : Room.Status = Room.Status.valueOf(status)
fun toPagination(): Pagination<Any> = Pagination(page, offset)
}

0 comments on commit 52ccd33

Please sign in to comment.