diff --git a/Application-Module/src/main/kotlin/com/asap/application/space/port/in/UpdateSpaceIndexUsecase.kt b/Application-Module/src/main/kotlin/com/asap/application/space/port/in/UpdateSpaceIndexUsecase.kt deleted file mode 100644 index 67327fcc..00000000 --- a/Application-Module/src/main/kotlin/com/asap/application/space/port/in/UpdateSpaceIndexUsecase.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.asap.application.space.port.`in` - -interface UpdateSpaceIndexUsecase { - fun update(command: Command) - - data class Command( - val userId: String, - val orders: List, - ) { - data class SpaceOrder( - val spaceId: String, - val index: Int, - ) - } -} diff --git a/Application-Module/src/main/kotlin/com/asap/application/space/port/in/UpdateSpaceUsecase.kt b/Application-Module/src/main/kotlin/com/asap/application/space/port/in/UpdateSpaceUsecase.kt new file mode 100644 index 00000000..622e3c73 --- /dev/null +++ b/Application-Module/src/main/kotlin/com/asap/application/space/port/in/UpdateSpaceUsecase.kt @@ -0,0 +1,25 @@ +package com.asap.application.space.port.`in` + +interface UpdateSpaceUsecase { + fun update(command: Command.Index) + + fun update(command: Command.Main) + + + sealed class Command { + data class Index( + val userId: String, + val orders: List, + ) : Command() + + data class SpaceOrder( + val spaceId: String, + val index: Int, + ) + + data class Main( + val userId: String, + val spaceId: String, + ) : Command() + } +} diff --git a/Application-Module/src/main/kotlin/com/asap/application/space/port/out/SpaceManagementPort.kt b/Application-Module/src/main/kotlin/com/asap/application/space/port/out/SpaceManagementPort.kt index 88c35382..b9973535 100644 --- a/Application-Module/src/main/kotlin/com/asap/application/space/port/out/SpaceManagementPort.kt +++ b/Application-Module/src/main/kotlin/com/asap/application/space/port/out/SpaceManagementPort.kt @@ -1,7 +1,6 @@ package com.asap.application.space.port.out import com.asap.domain.common.DomainId -import com.asap.domain.space.entity.IndexedSpace import com.asap.domain.space.entity.MainSpace import com.asap.domain.space.entity.Space @@ -13,13 +12,6 @@ interface SpaceManagementPort { spaceId: DomainId, ): Space - fun getIndexedSpaceNotNull( - userId: DomainId, - spaceId: DomainId, - ): IndexedSpace - - fun getAllIndexedSpace(userId: DomainId): List - fun getAllSpaceBy( userId: DomainId, spaceIds: List, @@ -29,14 +21,9 @@ interface SpaceManagementPort { fun save(space: Space): Space - fun update(space: Space): Space - - fun update(indexedSpace: IndexedSpace): IndexedSpace + fun saveAll(spaces: List): List - fun updateIndexes( - userId: DomainId, - orders: List, - ) + fun update(space: Space): Space fun deleteBy(space: Space) diff --git a/Application-Module/src/main/kotlin/com/asap/application/space/service/SpaceCommandService.kt b/Application-Module/src/main/kotlin/com/asap/application/space/service/SpaceCommandService.kt index ab72e2ff..18561a94 100644 --- a/Application-Module/src/main/kotlin/com/asap/application/space/service/SpaceCommandService.kt +++ b/Application-Module/src/main/kotlin/com/asap/application/space/service/SpaceCommandService.kt @@ -3,8 +3,8 @@ package com.asap.application.space.service import com.asap.application.space.exception.SpaceException import com.asap.application.space.port.`in`.CreateSpaceUsecase import com.asap.application.space.port.`in`.DeleteSpaceUsecase -import com.asap.application.space.port.`in`.UpdateSpaceIndexUsecase import com.asap.application.space.port.`in`.UpdateSpaceNameUsecase +import com.asap.application.space.port.`in`.UpdateSpaceUsecase import com.asap.application.space.port.out.SpaceManagementPort import com.asap.common.exception.DefaultException import com.asap.domain.common.DomainId @@ -20,53 +20,53 @@ class SpaceCommandService( ) : CreateSpaceUsecase, UpdateSpaceNameUsecase, DeleteSpaceUsecase, - UpdateSpaceIndexUsecase { + UpdateSpaceUsecase { private val spaceIndexValidator: SpaceIndexValidator = SpaceIndexValidator() override fun create(command: CreateSpaceUsecase.Command) { - Space - .create( - userId = DomainId(command.userId), - name = command.spaceName, - templateType = command.templateType, - ).apply { - spaceManagementPort.save(this) - } - reIndexingSpaceOrder(DomainId(command.userId)) - } + val userId = DomainId(command.userId) + Space.create( + userId = userId, + name = command.spaceName, + templateType = command.templateType, + ).apply { + spaceManagementPort.save(this) + } - override fun update(command: UpdateSpaceNameUsecase.Command) { - val space = - spaceManagementPort.getSpaceNotNull( - userId = DomainId(command.userId), - spaceId = DomainId(command.spaceId), - ) - space.updateName(command.name) - spaceManagementPort.update(space) + // TODO 동시성 문제가 발생한다면? + if (spaceManagementPort.countByUserId(userId) == 1L) { + updateMainSpace(userId) + } + + reIndexingSpaceOrder(userId) } override fun deleteOne(command: DeleteSpaceUsecase.DeleteOneCommand) { + val userId = DomainId(command.userId) spaceManagementPort .getSpaceNotNull( - userId = DomainId(command.userId), + userId = userId, spaceId = DomainId(command.spaceId), ).apply { delete() spaceManagementPort.deleteBy(this) } - reIndexingSpaceOrder(DomainId(command.userId)) + updateMainSpace(userId) + reIndexingSpaceOrder(userId) } override fun deleteAllBy(command: DeleteSpaceUsecase.DeleteAllCommand) { + val userId = DomainId(command.userId) spaceManagementPort .getAllSpaceBy( - userId = DomainId(command.userId), + userId = userId, spaceIds = command.spaceIds.map { DomainId(it) }, ).forEach { it.delete() spaceManagementPort.deleteBy(it) } - reIndexingSpaceOrder(DomainId(command.userId)) + updateMainSpace(userId) + reIndexingSpaceOrder(userId) } override fun deleteAllBy(command: DeleteSpaceUsecase.DeleteAllUser) { @@ -76,35 +76,71 @@ class SpaceCommandService( } } - override fun update(command: UpdateSpaceIndexUsecase.Command) { - val indexedSpaces = spaceManagementPort.getAllIndexedSpace(DomainId(command.userId)) + override fun update(command: UpdateSpaceUsecase.Command.Index) { + val spaces = spaceManagementPort.getAllSpaceBy(DomainId(command.userId)) val changeIndexMap = command.orders.associateBy({ DomainId(it.spaceId) }, { it.index }) try { spaceIndexValidator.validate( - indexedSpaces = indexedSpaces, + spaces = spaces, validateIndex = changeIndexMap, ) } catch (e: DefaultException.InvalidArgumentException) { - throw SpaceException.InvalidSpaceUpdateException() + throw SpaceException.InvalidSpaceUpdateException(message = e.message) } - indexedSpaces.map { + spaces.map { it.updateIndex(changeIndexMap.getValue(it.id)) } - spaceManagementPort.updateIndexes( + spaceManagementPort.saveAll(spaces) + } + + override fun update(command: UpdateSpaceUsecase.Command.Main) { + val spaces = spaceManagementPort.getAllSpaceBy( userId = DomainId(command.userId), - orders = indexedSpaces, - ) + ).onEach { + if (it.id.value == command.spaceId) { + it.updateToMain() + } else { + it.updateToSub() + } + } + + spaceManagementPort.saveAll(spaces) + } + + override fun update(command: UpdateSpaceNameUsecase.Command) { + val space = + spaceManagementPort.getSpaceNotNull( + userId = DomainId(command.userId), + spaceId = DomainId(command.spaceId), + ) + + space.updateName(command.name) + spaceManagementPort.update(space) } private fun reIndexingSpaceOrder(userId: DomainId) { - spaceManagementPort - .getAllIndexedSpace(userId) + val spaces = spaceManagementPort + .getAllSpaceBy(userId) .sortedBy { it.index } - .forEachIndexed { index, indexedSpace -> - indexedSpace.updateIndex(index) - spaceManagementPort.update(indexedSpace) + .onEachIndexed { index, space -> + space.updateIndex(index) } + spaceManagementPort.saveAll(spaces) + } + + + private fun updateMainSpace(userId: DomainId) { + val spaces = spaceManagementPort.getAllSpaceBy(userId) + + if (spaces.isEmpty()) { + return + } + + spaces.forEach { it.updateToSub() } + spaces.first().updateToMain() + + spaceManagementPort.saveAll(spaces) } } diff --git a/Application-Module/src/main/kotlin/com/asap/application/space/service/SpaceQueryService.kt b/Application-Module/src/main/kotlin/com/asap/application/space/service/SpaceQueryService.kt index f0df5790..e64dc805 100644 --- a/Application-Module/src/main/kotlin/com/asap/application/space/service/SpaceQueryService.kt +++ b/Application-Module/src/main/kotlin/com/asap/application/space/service/SpaceQueryService.kt @@ -37,7 +37,7 @@ class SpaceQueryService( override fun getAll(query: GetSpaceUsecase.GetAllQuery): GetSpaceUsecase.GetAllResponse { val spaces = - spaceManagementPort.getAllIndexedSpace( + spaceManagementPort.getAllSpaceBy( userId = DomainId(query.userId), ) @@ -47,7 +47,7 @@ class SpaceQueryService( GetSpaceUsecase.SpaceDetail( spaceName = it.name, letterCount = spaceLetterManagementPort.countSpaceLetterBy(it.id, DomainId(query.userId)), - isMainSpace = it.isMain(), + isMainSpace = it.isMain, spaceIndex = it.index, spaceId = it.id.value, ) diff --git a/Application-Module/src/test/kotlin/com/asap/application/letter/service/LetterQueryServiceTest.kt b/Application-Module/src/test/kotlin/com/asap/application/letter/service/LetterQueryServiceTest.kt index f5d56e9e..18e67b7c 100644 --- a/Application-Module/src/test/kotlin/com/asap/application/letter/service/LetterQueryServiceTest.kt +++ b/Application-Module/src/test/kotlin/com/asap/application/letter/service/LetterQueryServiceTest.kt @@ -7,9 +7,9 @@ import com.asap.application.letter.port.out.SpaceLetterManagementPort import com.asap.application.space.port.out.SpaceManagementPort import com.asap.application.user.port.out.UserManagementPort import com.asap.domain.LetterFixture +import com.asap.domain.SpaceFixture import com.asap.domain.UserFixture import com.asap.domain.common.DomainId -import com.asap.domain.space.entity.Space import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe @@ -96,13 +96,9 @@ class LetterQueryServiceTest : letterId = "letter-id", userId = "user-id", ) - val space = - Space( - id = DomainId.generate(), - name = "space-name", - userId = DomainId(query.userId), - templateType = 1, - ) + val space = SpaceFixture.createSpace( + userId = DomainId(query.userId), + ) val spaceLetter = LetterFixture.generateSpaceLetter(receiverId = DomainId(query.userId), spaceId = space.id) val prevSpaceLetter = LetterFixture.generateSpaceLetter(receiverId = DomainId(query.userId), spaceId = space.id) diff --git a/Application-Module/src/test/kotlin/com/asap/application/space/service/SpaceCommandServiceTest.kt b/Application-Module/src/test/kotlin/com/asap/application/space/service/SpaceCommandServiceTest.kt index c35391e2..cc7fd7c4 100644 --- a/Application-Module/src/test/kotlin/com/asap/application/space/service/SpaceCommandServiceTest.kt +++ b/Application-Module/src/test/kotlin/com/asap/application/space/service/SpaceCommandServiceTest.kt @@ -3,11 +3,11 @@ package com.asap.application.space.service import com.asap.application.space.exception.SpaceException import com.asap.application.space.port.`in`.CreateSpaceUsecase import com.asap.application.space.port.`in`.DeleteSpaceUsecase -import com.asap.application.space.port.`in`.UpdateSpaceIndexUsecase import com.asap.application.space.port.`in`.UpdateSpaceNameUsecase +import com.asap.application.space.port.`in`.UpdateSpaceUsecase import com.asap.application.space.port.out.SpaceManagementPort +import com.asap.domain.SpaceFixture import com.asap.domain.common.DomainId -import com.asap.domain.space.entity.IndexedSpace import com.asap.domain.space.entity.Space import io.kotest.assertions.throwables.shouldThrow import io.kotest.core.spec.style.BehaviorSpec @@ -49,13 +49,13 @@ class SpaceCommandServiceTest : spaceId = "spaceId", name = "newName", ) - val mockSpace = - Space( - id = DomainId(spaceUpdateNameCommand.spaceId), - userId = DomainId(spaceUpdateNameCommand.userId), - name = "oldName", - templateType = 1, - ) + val mockSpace = SpaceFixture.createSpace( + id = DomainId(spaceUpdateNameCommand.spaceId), + userId = DomainId(spaceUpdateNameCommand.userId), + name = "oldName", + templateType = 1, + ) + every { spaceManagementPort.getSpaceNotNull( userId = DomainId(spaceUpdateNameCommand.userId), @@ -114,63 +114,48 @@ class SpaceCommandServiceTest : given("스페이스 인덱스 수정 요청이 들어올 때") { val spaceUpdateIndexCommand = - UpdateSpaceIndexUsecase.Command( + UpdateSpaceUsecase.Command.Index( userId = "userId", orders = listOf( - UpdateSpaceIndexUsecase.Command.SpaceOrder("spaceId1", 1), - UpdateSpaceIndexUsecase.Command.SpaceOrder("spaceId2", 0), + UpdateSpaceUsecase.Command.SpaceOrder("spaceId1", 1), + UpdateSpaceUsecase.Command.SpaceOrder("spaceId2", 0), ), ) - val indexedSpaces = + val spaces = listOf( - IndexedSpace( - id = DomainId("spaceId1"), - userId = DomainId("userId"), - name = "space1", - index = 0, - templateType = 1, + SpaceFixture.createSpace( + id = DomainId(spaceUpdateIndexCommand.orders[0].spaceId), + userId = DomainId(spaceUpdateIndexCommand.userId), ), - IndexedSpace( - id = DomainId("spaceId2"), - userId = DomainId("userId"), - name = "space2", - index = 1, - templateType = 1, + SpaceFixture.createSpace( + id = DomainId(spaceUpdateIndexCommand.orders[1].spaceId), + userId = DomainId(spaceUpdateIndexCommand.userId), ), ) - every { spaceManagementPort.getAllIndexedSpace(DomainId(spaceUpdateIndexCommand.userId)) } returns indexedSpaces + every { spaceManagementPort.getAllSpaceBy(DomainId(spaceUpdateIndexCommand.userId)) } returns spaces `when`("유저 아이디, 스페이스 순서가 주어진다면") { spaceCommandService.update(spaceUpdateIndexCommand) then("스페이스 순서를 수정한다") { - indexedSpaces[0].updateIndex(1) - indexedSpaces[1].updateIndex(0) verify { - spaceManagementPort.updateIndexes( - userId = DomainId(spaceUpdateIndexCommand.userId), - orders = - listOf( - indexedSpaces[0], - indexedSpaces[1], - ), - ) + spaceManagementPort.saveAll(spaces) } } } val invalidCommand = - UpdateSpaceIndexUsecase.Command( + UpdateSpaceUsecase.Command.Index( userId = "userId", orders = listOf( - UpdateSpaceIndexUsecase.Command.SpaceOrder("spaceId1", 1), - UpdateSpaceIndexUsecase.Command.SpaceOrder("spaceId2", 2), - UpdateSpaceIndexUsecase.Command.SpaceOrder("spaceId3", 3), + UpdateSpaceUsecase.Command.SpaceOrder("spaceId1", 1), + UpdateSpaceUsecase.Command.SpaceOrder("spaceId2", 2), + UpdateSpaceUsecase.Command.SpaceOrder("spaceId3", 3), ), ) every { - spaceManagementPort.getAllIndexedSpace(DomainId(invalidCommand.userId)) - } returns indexedSpaces + spaceManagementPort.getAllSpaceBy(DomainId(invalidCommand.userId)) + } returns spaces `when`("인덱스 검증과정에서 예외가 발생한다면") { then("스페이스 순서를 수정하지 않는다") { shouldThrow { diff --git a/Application-Module/src/test/kotlin/com/asap/application/space/service/SpaceQueryServiceTest.kt b/Application-Module/src/test/kotlin/com/asap/application/space/service/SpaceQueryServiceTest.kt index a4cb214d..b6ca5ccc 100644 --- a/Application-Module/src/test/kotlin/com/asap/application/space/service/SpaceQueryServiceTest.kt +++ b/Application-Module/src/test/kotlin/com/asap/application/space/service/SpaceQueryServiceTest.kt @@ -5,11 +5,10 @@ import com.asap.application.space.port.`in`.GetMainSpaceUsecase import com.asap.application.space.port.`in`.GetSpaceUsecase import com.asap.application.space.port.out.SpaceManagementPort import com.asap.application.user.port.out.UserManagementPort +import com.asap.domain.SpaceFixture import com.asap.domain.UserFixture import com.asap.domain.common.DomainId -import com.asap.domain.space.entity.IndexedSpace import com.asap.domain.space.entity.MainSpace -import com.asap.domain.space.entity.Space import io.kotest.core.spec.style.BehaviorSpec import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe @@ -40,13 +39,9 @@ class SpaceQueryServiceTest : GetMainSpaceUsecase.Query( userId = user.id.value, ) - val space = - Space( - id = mainSpace.id, - name = "name", - userId = user.id, - templateType = 1, - ) + val space = SpaceFixture.createSpace( + userId = user.id, + ) every { spaceManagementPort.getMainSpace(any()) } returns mainSpace every { userManagementPort.getUserNotNull(any()) } returns user every { spaceManagementPort.getSpaceNotNull(any(), any()) } returns space @@ -62,41 +57,23 @@ class SpaceQueryServiceTest : } given("모든 스페이스 조회 요청이 들어왔을 때") { - val indexedSpaces = - listOf( - IndexedSpace( - id = DomainId.generate(), - name = "name", - index = 0, - userId = DomainId("userId"), - templateType = 1, - ), - IndexedSpace( - id = DomainId.generate(), - name = "name", - index = 1, - userId = DomainId("userId"), - templateType = 1, - ), - IndexedSpace( - id = DomainId.generate(), - name = "name", - index = 2, - userId = DomainId("userId"), - templateType = 1, - ), + val spaces = (0..2).mapIndexed { index, _ -> + SpaceFixture.createSpace( + userId = DomainId("userId"), + index = index, ) - val indexedSpaceMap = indexedSpaces.associateBy { it.id } + } + val indexedSpaceMap = spaces.associateBy { it.id } val query = GetSpaceUsecase.GetAllQuery( userId = "userId", ) - every { spaceManagementPort.getAllIndexedSpace(DomainId(query.userId)) } returns indexedSpaces + every { spaceManagementPort.getAllSpaceBy(DomainId(query.userId)) } returns spaces every { spaceLetterManagementPort.countSpaceLetterBy(any(), any()) } returns 0 `when`("유저 아이디가 주어진다면") { val response = spaceQueryService.getAll(query) then("모든 스페이스를 반환한다") { - response.spaces.size shouldBe indexedSpaces.size + response.spaces.size shouldBe spaces.size response.spaces.forEach { spaceDetail -> val indexedSpace = indexedSpaceMap[DomainId(spaceDetail.spaceId)] indexedSpace.shouldNotBeNull { @@ -111,42 +88,24 @@ class SpaceQueryServiceTest : } given("행성 모두 조회 요청이 들어왔을 때") { - val indexedSpaces = - listOf( - IndexedSpace( - id = DomainId.generate(), - name = "name", - index = 0, - userId = DomainId("userId"), - templateType = 1, - ), - IndexedSpace( - id = DomainId.generate(), - name = "name", - index = 1, - userId = DomainId("userId"), - templateType = 1, - ), - IndexedSpace( - id = DomainId.generate(), - name = "name", - index = 2, - userId = DomainId("userId"), - templateType = 1, - ), + val spaces = (0..2).mapIndexed { index, _ -> + SpaceFixture.createSpace( + userId = DomainId("userId"), + index = index, ) - val indexedSpaceMap = indexedSpaces.associateBy { it.id } + } + val spaceMap = spaces.associateBy { it.id } val query = GetSpaceUsecase.GetAllQuery( userId = "userId", ) - every { spaceManagementPort.getAllIndexedSpace(DomainId(query.userId)) } returns indexedSpaces + every { spaceManagementPort.getAllSpaceBy(DomainId(query.userId)) } returns spaces `when`("유저 아이디가 주어진다면") { val response = spaceQueryService.getAll(query) then("모든 스페이스를 반환한다") { - response.spaces.size shouldBe indexedSpaces.size + response.spaces.size shouldBe spaces.size response.spaces.forEach { spaceDetail -> - val indexedSpace = indexedSpaceMap[DomainId(spaceDetail.spaceId)] + val indexedSpace = spaceMap[DomainId(spaceDetail.spaceId)] indexedSpace.shouldNotBeNull { this.id shouldBe DomainId(spaceDetail.spaceId) this.name shouldBe spaceDetail.spaceName @@ -159,13 +118,10 @@ class SpaceQueryServiceTest : } given("행성 조회 요청이 들어왔을 때") { - val space = - Space( - id = DomainId.generate(), - name = "name", - userId = DomainId("userId"), - templateType = 1, - ) + val space = SpaceFixture.createSpace( + id = DomainId("spaceId"), + userId = DomainId("userId"), + ) val query = GetSpaceUsecase.GetQuery( userId = "userId", diff --git a/Application-Module/src/testFixtures/kotlin/com/asap/application/space/SpaceMockManager.kt b/Application-Module/src/testFixtures/kotlin/com/asap/application/space/SpaceMockManager.kt index 4514cce4..f9d6510f 100644 --- a/Application-Module/src/testFixtures/kotlin/com/asap/application/space/SpaceMockManager.kt +++ b/Application-Module/src/testFixtures/kotlin/com/asap/application/space/SpaceMockManager.kt @@ -10,23 +10,22 @@ class SpaceMockManager( fun settingSpace( userId: String, index: Int = 0, + isMain: Boolean = false, ): Space { val space = Space.create( userId = DomainId(userId), name = "test", templateType = 0, + index = index, ) + if(isMain) space.updateToMain() + return spaceManagementPort.save(space).also { - spaceManagementPort.getIndexedSpaceNotNull(DomainId(userId), it.id).apply { + spaceManagementPort.getSpaceNotNull(DomainId(userId), it.id).apply { updateIndex(index) spaceManagementPort.update(this) } } } - - fun getSpaceIndexes(userId: String): List> = - spaceManagementPort.getAllIndexedSpace(DomainId(userId)).map { - it.id.value to it.index - } } diff --git a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/space/api/SpaceApi.kt b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/space/api/SpaceApi.kt index f84fab5a..db9695a7 100644 --- a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/space/api/SpaceApi.kt +++ b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/space/api/SpaceApi.kt @@ -114,6 +114,21 @@ interface SpaceApi { @AccessUser userId: String, ) + @Operation(summary = "메인 스페이스 변경") + @PutMapping("/{spaceId}/main") + @ApiResponses( + value = [ + ApiResponse( + responseCode = "200", + description = "메인 스페이스 변경 성공", + ), + ], + ) + fun updateSpaceMain( + @PathVariable spaceId: String, + @AccessUser userId: String, + ) + @Operation(summary = "여러 스페이스 삭제") @DeleteMapping @ApiResponses( diff --git a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/space/controller/SpaceController.kt b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/space/controller/SpaceController.kt index f65d4ff6..914f01c3 100644 --- a/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/space/controller/SpaceController.kt +++ b/Bootstrap-Module/src/main/kotlin/com/asap/bootstrap/web/space/controller/SpaceController.kt @@ -12,7 +12,7 @@ class SpaceController( private val updateSpaceNameUsecase: UpdateSpaceNameUsecase, private val getSpaceUsecase: GetSpaceUsecase, private val deleteSpaceUsecase: DeleteSpaceUsecase, - private val updateSpaceIndexUsecase: UpdateSpaceIndexUsecase, + private val updateSpaceUsecase: UpdateSpaceUsecase, ) : SpaceApi { override fun getMainSpace(userId: String): MainSpaceInfoResponse { val response = @@ -83,10 +83,19 @@ class SpaceController( request: UpdateSpaceOrderRequest, userId: String, ) { - updateSpaceIndexUsecase.update( - UpdateSpaceIndexUsecase.Command( + updateSpaceUsecase.update( + UpdateSpaceUsecase.Command.Index( userId = userId, - orders = request.orders.map { UpdateSpaceIndexUsecase.Command.SpaceOrder(it.spaceId, it.index) }, + orders = request.orders.map { UpdateSpaceUsecase.Command.SpaceOrder(it.spaceId, it.index) }, + ), + ) + } + + override fun updateSpaceMain(spaceId: String, userId: String) { + updateSpaceUsecase.update( + UpdateSpaceUsecase.Command.Main( + userId = userId, + spaceId = spaceId, ), ) } diff --git a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/space/controller/SpaceControllerTest.kt b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/space/controller/SpaceControllerTest.kt index 197c8578..693dbe81 100644 --- a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/space/controller/SpaceControllerTest.kt +++ b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/acceptance/space/controller/SpaceControllerTest.kt @@ -33,7 +33,7 @@ class SpaceControllerTest : AcceptanceSupporter() { lateinit var deleteSpaceUsecase: DeleteSpaceUsecase @MockBean - lateinit var updateSpaceIndexUsecase: UpdateSpaceIndexUsecase + lateinit var updateSpaceUsecase: UpdateSpaceUsecase @Test fun getMainSpaceId() { @@ -293,4 +293,20 @@ class SpaceControllerTest : AcceptanceSupporter() { } } } + + @Test + fun updateSpaceMain() { + // given + val accessToken = jwtMockManager.generateAccessToken() + val spaceId = "spaceId" + // when + val response = + mockMvc.put("/api/v1/spaces/$spaceId/main") { + header("Authorization", "Bearer $accessToken") + } + // then + response.andExpect { + status { isOk() } + } + } } diff --git a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/space/SpaceApiIntegrationTest.kt b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/space/SpaceApiIntegrationTest.kt index ea722543..e4dc91dc 100644 --- a/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/space/SpaceApiIntegrationTest.kt +++ b/Bootstrap-Module/src/test/kotlin/com/asap/bootstrap/integration/space/SpaceApiIntegrationTest.kt @@ -1,18 +1,20 @@ package com.asap.bootstrap.integration.space import com.asap.application.space.SpaceMockManager +import com.asap.application.space.port.out.SpaceManagementPort import com.asap.bootstrap.IntegrationSupporter import com.asap.bootstrap.web.space.dto.CreateSpaceRequest import com.asap.bootstrap.web.space.dto.DeleteMultipleSpacesRequest import com.asap.bootstrap.web.space.dto.UpdateSpaceNameRequest import com.asap.bootstrap.web.space.dto.UpdateSpaceOrderRequest +import com.asap.domain.common.DomainId +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder import io.kotest.matchers.maps.haveValue import io.kotest.matchers.shouldBe import io.kotest.matchers.string.haveLength import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test -import org.springframework.beans.factory.annotation.Autowired import org.springframework.http.MediaType import org.springframework.test.web.servlet.delete import org.springframework.test.web.servlet.get @@ -20,9 +22,10 @@ import org.springframework.test.web.servlet.post import org.springframework.test.web.servlet.put import java.util.* -class SpaceApiIntegrationTest : IntegrationSupporter() { - @Autowired - lateinit var spaceMockManager: SpaceMockManager +class SpaceApiIntegrationTest( + private val spaceMockManager: SpaceMockManager, + private val spaceManagementPort: SpaceManagementPort +) : IntegrationSupporter() { @Nested inner class GetMainSpace { @@ -31,57 +34,10 @@ class SpaceApiIntegrationTest : IntegrationSupporter() { // given val userId = userMockManager.settingUser() val accessToken = jwtMockManager.generateAccessToken(userId) - spaceMockManager.settingSpace(userId) - // when - val response = - mockMvc.get("/api/v1/spaces/main") { - header("Authorization", "Bearer $accessToken") - } - - // then - response.andExpect { - status { isOk() } - jsonPath("$.spaceId") { - exists() - isString() - isNotEmpty() - } - jsonPath("$.username") { - exists() - isString() - isNotEmpty() - } - jsonPath("$.templateType") { - exists() - isNumber() - } - jsonPath("$.spaceName") { - exists() - isString() - isNotEmpty() - } - } - } - - @Test - fun getMainSpaceId_with_changedIndex() { - // given - val userId = userMockManager.settingUser() - val accessToken = jwtMockManager.generateAccessToken(userId) - val spaceIndexes = - (0..3).map { - val spaceId = spaceMockManager.settingSpace(userId).id.value - UpdateSpaceOrderRequest.SpaceOrder(spaceId, 3 - it) - } - mockMvc.put("/api/v1/spaces/order") { - contentType = MediaType.APPLICATION_JSON - content = objectMapper.writeValueAsString(UpdateSpaceOrderRequest(spaceIndexes)) - header("Authorization", "Bearer $accessToken") - } + spaceMockManager.settingSpace(userId, isMain = true) // when val response = mockMvc.get("/api/v1/spaces/main") { - contentType = MediaType.APPLICATION_JSON header("Authorization", "Bearer $accessToken") } @@ -92,7 +48,6 @@ class SpaceApiIntegrationTest : IntegrationSupporter() { exists() isString() isNotEmpty() - value(spaceIndexes[3].spaceId) } jsonPath("$.username") { exists() @@ -138,13 +93,10 @@ class SpaceApiIntegrationTest : IntegrationSupporter() { } @Test - fun createSpace_and_get_main_space() { + fun create_first_space_and_get_main_space() { // given val userId = userMockManager.settingUser() val accessToken = jwtMockManager.generateAccessToken(userId) - (0..2).forEach { - spaceMockManager.settingSpace(userId) - } val request = CreateSpaceRequest( spaceName = "createdSpace", @@ -190,26 +142,31 @@ class SpaceApiIntegrationTest : IntegrationSupporter() { } } - @Test - fun updateSpaceName() { - // given - val userId = userMockManager.settingUser() - val accessToken = jwtMockManager.generateAccessToken(userId) - val spaceId = spaceMockManager.settingSpace(userId).id.value - val request = - UpdateSpaceNameRequest( - spaceName = "change space name", - ) - // when - val response = - mockMvc.put("/api/v1/spaces/$spaceId/name") { - contentType = MediaType.APPLICATION_JSON - content = objectMapper.writeValueAsString(request) - header("Authorization", "Bearer $accessToken") + + @Nested + @DisplayName("updateSpaceName") + inner class UpdateSpaceName{ + @Test + fun updateSpaceName() { + // given + val userId = userMockManager.settingUser() + val accessToken = jwtMockManager.generateAccessToken(userId) + val spaceId = spaceMockManager.settingSpace(userId).id.value + val request = + UpdateSpaceNameRequest( + spaceName = "change space name", + ) + // when + val response = + mockMvc.put("/api/v1/spaces/$spaceId/name") { + contentType = MediaType.APPLICATION_JSON + content = objectMapper.writeValueAsString(request) + header("Authorization", "Bearer $accessToken") + } + // then + response.andExpect { + status { isOk() } } - // then - response.andExpect { - status { isOk() } } } @@ -444,10 +401,8 @@ class SpaceApiIntegrationTest : IntegrationSupporter() { response.andExpect { status { isOk() } } - spaceMockManager.getSpaceIndexes(userId) shouldBe - spaceIndexes - .map { it.spaceId to it.index } - .sortedBy { it.second } + val spaceIds = spaceManagementPort.getAllSpaceBy(DomainId(userId)).map { it.id.value } + spaceIds shouldContainExactlyInAnyOrder spaceIndexes.map { it.spaceId } } @Test @@ -612,4 +567,35 @@ class SpaceApiIntegrationTest : IntegrationSupporter() { } } } + + @Nested + @DisplayName("updateSpaceMain") + inner class UpdateSpaceMain{ + @Test + fun updateSpaceMain() { + // given + val userId = userMockManager.settingUser() + val accessToken = jwtMockManager.generateAccessToken(userId) + val spaceId = spaceMockManager.settingSpace(userId).id.value + (0..2).forEach { + spaceMockManager.settingSpace(userId) + } + // when + val response = + mockMvc.put("/api/v1/spaces/$spaceId/main") { + header("Authorization", "Bearer $accessToken") + } + // then + response.andExpect { + status { isOk() } + } + spaceManagementPort.getAllSpaceBy(DomainId(userId)).forEach { + if(it.id.value == spaceId) { + it.isMain shouldBe true + }else{ + it.isMain shouldBe false + } + } + } + } } diff --git a/Domain-Module/src/main/kotlin/com/asap/domain/space/entity/IndexedSpace.kt b/Domain-Module/src/main/kotlin/com/asap/domain/space/entity/IndexedSpace.kt deleted file mode 100644 index 7ba52386..00000000 --- a/Domain-Module/src/main/kotlin/com/asap/domain/space/entity/IndexedSpace.kt +++ /dev/null @@ -1,19 +0,0 @@ -package com.asap.domain.space.entity - -import com.asap.domain.common.Aggregate -import com.asap.domain.common.DomainId - -class IndexedSpace( - id: DomainId, - val userId: DomainId, - val name: String, - var index: Int, - val templateType: Int, -) : Aggregate(id) { - fun isMain(): Boolean = index == 0 - - fun updateIndex(index: Int) { - check(index >= 0) { "Index must be greater than or equal to 0" } - this.index = index - } -} diff --git a/Domain-Module/src/main/kotlin/com/asap/domain/space/entity/Space.kt b/Domain-Module/src/main/kotlin/com/asap/domain/space/entity/Space.kt index 2c2ac534..bcd128d0 100644 --- a/Domain-Module/src/main/kotlin/com/asap/domain/space/entity/Space.kt +++ b/Domain-Module/src/main/kotlin/com/asap/domain/space/entity/Space.kt @@ -8,7 +8,9 @@ class Space( id: DomainId, val userId: DomainId, var name: String, + var index: Int, val templateType: Int, + var isMain: Boolean = false, ) : Aggregate(id) { companion object { fun create( @@ -16,12 +18,14 @@ class Space( userId: DomainId, name: String, templateType: Int, + index: Int = -1, ): Space = Space( id = id, userId = userId, name = name, templateType = templateType, + index = index, ).also { it.registerEvent(SpaceEvent.SpaceCreatedEvent(it)) } @@ -31,6 +35,19 @@ class Space( this.name = name } + fun updateToMain() { + this.isMain = true + } + + fun updateToSub() { + this.isMain = false + } + + fun updateIndex(index: Int) { + check(index >= 0) { "Index must be greater than or equal to 0" } + this.index = index + } + fun delete() { this.registerEvent(SpaceEvent.SpaceDeletedEvent(this)) } diff --git a/Domain-Module/src/main/kotlin/com/asap/domain/space/service/SpaceIndexValidator.kt b/Domain-Module/src/main/kotlin/com/asap/domain/space/service/SpaceIndexValidator.kt index 89b64ebe..507ed301 100644 --- a/Domain-Module/src/main/kotlin/com/asap/domain/space/service/SpaceIndexValidator.kt +++ b/Domain-Module/src/main/kotlin/com/asap/domain/space/service/SpaceIndexValidator.kt @@ -2,20 +2,20 @@ package com.asap.domain.space.service import com.asap.common.exception.DefaultException import com.asap.domain.common.DomainId -import com.asap.domain.space.entity.IndexedSpace +import com.asap.domain.space.entity.Space class SpaceIndexValidator { - fun validate(indexedSpaces: List, validateIndex: Map){ - val indexedSpaceSize = indexedSpaces.size + fun validate(spaces: List, validateIndex: Map){ + val spaceSize = spaces.size val indexSet = validateIndex.values.toSet() - if (indexSet.size != indexedSpaceSize) { + if (indexSet.size != spaceSize) { throw DefaultException.InvalidArgumentException("요청 인덱스가 기존 엔덱스와 일치하지 않습니다.") } - if (indexSet.minOrNull() != 0 || indexSet.maxOrNull() != indexedSpaceSize - 1) { + if (indexSet.minOrNull() != 0 || indexSet.maxOrNull() != spaceSize - 1) { throw DefaultException.InvalidArgumentException("인덱스 범위가 기존 인덱스 범위와 일치하지 않습니다.") } - indexedSpaces.forEach { + spaces.forEach { if(validateIndex.containsKey(it.id).not()){ throw DefaultException.InvalidArgumentException("사용자의 스페이스에 포함되지 않은 요청이 있습니다.") } diff --git a/Domain-Module/src/testFixtures/kotlin/com/asap/domain/SpaceFixture.kt b/Domain-Module/src/testFixtures/kotlin/com/asap/domain/SpaceFixture.kt new file mode 100644 index 00000000..d10dc7a2 --- /dev/null +++ b/Domain-Module/src/testFixtures/kotlin/com/asap/domain/SpaceFixture.kt @@ -0,0 +1,24 @@ +package com.asap.domain + +import com.asap.domain.common.DomainId +import com.asap.domain.space.entity.Space + +object SpaceFixture { + fun createSpace( + id: DomainId = DomainId.generate(), + userId: DomainId = DomainId.generate(), + name: String = "test", + templateType: Int = 0, + index : Int = 0, + isMain: Boolean = false, + ): Space { + return Space( + id = id, + userId = userId, + name = name, + templateType = templateType, + index = index, + isMain = isMain + ) + } +} \ No newline at end of file diff --git a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/space/SpaceMapper.kt b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/space/SpaceMapper.kt index d38fe559..f6c8a095 100644 --- a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/space/SpaceMapper.kt +++ b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/space/SpaceMapper.kt @@ -1,7 +1,6 @@ package com.asap.persistence.jpa.space import com.asap.domain.common.DomainId -import com.asap.domain.space.entity.IndexedSpace import com.asap.domain.space.entity.MainSpace import com.asap.domain.space.entity.Space import com.asap.persistence.jpa.space.entity.SpaceEntity @@ -18,25 +17,18 @@ object SpaceMapper { userId = DomainId(spaceEntity.userId), name = spaceEntity.name, templateType = spaceEntity.templateType, - ) - - fun toIndexedSpace(spaceEntity: SpaceEntity) = - IndexedSpace( - id = DomainId(spaceEntity.id), - userId = DomainId(spaceEntity.userId), - name = spaceEntity.name, - templateType = spaceEntity.templateType, index = spaceEntity.index, + isMain = spaceEntity.isMain, ) fun toSpaceEntity( space: Space, - index: Int, ) = SpaceEntity( id = space.id.value, userId = space.userId.value, name = space.name, templateType = space.templateType, - index = index, + index = space.index, + isMain = space.isMain, ) } diff --git a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/space/adapter/SpaceManagementJpaAdapter.kt b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/space/adapter/SpaceManagementJpaAdapter.kt index faa00365..4f12b165 100644 --- a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/space/adapter/SpaceManagementJpaAdapter.kt +++ b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/space/adapter/SpaceManagementJpaAdapter.kt @@ -4,7 +4,6 @@ import com.asap.application.space.exception.SpaceException import com.asap.application.space.port.out.SpaceManagementPort import com.asap.common.event.EventPublisher import com.asap.domain.common.DomainId -import com.asap.domain.space.entity.IndexedSpace import com.asap.domain.space.entity.MainSpace import com.asap.domain.space.entity.Space import com.asap.persistence.jpa.space.SpaceMapper @@ -20,7 +19,7 @@ class SpaceManagementJpaAdapter( spaceJpaRepository .findAllActiveSpaceByUserId(userId.value) .first { - it.index == 0 + it.isMain }.let { SpaceMapper.toMainSpace(it) } @@ -33,22 +32,6 @@ class SpaceManagementJpaAdapter( SpaceMapper.toSpace(it) } ?: throw SpaceException.SpaceNotFoundException() - override fun getIndexedSpaceNotNull( - userId: DomainId, - spaceId: DomainId, - ): IndexedSpace { - spaceJpaRepository.findActiveSpaceByIdAndUserId(spaceId.value, userId.value)?.let { - return SpaceMapper.toIndexedSpace(it) - } ?: throw SpaceException.SpaceNotFoundException() - } - - override fun getAllIndexedSpace(userId: DomainId): List = - spaceJpaRepository - .findAllActiveSpaceByUserId(userId.value) - .map { - SpaceMapper.toIndexedSpace(it) - }.sortedBy { it.index } - override fun getAllSpaceBy( userId: DomainId, spaceIds: List, @@ -74,7 +57,6 @@ class SpaceManagementJpaAdapter( val entity = SpaceMapper.toSpaceEntity( space = space, - index = -1, ) return spaceJpaRepository .save(entity) @@ -85,6 +67,21 @@ class SpaceManagementJpaAdapter( } } + override fun saveAll(spaces: List): List { + val entities = spaces.map { + SpaceMapper.toSpaceEntity( + space = it, + ) + } + return spaceJpaRepository + .saveAll(entities) + .map { + SpaceMapper.toSpace(it) + }.also { + eventPublisher.publishAll(spaces.flatMap { it.pullEvents() }) + } + } + override fun update(space: Space): Space = spaceJpaRepository.findActiveSpaceByIdAndUserId(space.id.value, space.userId.value)?.let { it.update(space) @@ -95,32 +92,6 @@ class SpaceManagementJpaAdapter( SpaceMapper.toSpace(it) } ?: throw SpaceException.SpaceNotFoundException() - override fun update(indexedSpace: IndexedSpace): IndexedSpace { - spaceJpaRepository.findActiveSpaceByIdAndUserId(indexedSpace.id.value, indexedSpace.userId.value)?.let { - it.index = indexedSpace.index - spaceJpaRepository.save(it) - } ?: throw SpaceException.SpaceNotFoundException() - - eventPublisher.publishAll(indexedSpace.pullEvents()) - - return indexedSpace - } - - override fun updateIndexes( - userId: DomainId, - orders: List, - ) { - val spaces = spaceJpaRepository.findAllActiveSpaceByUserId(userId.value) - orders.forEach { order -> - spaces.find { it.id == order.id.value }?.let { - it.index = order.index - spaceJpaRepository.save(it) - } - } - - eventPublisher.publishAll(orders.flatMap { it.pullEvents() }) - } - override fun deleteBy(space: Space) { spaceJpaRepository.deleteByUserIdAndId( userId = space.userId.value, diff --git a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/space/entity/SpaceEntity.kt b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/space/entity/SpaceEntity.kt index 6a616bde..289364cd 100644 --- a/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/space/entity/SpaceEntity.kt +++ b/Infrastructure-Module/Persistence/src/main/kotlin/com/asap/persistence/jpa/space/entity/SpaceEntity.kt @@ -14,6 +14,7 @@ class SpaceEntity( name: String, templateType: Int, index: Int, + isMain: Boolean, ) : AggregateRoot(id) { @Column(name = "user_id", nullable = false) var userId: String = userId @@ -39,6 +40,13 @@ class SpaceEntity( ) var spaceStatus: EntityStatus = EntityStatus.ACTIVE + + @Column( + name = "is_main", + nullable = false, + ) + var isMain: Boolean = isMain + fun update(space: Space) { this.userId = space.userId.value this.name = space.name