diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 8ad8c86107..739bc36583 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,7 @@ - \ No newline at end of file diff --git a/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/ContributionPopUpMenu.kt b/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/ContributionPopUpMenu.kt index 8dc388cf17..cdc9172629 100644 --- a/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/ContributionPopUpMenu.kt +++ b/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/ContributionPopUpMenu.kt @@ -1,6 +1,6 @@ package com.zegreatrob.coupling.client.components -import com.zegreatrob.coupling.action.player.SavePlayerCommand +import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.client.components.contributor.ContributorMenu import com.zegreatrob.coupling.model.party.PartyId import com.zegreatrob.coupling.model.player.Player @@ -34,7 +34,7 @@ external interface ContributionPopUpMenuProps : Props { @Suppress("INLINE_CLASS_IN_EXTERNAL_DECLARATION_WARNING") var partyId: PartyId var players: List - var dispatchFunc: DispatchFunc + var dispatchFunc: DispatchFunc var children: ((ReferenceElement, Player) -> Unit) -> ReactNode } diff --git a/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/contribution/ContributionListContent.kt b/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/contribution/ContributionListContent.kt index 76b0ff4048..fe6f0d6e7c 100644 --- a/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/contribution/ContributionListContent.kt +++ b/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/contribution/ContributionListContent.kt @@ -1,6 +1,6 @@ package com.zegreatrob.coupling.client.components.contribution -import com.zegreatrob.coupling.action.player.SavePlayerCommand +import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.client.components.ContributionPopUpMenu import com.zegreatrob.coupling.client.components.DispatchFunc import com.zegreatrob.coupling.client.components.graphing.ContributionWindow @@ -38,7 +38,7 @@ external interface ContributionListContentProps : Props { var window: ContributionWindow var setWindow: (ContributionWindow) -> Unit var players: List - var dispatchFunc: DispatchFunc + var dispatchFunc: DispatchFunc } @ReactFunc diff --git a/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/contribution/ContributionOverviewContent.kt b/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/contribution/ContributionOverviewContent.kt index ea4aec053d..679a475440 100644 --- a/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/contribution/ContributionOverviewContent.kt +++ b/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/contribution/ContributionOverviewContent.kt @@ -1,6 +1,6 @@ package com.zegreatrob.coupling.client.components.contribution -import com.zegreatrob.coupling.action.player.SavePlayerCommand +import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.client.components.ContributionPopUpMenu import com.zegreatrob.coupling.client.components.DispatchFunc import com.zegreatrob.coupling.model.Contribution @@ -23,7 +23,7 @@ external interface ContributionOverviewContentProps : Props { var party: PartyDetails var contributions: List var players: List - var dispatchFunc: DispatchFunc + var dispatchFunc: DispatchFunc } @ReactFunc diff --git a/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/contributor/ContributorMenu.kt b/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/contributor/ContributorMenu.kt index 3452c60559..536aaeab67 100644 --- a/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/contributor/ContributorMenu.kt +++ b/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/contributor/ContributorMenu.kt @@ -1,7 +1,7 @@ package com.zegreatrob.coupling.client.components.contributor -import com.zegreatrob.coupling.action.player.SavePlayerCommand -import com.zegreatrob.coupling.action.player.fire +import com.zegreatrob.coupling.action.party.SavePartyCommand +import com.zegreatrob.coupling.action.party.fire import com.zegreatrob.coupling.client.components.CouplingButton import com.zegreatrob.coupling.client.components.DispatchFunc import com.zegreatrob.coupling.client.components.Paths.playerConfigPath @@ -27,7 +27,7 @@ external interface ContributorMenuProps : Props { @Suppress("INLINE_CLASS_IN_EXTERNAL_DECLARATION_WARNING") var partyId: PartyId - var dispatchFunc: DispatchFunc + var dispatchFunc: DispatchFunc } @ReactFunc @@ -36,12 +36,12 @@ val ContributorMenu by nfc { props -> val navigate = useNavigate() val createPlayer = dispatchFunc { - fire(SavePlayerCommand(partyId, contributor)) + fire(SavePartyCommand(partyId = partyId, players = listOf(contributor))) } val addEmailToExistingPlayer = { player: Player -> fun(_: MouseEvent) { val updatedPlayer = player.copy(additionalEmails = player.additionalEmails + contributor.email) - dispatchFunc { fire(SavePlayerCommand(partyId, updatedPlayer)) }() + dispatchFunc { fire(SavePartyCommand(partyId = partyId, players = listOf(updatedPlayer))) }() } } diff --git a/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/pin/PinConfig.kt b/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/pin/PinConfig.kt index 49afcb404e..d98d94008d 100644 --- a/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/pin/PinConfig.kt +++ b/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/pin/PinConfig.kt @@ -1,7 +1,8 @@ package com.zegreatrob.coupling.client.components.pin +import com.zegreatrob.coupling.action.party.SavePartyCommand +import com.zegreatrob.coupling.action.party.fire import com.zegreatrob.coupling.action.pin.DeletePinCommand -import com.zegreatrob.coupling.action.pin.SavePinCommand import com.zegreatrob.coupling.action.pin.fire import com.zegreatrob.coupling.client.components.DispatchFunc import com.zegreatrob.coupling.client.components.Paths.pinListPath @@ -24,7 +25,7 @@ import react.router.dom.usePrompt import react.useState import kotlin.js.Json -external interface PinConfigProps : Props where D : DeletePinCommand.Dispatcher, D : SavePinCommand.Dispatcher { +external interface PinConfigProps : Props where D : DeletePinCommand.Dispatcher, D : SavePartyCommand.Dispatcher { var party: PartyDetails var boost: Boost? var pin: Pin @@ -41,7 +42,7 @@ val PinConfig by nfc> { props -> val updatedPin = values.fromJsonDynamic().toModel() val (redirectUrl, setRedirectUrl) = useState(null) val onSubmit = dispatchFunc { - fire(SavePinCommand(party.id, updatedPin)) + fire(SavePartyCommand(partyId = party.id, pins = listOf(updatedPin))) reload() } val onRemove = if (!pinList.contains(pin)) { diff --git a/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/player/PlayerConfig.kt b/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/player/PlayerConfig.kt index af73ffbfc4..0182736887 100644 --- a/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/player/PlayerConfig.kt +++ b/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/player/PlayerConfig.kt @@ -1,7 +1,8 @@ package com.zegreatrob.coupling.client.components.player +import com.zegreatrob.coupling.action.party.SavePartyCommand +import com.zegreatrob.coupling.action.party.fire import com.zegreatrob.coupling.action.player.DeletePlayerCommand -import com.zegreatrob.coupling.action.player.SavePlayerCommand import com.zegreatrob.coupling.action.player.fire import com.zegreatrob.coupling.client.components.DispatchFunc import com.zegreatrob.coupling.client.components.Paths.currentPairsPath @@ -26,7 +27,7 @@ import react.useState import kotlin.js.Json external interface PlayerConfigProps

: Props - where P : SavePlayerCommand.Dispatcher, P : DeletePlayerCommand.Dispatcher { + where P : SavePartyCommand.Dispatcher, P : DeletePlayerCommand.Dispatcher { var party: PartyDetails var boost: Boost? var player: Player @@ -51,10 +52,12 @@ val PlayerConfig by nfc> { props -> ) val onSubmit = dispatchFunc { fire( - SavePlayerCommand( + SavePartyCommand( partyId = party.id, - player = updatedPlayer.copy( - additionalEmails = updatedPlayer.additionalEmails.filterNot(String::isBlank).toSet(), + players = listOf( + updatedPlayer.copy( + additionalEmails = updatedPlayer.additionalEmails.filterNot(String::isBlank).toSet(), + ), ), ), ) diff --git a/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/player/UpdatingPlayerList.kt b/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/player/UpdatingPlayerList.kt index b796481f02..9fbe86f57f 100644 --- a/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/player/UpdatingPlayerList.kt +++ b/client/components/src/jsMain/kotlin/com/zegreatrob/coupling/client/components/player/UpdatingPlayerList.kt @@ -1,7 +1,7 @@ package com.zegreatrob.coupling.client.components.player import com.zegreatrob.coupling.action.VoidResult -import com.zegreatrob.coupling.action.player.SavePlayerCommand +import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.client.components.DispatchFunc import com.zegreatrob.coupling.model.player.Player import com.zegreatrob.minreact.ReactFunc @@ -13,7 +13,7 @@ import react.Props import react.ReactNode import react.useState -external interface UpdatingPlayerListProps : Props where D : SavePlayerCommand.Dispatcher { +external interface UpdatingPlayerListProps : Props where D : SavePartyCommand.Dispatcher { var players: List var dispatchFunc: DispatchFunc var children: (List, DispatchFunc) -> ReactNode @@ -62,8 +62,8 @@ private class SecretCannon( ) { val unwrappedAction = action.unwrap() @Suppress("USELESS_IS_CHECK") - if (unwrappedAction is SavePlayerCommand && result == VoidResult.Accepted) { - addPlayer(unwrappedAction.player) + if (unwrappedAction is SavePartyCommand && result == VoidResult.Accepted) { + unwrappedAction.players.forEach(addPlayer) } } } diff --git a/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/contributor/ContributorMenuTest.kt b/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/contributor/ContributorMenuTest.kt index e35d7f9d04..457c99c143 100644 --- a/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/contributor/ContributorMenuTest.kt +++ b/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/contributor/ContributorMenuTest.kt @@ -1,6 +1,6 @@ package com.zegreatrob.coupling.client.components.contributor -import com.zegreatrob.coupling.action.player.SavePlayerCommand +import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.client.components.Paths.playerConfigPath import com.zegreatrob.coupling.client.components.StubDispatcher import com.zegreatrob.coupling.client.components.TestRouter @@ -84,17 +84,21 @@ class ContributorMenuTest { } verify { stubDispatcher.receivedActions .map { - if (it !is SavePlayerCommand) { + if (it !is SavePartyCommand) { it } else { - it.copy(player = it.player.copy(id = PlayerId("generated".toNotBlankString().getOrThrow()))) + it.copy( + players = it.players.map { player -> + player.copy(id = PlayerId("generated".toNotBlankString().getOrThrow())) + }, + ) } } .assertIsEqualTo( listOf( - SavePlayerCommand( - partyId, - contributor.copy(id = PlayerId("generated".toNotBlankString().getOrThrow())), + SavePartyCommand( + partyId = partyId, + players = listOf(contributor.copy(id = PlayerId("generated".toNotBlankString().getOrThrow()))), ), ), ) @@ -122,9 +126,11 @@ class ContributorMenuTest { stubDispatcher.receivedActions .assertIsEqualTo( listOf( - SavePlayerCommand( + SavePartyCommand( partyId = partyId, - player = targetPlayer.copy(additionalEmails = targetPlayer.additionalEmails + contributor.email), + players = listOf( + targetPlayer.copy(additionalEmails = targetPlayer.additionalEmails + contributor.email), + ), ), ), ) diff --git a/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/party/PartyConfigTest.kt b/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/party/PartyConfigTest.kt index ac7674d989..81373e5bb3 100644 --- a/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/party/PartyConfigTest.kt +++ b/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/party/PartyConfigTest.kt @@ -115,7 +115,11 @@ class PartyConfigTest { stubDispatcher.receivedActions .filterIsInstance() .first() - .party.id.value.toString().run { + .party + ?.id + ?.value + .toString() + .run { assertIsNotEqualTo("") assertIsEqualTo(automatedPartyId) } diff --git a/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/pin/PinConfigEditorTest.kt b/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/pin/PinConfigEditorTest.kt index f5dd7fe5cf..39001c3639 100644 --- a/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/pin/PinConfigEditorTest.kt +++ b/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/pin/PinConfigEditorTest.kt @@ -1,6 +1,6 @@ package com.zegreatrob.coupling.client.components.pin -import com.zegreatrob.coupling.action.pin.SavePinCommand +import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.client.components.DispatchFunc import com.zegreatrob.coupling.client.components.StubDispatcher import com.zegreatrob.coupling.client.components.assertNotNull @@ -100,6 +100,8 @@ class PinConfigEditorTest { act { fireEvent.submit(screen.getByRole("form")) } } verify { stubDispatcher.receivedActions - .assertIsEqualTo(listOf(SavePinCommand(party.id, pin.copy(name = newName, icon = newIcon)))) + .assertIsEqualTo( + listOf(SavePartyCommand(partyId = party.id, pins = listOf(pin.copy(name = newName, icon = newIcon)))), + ) } } diff --git a/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/player/PlayerConfigTest.kt b/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/player/PlayerConfigTest.kt index 1166cc30df..1542aace0f 100644 --- a/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/player/PlayerConfigTest.kt +++ b/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/player/PlayerConfigTest.kt @@ -1,8 +1,8 @@ package com.zegreatrob.coupling.client.components.player import com.zegreatrob.coupling.action.VoidResult +import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.player.DeletePlayerCommand -import com.zegreatrob.coupling.action.player.SavePlayerCommand import com.zegreatrob.coupling.client.components.StubDispatcher import com.zegreatrob.coupling.client.components.TestRouter import com.zegreatrob.coupling.client.components.assertNotNull @@ -71,9 +71,9 @@ class PlayerConfigTest { } exercise { act { actor.click(screen.getByRole("button", RoleOptions(name = "Save"))) } } verify { - val expectedCommand = SavePlayerCommand( + val expectedCommand = SavePartyCommand( partyId = party.id, - player = player.copy(avatarType = AvatarType.DicebearAdventurer), + players = listOf(player.copy(avatarType = AvatarType.DicebearAdventurer)), ) stubDispatcher.receivedActions .assertIsEqualTo(listOf(expectedCommand)) @@ -105,9 +105,9 @@ class PlayerConfigTest { actor.click(screen.getByRole("button", RoleOptions(name = "Save"))) } } verify { - val expectedCommand = SavePlayerCommand( + val expectedCommand = SavePartyCommand( partyId = party.id, - player = player.copy(avatarType = null), + players = listOf(player.copy(avatarType = null)), ) stubDispatcher.receivedActions .assertIsEqualTo(listOf(expectedCommand)) @@ -195,9 +195,9 @@ class PlayerConfigTest { } exercise { actor.click(screen.getByRole("button", RoleOptions(name = "Save"))) } verify { - val expectedCommand = SavePlayerCommand( + val expectedCommand = SavePartyCommand( partyId = party.id, - player = player.copy(additionalEmails = setOf(secondEmail)), + players = listOf(player.copy(additionalEmails = setOf(secondEmail))), ) stubDispatcher.receivedActions .assertIsEqualTo(listOf(expectedCommand)) @@ -228,7 +228,7 @@ class PlayerConfigTest { act { actor.click(screen.getByRole("button", RoleOptions(name = "Save"))) } } verify { stubDispatcher.receivedActions - .assertIsEqualTo(listOf(SavePlayerCommand(partyId = party.id, player = player))) + .assertIsEqualTo(listOf(SavePartyCommand(partyId = party.id, players = listOf(player)))) } @Test @@ -282,7 +282,7 @@ class PlayerConfigTest { fireEvent.submit(screen.getByRole("form")) act { altStubDispatcher.onActionReturn(VoidResult.Accepted) } } verify { action -> - action.assertIsEqualTo(SavePlayerCommand(party.id, player.copy(name = "nonsense"))) + action.assertIsEqualTo(SavePartyCommand(partyId = party.id, players = listOf(player.copy(name = "nonsense")))) reloaderSpy.callCount.assertIsEqualTo(1) } diff --git a/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/player/UpdatingPlayerListTest.kt b/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/player/UpdatingPlayerListTest.kt index abf946e77c..30123e12b7 100644 --- a/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/player/UpdatingPlayerListTest.kt +++ b/client/components/src/jsTest/kotlin/com/zegreatrob/coupling/client/components/player/UpdatingPlayerListTest.kt @@ -1,9 +1,9 @@ package com.zegreatrob.coupling.client.components.player import com.zegreatrob.coupling.action.VoidResult -import com.zegreatrob.coupling.action.player.SavePlayerCommand -import com.zegreatrob.coupling.action.player.SavePlayerCommandWrapper -import com.zegreatrob.coupling.action.player.fire +import com.zegreatrob.coupling.action.party.SavePartyCommand +import com.zegreatrob.coupling.action.party.SavePartyCommandWrapper +import com.zegreatrob.coupling.action.party.fire import com.zegreatrob.coupling.client.components.DispatchFunc import com.zegreatrob.coupling.client.components.StubDispatcher import com.zegreatrob.coupling.client.components.stubDispatchFunc @@ -21,15 +21,15 @@ import kotlin.test.Test class UpdatingPlayerListTest { @Test - fun whenSavePlayerCommandSucceedsWillAddPlayerToList() = asyncSetup(object { + fun whenSavePartyCommandSucceedsWillAddPlayerToList() = asyncSetup(object { val newPlayer = stubPlayer() val partyId = stubPartyId() val players = stubPlayers(3) - val stubCannon = StubCannon(mutableListOf()).apply { - givenAny(SavePlayerCommandWrapper::class, VoidResult.Accepted) + val stubCannon = StubCannon(mutableListOf()).apply { + givenAny(SavePartyCommandWrapper::class, VoidResult.Accepted) } var lastPlayersCallback: List? = null - var dispatchFunc: DispatchFunc? = null + var dispatchFunc: DispatchFunc? = null }) { render { UpdatingPlayerList(players, dispatchFunc = stubDispatchFunc(stubCannon)) { players, dispatcher -> @@ -39,22 +39,22 @@ class UpdatingPlayerListTest { } } } exercise { - act { dispatchFunc?.invoke { fire(SavePlayerCommand(partyId, newPlayer)) }() } + act { dispatchFunc?.invoke { fire(SavePartyCommand(partyId = partyId, players = listOf(newPlayer))) }() } } verify { - stubCannon.receivedActions.contains(SavePlayerCommand(partyId, newPlayer)) + stubCannon.receivedActions.contains(SavePartyCommand(partyId = partyId, players = listOf(newPlayer))) lastPlayersCallback.assertIsEqualTo(players + newPlayer) } @Test - fun whenSavePlayerCommandSucceedsWillReplacePlayerInList() = asyncSetup(object { + fun whenSavePartyCommandSucceedsWillReplacePlayerInList() = asyncSetup(object { val targetPlayer = stubPlayer() val partyId = stubPartyId() val players = stubPlayers(3) - val stubCannon = StubCannon(mutableListOf()).apply { - givenAny(SavePlayerCommandWrapper::class, VoidResult.Accepted) + val stubCannon = StubCannon(mutableListOf()).apply { + givenAny(SavePartyCommandWrapper::class, VoidResult.Accepted) } var lastPlayersCallback: List? = null - var dispatchFunc: DispatchFunc? = null + var dispatchFunc: DispatchFunc? = null val updatedPlayer = targetPlayer.copy(name = "Bill") }) { render { @@ -68,20 +68,20 @@ class UpdatingPlayerListTest { } } } exercise { - act { dispatchFunc?.invoke { fire(SavePlayerCommand(partyId, updatedPlayer)) }() } + act { dispatchFunc?.invoke { fire(SavePartyCommand(partyId = partyId, players = listOf(updatedPlayer))) }() } } verify { - stubCannon.receivedActions.contains(SavePlayerCommand(partyId, updatedPlayer)) + stubCannon.receivedActions.contains(SavePartyCommand(partyId = partyId, players = listOf(updatedPlayer))) lastPlayersCallback.assertIsEqualTo(players + updatedPlayer) } @Test - fun whenSavePlayerCommandFailsWillNotAddPlayerToList() = asyncSetup(object { + fun whenSavePartyCommandFailsWillNotAddPlayerToList() = asyncSetup(object { val newPlayer = stubPlayer() val partyId = stubPartyId() val players = stubPlayers(3) val stubDispatcher = StubDispatcher.Channel() var lastPlayersCallback: List? = null - var dispatchFunc: DispatchFunc? = null + var dispatchFunc: DispatchFunc? = null }) { render { UpdatingPlayerList(players, dispatchFunc = stubDispatcher.func()) { players, dispatcher -> @@ -92,7 +92,7 @@ class UpdatingPlayerListTest { } } exercise { act { - dispatchFunc?.invoke { fire(SavePlayerCommand(partyId, newPlayer)) }() + dispatchFunc?.invoke { fire(SavePartyCommand(partyId = partyId, players = listOf(newPlayer))) }() stubDispatcher.onActionReturn(VoidResult.Rejected) } } verify { diff --git a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/ContributionListPageE2ETest.kt b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/ContributionListPageE2ETest.kt index b26c28ee65..0c63c6b426 100644 --- a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/ContributionListPageE2ETest.kt +++ b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/ContributionListPageE2ETest.kt @@ -4,7 +4,6 @@ import com.zegreatrob.coupling.action.party.SaveContributionCommand import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.fire import com.zegreatrob.coupling.action.player.DeletePlayerCommand -import com.zegreatrob.coupling.action.player.SavePlayerCommand import com.zegreatrob.coupling.action.player.fire import com.zegreatrob.coupling.model.pairassignmentdocument.PairingSet import com.zegreatrob.coupling.stubmodel.stubContributionInput @@ -27,8 +26,7 @@ class ContributionListPageE2ETest { val deletedPlayers = listOf(stubPlayer()) val allPlayers = players + deletedPlayers }) { - sdk().fire(SavePartyCommand(party)) - allPlayers.forEach { sdk().fire(SavePlayerCommand(party.id, it)) } + sdk().fire(SavePartyCommand(partyId = party.id, party = party, players = allPlayers)) deletedPlayers.forEach { sdk().fire(DeletePlayerCommand(party.id, it.id)) } sdk().fire( SaveContributionCommand( diff --git a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/ContributionOverviewPageE2ETest.kt b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/ContributionOverviewPageE2ETest.kt index 863f6c2bda..c0f9c67908 100644 --- a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/ContributionOverviewPageE2ETest.kt +++ b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/ContributionOverviewPageE2ETest.kt @@ -4,7 +4,6 @@ import com.zegreatrob.coupling.action.party.SaveContributionCommand import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.fire import com.zegreatrob.coupling.action.player.DeletePlayerCommand -import com.zegreatrob.coupling.action.player.SavePlayerCommand import com.zegreatrob.coupling.action.player.fire import com.zegreatrob.coupling.model.pairassignmentdocument.PairingSet import com.zegreatrob.coupling.stubmodel.stubContributionInput @@ -53,8 +52,7 @@ class ContributionOverviewPageE2ETest { val deletedPlayers = listOf(stubPlayer()) val allPlayers = players + deletedPlayers }) { - sdk().fire(SavePartyCommand(party)) - allPlayers.forEach { sdk().fire(SavePlayerCommand(party.id, it)) } + sdk().fire(SavePartyCommand(partyId = party.id, party = party, players = allPlayers)) deletedPlayers.forEach { sdk().fire(DeletePlayerCommand(party.id, it.id)) } sdk().fire( SaveContributionCommand( diff --git a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/ContributionVisualizationPageE2ETest.kt b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/ContributionVisualizationPageE2ETest.kt index c3d72cbdbc..a32bad5fe4 100644 --- a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/ContributionVisualizationPageE2ETest.kt +++ b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/ContributionVisualizationPageE2ETest.kt @@ -4,7 +4,6 @@ import com.zegreatrob.coupling.action.party.SaveContributionCommand import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.fire import com.zegreatrob.coupling.action.player.DeletePlayerCommand -import com.zegreatrob.coupling.action.player.SavePlayerCommand import com.zegreatrob.coupling.action.player.fire import com.zegreatrob.coupling.e2e.test.PartyConfigPage.findByRole import com.zegreatrob.coupling.model.ContributionId @@ -36,8 +35,7 @@ class ContributionVisualizationPageE2ETest { val deletedPlayers = listOf(stubPlayer()) val allPlayers = players + deletedPlayers }) { - sdk().fire(SavePartyCommand(party)) - allPlayers.forEach { sdk().fire(SavePlayerCommand(party.id, it)) } + sdk().fire(SavePartyCommand(partyId = party.id, party = party, players = allPlayers)) deletedPlayers.forEach { sdk().fire(DeletePlayerCommand(party.id, it.id)) } sdk().fire( SaveContributionCommand( @@ -66,8 +64,7 @@ class ContributionVisualizationPageE2ETest { suspend fun styleSelector() = findByRole("combobox", RoleOptions(name = "Visualization Style")) suspend fun styleOptions() = within(styleSelector()).getAllByRole("option", RoleOptions()) }) { - sdk().fire(SavePartyCommand(party)) - players.forEach { sdk().fire(SavePlayerCommand(party.id, it)) } + sdk().fire(SavePartyCommand(partyId = party.id, party = party, players = players)) ContributionVisualizationPage.goTo(party.id) } exercise { (0.. @@ -89,8 +86,7 @@ class ContributionVisualizationPageE2ETest { suspend fun styleOptions() = within(styleSelector()).getAllByRole("option", RoleOptions()) suspend fun playerCard() = findByText(players.first().name) }) { - sdk().fire(SavePartyCommand(party)) - players.forEach { sdk().fire(SavePlayerCommand(party.id, it)) } + sdk().fire(SavePartyCommand(partyId = party.id, party = party, players = players)) sdk().fire( SaveContributionCommand( party.id, diff --git a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/PairAssignmentsPageE2ETest.kt b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/PairAssignmentsPageE2ETest.kt index a38419393a..bb7e50652a 100644 --- a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/PairAssignmentsPageE2ETest.kt +++ b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/PairAssignmentsPageE2ETest.kt @@ -4,7 +4,6 @@ import com.zegreatrob.coupling.action.pairassignmentdocument.SavePairAssignments import com.zegreatrob.coupling.action.pairassignmentdocument.fire import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.fire -import com.zegreatrob.coupling.action.player.SavePlayerCommand import com.zegreatrob.coupling.action.player.fire import com.zegreatrob.coupling.e2e.test.AssignedPair.assignedPairCallSigns import com.zegreatrob.coupling.e2e.test.AssignedPair.assignedPairElements @@ -41,9 +40,7 @@ class PairAssignmentsPageE2ETest { companion object { private suspend fun ActionCannon.save(party: PartyDetails, players: List) = coroutineScope { - fire(SavePartyCommand(party)) - players.map { SavePlayerCommand(party.id, it) } - .forEach { fire(it) } + fire(SavePartyCommand(partyId = party.id, party = party, players = players)) } } @@ -187,7 +184,7 @@ class PairAssignmentsPageE2ETest { sdk.await().apply { fire(SavePartyCommand(party)) coroutineScope { - launch { players.forEach { fire(SavePlayerCommand(party.id, it)) } } + launch { fire(SavePartyCommand(partyId = party.id, party = party, players = players)) } launch { sdk.await().fire(SavePairAssignmentsCommand(party.id, pairingSet)) } } } diff --git a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/PinConfigE2ETest.kt b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/PinConfigE2ETest.kt index 64f49f02bc..ba67a8511f 100644 --- a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/PinConfigE2ETest.kt +++ b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/PinConfigE2ETest.kt @@ -2,7 +2,6 @@ package com.zegreatrob.coupling.e2e.test import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.fire -import com.zegreatrob.coupling.action.pin.SavePinCommand import com.zegreatrob.coupling.action.pin.fire import com.zegreatrob.coupling.e2e.test.ConfigForm.retireButton import com.zegreatrob.coupling.e2e.test.ConfigForm.saveButton @@ -101,7 +100,7 @@ class PinConfigE2ETest { val pin = randomPin() }.attachParty(), ) { - sdk.fire(SavePinCommand(party.id, pin)) + sdk.fire(SavePartyCommand(partyId = party.id, pins = listOf(pin))) } exercise { PinConfigPage.goTo(party.id, pin.id) } verify { @@ -119,7 +118,7 @@ class PinConfigE2ETest { val pin = randomPin() }.attachParty(), ) { - sdk.fire(SavePinCommand(party.id, pin)) + sdk.fire(SavePartyCommand(partyId = party.id, pins = listOf(pin))) PinConfigPage.goTo(party.id, pin.id) } exercise { acceptDialogAndGetMessage { retireButton().click() } diff --git a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/PlayerConfigPageE2ETest.kt b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/PlayerConfigPageE2ETest.kt index aeb50718ca..ececaa04a9 100644 --- a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/PlayerConfigPageE2ETest.kt +++ b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/PlayerConfigPageE2ETest.kt @@ -5,7 +5,6 @@ package com.zegreatrob.coupling.e2e.test import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.fire import com.zegreatrob.coupling.action.player.DeletePlayerCommand -import com.zegreatrob.coupling.action.player.SavePlayerCommand import com.zegreatrob.coupling.action.player.fire import com.zegreatrob.coupling.e2e.gql.PartyPlayerListQuery import com.zegreatrob.coupling.e2e.test.ConfigForm.retireButton @@ -37,8 +36,7 @@ class PlayerConfigPageE2ETest { val party = buildParty() val player = buildPlayer() sdk.await().apply { - fire(SavePartyCommand(party)) - fire(SavePlayerCommand(party.id, player)) + fire(SavePartyCommand(partyId = party.id, party = party, players = listOf(player))) } Triple(player, party, sdk.await()) }) @@ -207,8 +205,7 @@ class PlayerConfigPageE2ETest { val page = PlayerConfigPage }) { sdk.await().apply { - fire(SavePartyCommand(party)) - players.forEach { player -> fire(SavePlayerCommand(party.id, player)) } + fire(SavePartyCommand(partyId = party.id, party = party, players = players)) } PlayerConfigPage.goTo(party.id, players[0].id) } exercise { @@ -332,8 +329,7 @@ class PlayerConfigPageE2ETest { val party = stubPartyDetails() val player = stubPlayer() }) { - sdk().fire(SavePartyCommand(party)) - sdk().fire(SavePlayerCommand(party.id, player)) + sdk().fire(SavePartyCommand(partyId = party.id, party = party, players = listOf(player))) sdk().fire(DeletePlayerCommand(party.id, player.id)) } exercise { PlayerConfigPage.goTo(party.id, player.id) diff --git a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/PrepareToSpinPageE2ETest.kt b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/PrepareToSpinPageE2ETest.kt index d584f464eb..e4e4738b16 100644 --- a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/PrepareToSpinPageE2ETest.kt +++ b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/PrepareToSpinPageE2ETest.kt @@ -2,10 +2,6 @@ package com.zegreatrob.coupling.e2e.test import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.fire -import com.zegreatrob.coupling.action.pin.SavePinCommand -import com.zegreatrob.coupling.action.pin.fire -import com.zegreatrob.coupling.action.player.SavePlayerCommand -import com.zegreatrob.coupling.action.player.fire import com.zegreatrob.coupling.e2e.test.AssignedPair.assignedPairElements import com.zegreatrob.coupling.e2e.test.CouplingLogin.sdk import com.zegreatrob.coupling.e2e.test.CurrentPairAssignmentsPanel.getSaveButton @@ -35,9 +31,7 @@ class PrepareToSpinPageE2ETest { val players = (1..5).map(Companion::buildPlayer) val pin = Pin(PinId.new(), name = "e2e-pin") val sdk = sdk.await().apply { - fire(SavePartyCommand(party)) - players.forEach { fire(SavePlayerCommand(party.id, it)) } - sdk.await().fire(SavePinCommand(party.id, pin)) + fire(SavePartyCommand(partyId = party.id, party = party, players = players, pins = listOf(pin))) } FullPartyData(players, listOf(pin), party, sdk) diff --git a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/RetiredPlayersPageE2ETest.kt b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/RetiredPlayersPageE2ETest.kt index ee3bef54eb..1438060b50 100644 --- a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/RetiredPlayersPageE2ETest.kt +++ b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/RetiredPlayersPageE2ETest.kt @@ -3,7 +3,6 @@ package com.zegreatrob.coupling.e2e.test import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.fire import com.zegreatrob.coupling.action.player.DeletePlayerCommand -import com.zegreatrob.coupling.action.player.SavePlayerCommand import com.zegreatrob.coupling.action.player.fire import com.zegreatrob.coupling.e2e.test.PartyCard.element import com.zegreatrob.coupling.model.party.PartyDetails @@ -45,8 +44,7 @@ class RetiredPlayersPageE2ETest { val notDeletedPlayer = players[2] val retiredPlayers = players - notDeletedPlayer }) { - sdk.fire(SavePartyCommand(party)) - players.forEach { sdk.fire(SavePlayerCommand(party.id, it)) } + sdk.fire(SavePartyCommand(partyId = party.id, party = party, players = players)) delete(retiredPlayers, sdk, party) } exercise { RetiredPlayersPage.goTo(party.id) diff --git a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/StatisticsE2ETest.kt b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/StatisticsE2ETest.kt index 15b0e5a873..0a181e6855 100644 --- a/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/StatisticsE2ETest.kt +++ b/e2e/src/jsE2eTest/kotlin/com/zegreatrob/coupling/e2e/test/StatisticsE2ETest.kt @@ -3,7 +3,6 @@ package com.zegreatrob.coupling.e2e.test import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.fire import com.zegreatrob.coupling.action.player.DeletePlayerCommand -import com.zegreatrob.coupling.action.player.SavePlayerCommand import com.zegreatrob.coupling.action.player.fire import com.zegreatrob.coupling.e2e.test.PartyCard.element import com.zegreatrob.coupling.model.party.PartyDetails @@ -26,8 +25,7 @@ class StatisticsE2ETest { } .take(6).toList() }) { - sdk.fire(SavePartyCommand(party)) - players.forEach { player -> sdk.fire(SavePlayerCommand(party.id, player)) } + sdk.fire(SavePartyCommand(partyId = party.id, party = party, players = players)) } exercise { StatisticsPage.goTo(party.id) } verify { @@ -49,8 +47,7 @@ class StatisticsE2ETest { } .take(2).toList() }) { - sdk.fire(SavePartyCommand(party)) - players.forEach { player -> sdk.fire(SavePlayerCommand(party.id, player)) } + sdk.fire(SavePartyCommand(partyId = party.id, party = party, players = players)) sdk.fire(DeletePlayerCommand(party.id, players[0].id)) } exercise { StatisticsPage.goTo(party.id) diff --git a/libraries/action/src/commonMain/kotlin/com/zegreatrob/coupling/action/party/SavePartyCommand.kt b/libraries/action/src/commonMain/kotlin/com/zegreatrob/coupling/action/party/SavePartyCommand.kt index 3f1ea4dddc..9f6fd9e0fe 100644 --- a/libraries/action/src/commonMain/kotlin/com/zegreatrob/coupling/action/party/SavePartyCommand.kt +++ b/libraries/action/src/commonMain/kotlin/com/zegreatrob/coupling/action/party/SavePartyCommand.kt @@ -2,10 +2,20 @@ package com.zegreatrob.coupling.action.party import com.zegreatrob.coupling.action.VoidResult import com.zegreatrob.coupling.model.party.PartyDetails +import com.zegreatrob.coupling.model.party.PartyId +import com.zegreatrob.coupling.model.pin.Pin +import com.zegreatrob.coupling.model.player.Player import com.zegreatrob.testmints.action.annotation.ActionMint @ActionMint -data class SavePartyCommand(val party: PartyDetails) { +data class SavePartyCommand( + val partyId: PartyId, + val party: PartyDetails? = null, + val players: List = emptyList(), + val pins: List = emptyList(), +) { + constructor(party: PartyDetails) : this(party.id, party) + fun interface Dispatcher { suspend fun perform(command: SavePartyCommand): VoidResult } diff --git a/libraries/json/build.gradle.kts b/libraries/json/build.gradle.kts index 39920bd6df..e892df3433 100644 --- a/libraries/json/build.gradle.kts +++ b/libraries/json/build.gradle.kts @@ -27,6 +27,7 @@ kotlin { dependencies { commonMainApi(project(":libraries:model")) + commonMainImplementation(project(":libraries:action")) commonMainImplementation("io.ktor:ktor-client-content-negotiation") commonMainImplementation("io.ktor:ktor-client-core") commonMainImplementation("io.ktor:ktor-client-logging") diff --git a/libraries/json/src/commonMain/kotlin/com/zegreatrob/coupling/json/PartyDetailsMapper.kt b/libraries/json/src/commonMain/kotlin/com/zegreatrob/coupling/json/PartyDetailsMapper.kt index fa91fc43ed..edc7609e25 100644 --- a/libraries/json/src/commonMain/kotlin/com/zegreatrob/coupling/json/PartyDetailsMapper.kt +++ b/libraries/json/src/commonMain/kotlin/com/zegreatrob/coupling/json/PartyDetailsMapper.kt @@ -2,12 +2,13 @@ package com.zegreatrob.coupling.json import com.zegreatrob.coupling.model.party.PairingRule import com.zegreatrob.coupling.model.party.PartyDetails +import com.zegreatrob.coupling.model.party.PartyId import com.zegreatrob.coupling.model.party.defaultParty import kotlinx.serialization.Serializable import org.kotools.types.ExperimentalKotoolsTypesApi @OptIn(ExperimentalKotoolsTypesApi::class) -fun GqlSavePartyInput.toModel() = PartyDetails( +fun GqlSavePartyDetailsInput.toModel(partyId: PartyId) = PartyDetails( id = partyId, pairingRule = PairingRule.fromValue(pairingRule), badgesEnabled = badgesEnabled ?: defaultParty.badgesEnabled, diff --git a/libraries/json/src/commonMain/kotlin/com/zegreatrob/coupling/json/PinDetailsMapper.kt b/libraries/json/src/commonMain/kotlin/com/zegreatrob/coupling/json/PinDetailsMapper.kt index 4b09b957fe..b04c6a537c 100644 --- a/libraries/json/src/commonMain/kotlin/com/zegreatrob/coupling/json/PinDetailsMapper.kt +++ b/libraries/json/src/commonMain/kotlin/com/zegreatrob/coupling/json/PinDetailsMapper.kt @@ -27,3 +27,15 @@ fun GqlPin.toModel(): Record> = Record( isDeleted = isDeleted, timestamp = timestamp, ) + +fun GqlSavePinInput.toModel() = Pin( + id = pinId, + name = name, + icon = icon, +) + +fun GqlSavePartyPinInput.toModel() = Pin( + id = pinId, + name = name, + icon = icon, +) diff --git a/libraries/json/src/commonMain/kotlin/com/zegreatrob/coupling/json/PlayerDetailsMapper.kt b/libraries/json/src/commonMain/kotlin/com/zegreatrob/coupling/json/PlayerDetailsMapper.kt index a7f6e9a21f..cc06e6c0ed 100644 --- a/libraries/json/src/commonMain/kotlin/com/zegreatrob/coupling/json/PlayerDetailsMapper.kt +++ b/libraries/json/src/commonMain/kotlin/com/zegreatrob/coupling/json/PlayerDetailsMapper.kt @@ -35,6 +35,19 @@ fun GqlSavePlayerInput.toModel(): Player = Player( additionalEmails = unvalidatedEmails.toSet(), ) +@OptIn(ExperimentalKotoolsTypesApi::class) +fun GqlSavePartyPlayerInput.toModel(): Player = Player( + id = playerId, + badge = badge.toModel(), + name = name, + email = email, + callSignAdjective = callSignAdjective, + callSignNoun = callSignNoun, + imageURL = imageURL, + avatarType = avatarType?.let(AvatarType::valueOf), + additionalEmails = unvalidatedEmails.toSet(), +) + @OptIn(ExperimentalKotoolsTypesApi::class) fun GqlPlayer.toModel(): PartyRecord = PartyRecord( partyId.with( diff --git a/libraries/json/src/commonMain/kotlin/com/zegreatrob/coupling/json/SavePartyInputMapper.kt b/libraries/json/src/commonMain/kotlin/com/zegreatrob/coupling/json/SavePartyInputMapper.kt new file mode 100644 index 0000000000..c80fe510e3 --- /dev/null +++ b/libraries/json/src/commonMain/kotlin/com/zegreatrob/coupling/json/SavePartyInputMapper.kt @@ -0,0 +1,10 @@ +package com.zegreatrob.coupling.json + +import com.zegreatrob.coupling.action.party.SavePartyCommand + +fun GqlSavePartyInput.toCommand() = SavePartyCommand( + partyId = partyId, + party = party?.toModel(partyId), + players = players?.items?.map { it.toModel() }.orEmpty(), + pins = pins?.items?.map { it.toModel() }.orEmpty(), +) diff --git a/notes/save-party-plan.md b/notes/save-party-plan.md new file mode 100644 index 0000000000..89cc3a5c21 --- /dev/null +++ b/notes/save-party-plan.md @@ -0,0 +1,79 @@ +# SaveParty Consolidation + Test Perf Work Plan + +## Ultimate Goal +Unify all save behavior (party, players, pins) through **SaveParty** so tests and app use the same API path, keep implicit upsert, require `party` only for new party creation, and update tests to match without changing expectations. Then run tests and measure any performance delta (including seed command impact). Keep CDN/config-cache changes isolated on their own branch. + +## Current Status (As Of 2026-03-03) + +### Completed +1. **Branch split done** + - Branch `cdn-config-cache` created with commit `a4cd77482` containing only: + - `client/build.gradle.kts`: disable config cache for Vite tasks; validate non-empty `cdn.json` before Vite. + - `master` contains only SaveParty refactor work. + +2. **Schema + core SaveParty refactor done** + - `SavePartyInput` now supports sections: `party`, `players`, `pins`. + - `SavePartyCommand` signature expanded: `(partyId, party?, players, pins)`. + - Seed mutation removed (schema + server + sdk). + - Server SaveParty now saves optional `party` and updates players/pins via repositories. + - SaveParty requires `party` only for new party creation; implicit upsert only. + +3. **Mapping/SDK/resolver updates done** + - New `SavePartyInputMapper` and input DTOs. + - Resolver uses new mapper. + - SDK SaveParty dispatcher now builds new input shape. + +4. **Client runtime uses SaveParty for player/pin saves** + - PlayerConfig, UpdatingPlayerList, ContributorMenu, Contribution popups, PinConfig updated to SaveParty. + +5. **Many tests updated to SaveParty** + - e2e tests updated already (per prior summary). + - client component tests updated to SaveParty. + - sdk commonTest updated to SaveParty (players/pins). + +### Still Pending / Verify +1. **Run tests** + - ✅ `./gradlew test --no-configuration-cache` succeeded on 2026-03-05 after disabling `:testDistributionWebSocketCheck` when no server property is set. + +2. **Remove/adjust legacy SavePlayer/SavePin if desired** + - Current code keeps SavePlayer/SavePin actions/resolvers/dispatchers for compatibility. Decide whether to deprecate or keep. Tests now use SaveParty. + +3. **Regenerate GraphQL code if needed** + - If build fails on generated types, run the appropriate Apollo/Gradle tasks. + +4. **Measure performance delta** + - After tests pass, re-run e2e/perf and compare with prior baseline: + - Previous seed mutation delta: 162.779s vs 168.486s (~5.7s faster without seed) from earlier run. Re-measure after new SaveParty path. + - ✅ `./gradlew :e2e:e2eRun --no-configuration-cache --rerun-tasks` completed on 2026-03-05 (total build time 3m 48s). + +## Work Log (Edits Already Made) + +### Key Files Changed +- Schema: `server/src/jsMain/resources/schema.graphqls` +- SaveParty model: `libraries/action/src/commonMain/kotlin/.../SavePartyCommand.kt` +- Server SaveParty dispatcher: `server/actionz/src/jsMain/kotlin/.../ServerSavePartyCommandDispatcher.kt` +- Resolver: `server/src/jsMain/kotlin/.../SavePartyResolver.kt` +- SDK: `sdk/src/commonMain/kotlin/.../SdkSavePartyCommandDispatcher.kt` +- JSON mappers: `libraries/json/.../SavePartyInputMapper.kt`, plus player/pin/party mappers. +- Client components: PlayerConfig, UpdatingPlayerList, ContributorMenu, Contribution popups, PinConfig. +- Tests: client component tests; sdk commonTests; e2e tests. + +### Tests Updated To SaveParty +- Client: `UpdatingPlayerListTest`, `PlayerConfigTest`, `PinConfigEditorTest`, `ContributorMenuTest`. +- SDK: `SdkPlayerTest`, `SdkPinTest`, `SdkPartyTest`, `SdkUserTest`, `SpinTest`, `RequestCombineEndpointTest`, `SdkPairsRecentTimesTest`, `SavePartyState`. + +### Known Build Failure +- Running `./gradlew test --no-configuration-cache` fails at `:testDistributionWebSocketCheck` with: + - "Cannot query the value of task ':testDistributionWebSocketCheck' property 'server' because it has no value available." + - ✅ Fixed locally by skipping the task when no test distribution server property is set. + +## Next Steps (Recommended Order) +1. Fix/disable Develocity test distribution WebSocket check for local runs. +2. Re-run `./gradlew test --no-configuration-cache`. +3. Fix any remaining test failures. +4. Run e2e/perf measurement and compare results. + +## Notes +- User preference: keep **implicit upsert**; require `party` only for new party creation. +- User preference: keep API usage as close to app as possible; tests should be updated to new SaveParty path rather than changing expected behavior. +- Avoid config cache for Vite tasks (done in separate branch `cdn-config-cache`). diff --git a/sdk/src/commonMain/graphql/savePin.graphql b/sdk/src/commonMain/graphql/savePin.graphql deleted file mode 100644 index cd6d33c609..0000000000 --- a/sdk/src/commonMain/graphql/savePin.graphql +++ /dev/null @@ -1,3 +0,0 @@ -mutation savePin($input: SavePinInput!) { - savePin(input: $input) -} diff --git a/sdk/src/commonMain/graphql/savePlayer.graphql b/sdk/src/commonMain/graphql/savePlayer.graphql deleted file mode 100644 index 5ebacceda9..0000000000 --- a/sdk/src/commonMain/graphql/savePlayer.graphql +++ /dev/null @@ -1,3 +0,0 @@ -mutation savePlayer($input: SavePlayerInput!) { - savePlayer(input: $input) -} diff --git a/sdk/src/commonMain/kotlin/com/zegreatrob/coupling/sdk/SdkSavePartyCommandDispatcher.kt b/sdk/src/commonMain/kotlin/com/zegreatrob/coupling/sdk/SdkSavePartyCommandDispatcher.kt index ec12a2a68e..a7350f2534 100644 --- a/sdk/src/commonMain/kotlin/com/zegreatrob/coupling/sdk/SdkSavePartyCommandDispatcher.kt +++ b/sdk/src/commonMain/kotlin/com/zegreatrob/coupling/sdk/SdkSavePartyCommandDispatcher.kt @@ -5,21 +5,26 @@ import com.zegreatrob.coupling.action.VoidResult import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.model.party.PairingRule import com.zegreatrob.coupling.model.party.PartyDetails +import com.zegreatrob.coupling.model.pin.Pin +import com.zegreatrob.coupling.model.player.Player import com.zegreatrob.coupling.sdk.gql.GqlTrait import com.zegreatrob.coupling.sdk.schema.SavePartyMutation import com.zegreatrob.coupling.sdk.schema.type.SavePartyInput +import com.zegreatrob.coupling.sdk.schema.type.SavePartyPinInput +import com.zegreatrob.coupling.sdk.schema.type.SavePartyPinsInput +import com.zegreatrob.coupling.sdk.schema.type.SavePartyPlayerInput +import com.zegreatrob.coupling.sdk.schema.type.SavePartyPlayersInput interface SdkSavePartyCommandDispatcher : SavePartyCommand.Dispatcher, GqlTrait { override suspend fun perform(command: SavePartyCommand): VoidResult { - SavePartyMutation(command.party.savePartyInput()).execute() + SavePartyMutation(command.savePartyInput()).execute() return VoidResult.Accepted } } -private fun PartyDetails.savePartyInput() = SavePartyInput( - partyId = id, +internal fun PartyDetails.savePartyDetailsInput() = com.zegreatrob.coupling.sdk.schema.type.SavePartyDetailsInput( name = presentIfNotNull(name), email = presentIfNotNull(email), pairingRule = presentIfNotNull(PairingRule.toValue(pairingRule)), @@ -30,3 +35,44 @@ private fun PartyDetails.savePartyInput() = SavePartyInput( animationsEnabled = presentIfNotNull(animationEnabled), animationSpeed = presentIfNotNull(animationSpeed), ) + +internal fun SavePartyCommand.savePartyInput() = SavePartyInput( + partyId = partyId, + party = presentIfNotNull(party?.savePartyDetailsInput()), + players = presentIfNotNull( + if (players.isEmpty()) { + null + } else { + SavePartyPlayersInput( + items = players.map { it.toSavePartyPlayerInput() }, + ) + }, + ), + pins = presentIfNotNull( + if (pins.isEmpty()) { + null + } else { + SavePartyPinsInput( + items = pins.map { it.toSavePartyPinInput() }, + ) + }, + ), +) + +private fun Player.toSavePartyPlayerInput() = SavePartyPlayerInput( + playerId = id, + name = name, + email = email, + badge = badge.toSerializable(), + callSignAdjective = callSignAdjective, + callSignNoun = callSignNoun, + imageURL = presentIfNotNull(imageURL), + avatarType = presentIfNotNull(avatarType?.name), + unvalidatedEmails = additionalEmails.toList(), +) + +private fun Pin.toSavePartyPinInput() = SavePartyPinInput( + pinId = id, + icon = icon, + name = name, +) diff --git a/sdk/src/commonMain/kotlin/com/zegreatrob/coupling/sdk/SdkSavePinCommandDispatcher.kt b/sdk/src/commonMain/kotlin/com/zegreatrob/coupling/sdk/SdkSavePinCommandDispatcher.kt index 4b27518016..30e736bfe3 100644 --- a/sdk/src/commonMain/kotlin/com/zegreatrob/coupling/sdk/SdkSavePinCommandDispatcher.kt +++ b/sdk/src/commonMain/kotlin/com/zegreatrob/coupling/sdk/SdkSavePinCommandDispatcher.kt @@ -1,27 +1,21 @@ package com.zegreatrob.coupling.sdk import com.zegreatrob.coupling.action.VoidResult +import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.pin.SavePinCommand -import com.zegreatrob.coupling.model.party.PartyElement -import com.zegreatrob.coupling.model.party.with -import com.zegreatrob.coupling.model.pin.Pin import com.zegreatrob.coupling.sdk.gql.GqlTrait -import com.zegreatrob.coupling.sdk.schema.SavePinMutation -import com.zegreatrob.coupling.sdk.schema.type.SavePinInput +import com.zegreatrob.coupling.sdk.schema.SavePartyMutation interface SdkSavePinCommandDispatcher : SavePinCommand.Dispatcher, GqlTrait { override suspend fun perform(command: SavePinCommand): VoidResult.Accepted { - val (partyId, pin) = command - SavePinMutation(partyId.with(pin).savePinInput()).execute() + SavePartyMutation( + SavePartyCommand( + partyId = command.id, + pins = listOf(command.pin), + ).savePartyInput(), + ).execute() return VoidResult.Accepted } } - -private fun PartyElement.savePinInput() = SavePinInput( - partyId = partyId, - pinId = element.id, - icon = element.icon, - name = element.name, -) diff --git a/sdk/src/commonMain/kotlin/com/zegreatrob/coupling/sdk/SdkSavePlayerCommandDispatcher.kt b/sdk/src/commonMain/kotlin/com/zegreatrob/coupling/sdk/SdkSavePlayerCommandDispatcher.kt index 1f8d64077b..d319d926d1 100644 --- a/sdk/src/commonMain/kotlin/com/zegreatrob/coupling/sdk/SdkSavePlayerCommandDispatcher.kt +++ b/sdk/src/commonMain/kotlin/com/zegreatrob/coupling/sdk/SdkSavePlayerCommandDispatcher.kt @@ -1,33 +1,21 @@ package com.zegreatrob.coupling.sdk -import com.apollographql.apollo.api.Optional.Companion.presentIfNotNull import com.zegreatrob.coupling.action.VoidResult +import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.player.SavePlayerCommand -import com.zegreatrob.coupling.model.party.PartyElement -import com.zegreatrob.coupling.model.party.with -import com.zegreatrob.coupling.model.player.Player import com.zegreatrob.coupling.sdk.gql.GqlTrait -import com.zegreatrob.coupling.sdk.schema.SavePlayerMutation -import com.zegreatrob.coupling.sdk.schema.type.SavePlayerInput +import com.zegreatrob.coupling.sdk.schema.SavePartyMutation interface SdkSavePlayerCommandDispatcher : SavePlayerCommand.Dispatcher, GqlTrait { - override suspend fun perform(command: SavePlayerCommand) = with(command) { - SavePlayerMutation(partyId.with(player).input()).execute() - VoidResult.Accepted + override suspend fun perform(command: SavePlayerCommand): VoidResult.Accepted { + SavePartyMutation( + SavePartyCommand( + partyId = command.partyId, + players = listOf(command.player), + ).savePartyInput(), + ).execute() + return VoidResult.Accepted } } - -internal fun PartyElement.input() = SavePlayerInput( - partyId = partyId, - playerId = element.id, - name = element.name, - email = element.email, - badge = element.badge.toSerializable(), - callSignAdjective = element.callSignAdjective, - callSignNoun = element.callSignNoun, - imageURL = presentIfNotNull(element.imageURL), - avatarType = presentIfNotNull(element.avatarType?.name), - unvalidatedEmails = element.additionalEmails.toList(), -) diff --git a/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/RequestCombineEndpointTest.kt b/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/RequestCombineEndpointTest.kt index 59aa49279f..2697793dc3 100644 --- a/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/RequestCombineEndpointTest.kt +++ b/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/RequestCombineEndpointTest.kt @@ -2,10 +2,6 @@ package com.zegreatrob.coupling.sdk import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.fire -import com.zegreatrob.coupling.action.pin.SavePinCommand -import com.zegreatrob.coupling.action.pin.fire -import com.zegreatrob.coupling.action.player.SavePlayerCommand -import com.zegreatrob.coupling.action.player.fire import com.zegreatrob.coupling.model.party.PartyDetails import com.zegreatrob.coupling.model.party.PartyId import com.zegreatrob.coupling.model.pin.Pin @@ -40,8 +36,8 @@ class RequestCombineEndpointTest { } }) { sdk.fire(SavePartyCommand(party)) - pinsToSave.forEach { setupScope.launch { sdk.fire(SavePinCommand(party.id, it)) } } - playersToSave.forEach { setupScope.launch { sdk.fire(SavePlayerCommand(party.id, it)) } } + pinsToSave.forEach { setupScope.launch { sdk.fire(SavePartyCommand(partyId = party.id, pins = listOf(it))) } } + playersToSave.forEach { setupScope.launch { sdk.fire(SavePartyCommand(partyId = party.id, players = listOf(it))) } } } exercise { coroutineScope { sdk.fire(GqlQuery(PlayersAndPinsQuery(party.id))) diff --git a/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SavePartyState.kt b/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SavePartyState.kt index e0834ed512..3932f150db 100644 --- a/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SavePartyState.kt +++ b/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SavePartyState.kt @@ -4,8 +4,6 @@ import com.zegreatrob.coupling.action.pairassignmentdocument.SavePairAssignments import com.zegreatrob.coupling.action.pairassignmentdocument.fire import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.fire -import com.zegreatrob.coupling.action.player.SavePlayerCommand -import com.zegreatrob.coupling.action.player.fire import com.zegreatrob.coupling.model.pairassignmentdocument.PairingSet import com.zegreatrob.coupling.model.party.PartyDetails import com.zegreatrob.coupling.model.player.Player @@ -19,10 +17,8 @@ suspend fun savePartyState( ) = coroutineScope { with(sdk()) { fire(SavePartyCommand(party)) - players.forEach { - launch { - fire(SavePlayerCommand(party.id, it)) - } + launch { + fire(SavePartyCommand(partyId = party.id, players = players)) } pairAssignmentDocs.forEach { launch { @@ -40,7 +36,7 @@ suspend fun savePartyStateWithFixedPlayerOrder( with(sdk()) { fire(SavePartyCommand(party)) launch { - players.forEach { fire(SavePlayerCommand(party.id, it)) } + fire(SavePartyCommand(partyId = party.id, players = players)) } pairAssignmentDocs.forEach { launch { diff --git a/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkPairsRecentTimesTest.kt b/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkPairsRecentTimesTest.kt index a5dace6d5f..9adb1e59a9 100644 --- a/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkPairsRecentTimesTest.kt +++ b/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkPairsRecentTimesTest.kt @@ -5,8 +5,6 @@ import com.zegreatrob.coupling.action.pairassignmentdocument.SavePairAssignments import com.zegreatrob.coupling.action.pairassignmentdocument.fire import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.fire -import com.zegreatrob.coupling.action.player.SavePlayerCommand -import com.zegreatrob.coupling.action.player.fire import com.zegreatrob.coupling.model.pairassignmentdocument.PairingSet import com.zegreatrob.coupling.model.pairassignmentdocument.PairingSetId import com.zegreatrob.coupling.model.pairassignmentdocument.pairOf @@ -150,7 +148,7 @@ class SdkPairsRecentTimesTest { with(sdk()) { fire(SavePartyCommand(party)) players.forEach { - fire(SavePlayerCommand(party.id, it)) + fire(SavePartyCommand(partyId = party.id, players = listOf(it))) } history.forEach { fire(SavePairAssignmentsCommand(party.id, it)) diff --git a/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkPartyTest.kt b/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkPartyTest.kt index a1124fa242..a46dc9706b 100644 --- a/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkPartyTest.kt +++ b/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkPartyTest.kt @@ -7,7 +7,6 @@ import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.SaveSlackIntegrationCommand import com.zegreatrob.coupling.action.party.fire import com.zegreatrob.coupling.action.player.DeletePlayerCommand -import com.zegreatrob.coupling.action.player.SavePlayerCommand import com.zegreatrob.coupling.action.player.fire import com.zegreatrob.coupling.model.AccessType import com.zegreatrob.coupling.model.party.PartyDetails @@ -95,7 +94,7 @@ class SdkPartyTest { val playerMatchingSdkUser = stubPlayer().copy(email = PRIMARY_AUTHORIZED_USER_EMAIL) }) { sdk().fire(SavePartyCommand(party)) - sdk().fire(SavePlayerCommand(party.id, playerMatchingSdkUser)) + sdk().fire(SavePartyCommand(partyId = party.id, players = listOf(playerMatchingSdkUser))) } exercise { sdk().fire(GqlQuery(PartyAccessTypeAndListQuery(party.id))) } verify { result -> @@ -130,7 +129,7 @@ class SdkPartyTest { fun getWillReturnAnyPartyThatHasPlayerWithGivenEmail() = setupWithPlayerMatchingUserTwoSdks { with(altSdk()) { fire(SavePartyCommand(party)) - fire(SavePlayerCommand(party.id, playerMatchingSdkUser)) + fire(SavePartyCommand(partyId = party.id, players = listOf(playerMatchingSdkUser))) } } exercise { sdk().fire(GqlQuery(PartyAccessTypeDetailsListQuery())) @@ -149,9 +148,9 @@ class SdkPartyTest { with(altSdk()) { fire(SavePartyCommand(party)) fire( - SavePlayerCommand( - party.id, - stubPlayer().copy(additionalEmails = setOf(PRIMARY_AUTHORIZED_USER_EMAIL)), + SavePartyCommand( + partyId = party.id, + players = listOf(stubPlayer().copy(additionalEmails = setOf(PRIMARY_AUTHORIZED_USER_EMAIL))), ), ) } @@ -168,8 +167,8 @@ class SdkPartyTest { fun getWillNotReturnPartyIfPlayerHadEmailButThenHadItRemoved() = setupWithPlayerMatchingUserTwoSdks { with(altSdk()) { fire(SavePartyCommand(party)) - fire(SavePlayerCommand(party.id, playerMatchingSdkUser)) - fire(SavePlayerCommand(party.id, playerMatchingSdkUser.copy(email = "something else"))) + fire(SavePartyCommand(partyId = party.id, players = listOf(playerMatchingSdkUser))) + fire(SavePartyCommand(partyId = party.id, players = listOf(playerMatchingSdkUser.copy(email = "something else")))) } } exercise { sdk().fire(GqlQuery(PartyAccessTypeDetailsListQuery())) @@ -183,7 +182,7 @@ class SdkPartyTest { fun getWillNotReturnPartyIfPlayerHadEmailButPlayerWasRemoved() = setupWithPlayerMatchingUserTwoSdks { with(altSdk()) { fire(SavePartyCommand(party)) - fire(SavePlayerCommand(party.id, playerMatchingSdkUser)) + fire(SavePartyCommand(partyId = party.id, players = listOf(playerMatchingSdkUser))) fire(DeletePlayerCommand(party.id, playerMatchingSdkUser.id)) } } exercise { diff --git a/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkPinTest.kt b/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkPinTest.kt index a0024f0e02..6699c8ebc5 100644 --- a/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkPinTest.kt +++ b/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkPinTest.kt @@ -4,7 +4,6 @@ import com.zegreatrob.coupling.action.party.DeletePartyCommand import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.fire import com.zegreatrob.coupling.action.pin.DeletePinCommand -import com.zegreatrob.coupling.action.pin.SavePinCommand import com.zegreatrob.coupling.action.pin.fire import com.zegreatrob.coupling.model.pin.PinId import com.zegreatrob.coupling.repository.validation.verifyWithWait @@ -47,7 +46,7 @@ class SdkPinTest { } }, ) exercise { - pins.forEach { sdk.fire(SavePinCommand(party.id, it)) } + pins.forEach { sdk.fire(SavePartyCommand(partyId = party.id, pins = listOf(it))) } } verifyWithWait { sdk.fire(GqlQuery(PinListQuery(party.id))) ?.party @@ -85,7 +84,7 @@ class SdkPinTest { ) } }) exercise { - pins.forEach { sdk.fire(SavePinCommand(party.id, it)) } + pins.forEach { sdk.fire(SavePartyCommand(partyId = party.id, pins = listOf(it))) } sdk.fire(DeletePinCommand(party.id, this.pins[1].id)) } verifyWithWait { sdk.fire(GqlQuery(PinListQuery(party.id))) @@ -105,7 +104,7 @@ class SdkPinTest { suspend fun otherSdk() = altAuthorizedSdkDeferred.await() }) { otherSdk().fire(SavePartyCommand(otherParty)) - otherSdk().fire(SavePinCommand(otherParty.id, stubPin())) + otherSdk().fire(SavePartyCommand(partyId = otherParty.id, pins = listOf(stubPin()))) } exercise { sdk().fire(GqlQuery(PinListQuery(otherParty.id))) ?.party @@ -124,7 +123,7 @@ class SdkPinTest { }) { sdk = sdk() sdk.fire(SavePartyCommand(party)) - sdk.fire(SavePinCommand(party.id, pin)) + sdk.fire(SavePartyCommand(partyId = party.id, pins = listOf(pin))) } exercise { sdk.fire(GqlQuery(PinRecordListQuery(party.id))) ?.party diff --git a/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkPlayerTest.kt b/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkPlayerTest.kt index c642cdafa6..5a79297796 100644 --- a/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkPlayerTest.kt +++ b/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkPlayerTest.kt @@ -5,7 +5,6 @@ import com.zegreatrob.coupling.action.party.DeletePartyCommand import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.fire import com.zegreatrob.coupling.action.player.DeletePlayerCommand -import com.zegreatrob.coupling.action.player.SavePlayerCommand import com.zegreatrob.coupling.action.player.fire import com.zegreatrob.coupling.model.player.PlayerId import com.zegreatrob.coupling.repository.validation.assertHasIds @@ -58,9 +57,9 @@ class SdkPlayerTest { val updatedPlayer = player.copy(name = "Timmy!") } }) { - sdk.fire(SavePlayerCommand(party.id, player)) + sdk.fire(SavePartyCommand(partyId = party.id, players = listOf(player))) } exercise { - sdk.fire(SavePlayerCommand(party.id, updatedPlayer)) + sdk.fire(SavePartyCommand(partyId = party.id, players = listOf(updatedPlayer))) sdk().fire(GqlQuery(PartyPlayersDetailsQuery(party.id))) ?.party ?.playerList @@ -77,7 +76,7 @@ class SdkPlayerTest { val player = stubPlayer() } }) { - sdk.fire(SavePlayerCommand(partyId, player)) + sdk.fire(SavePartyCommand(partyId = partyId, players = listOf(player))) } exercise { sdk.fire(DeletePlayerCommand(partyId, player.id)) sdk().fire(GqlQuery(PartyPlayersDetailsQuery(partyId))) @@ -113,7 +112,7 @@ class SdkPlayerTest { } }, ) { - sdk.fire(SavePlayerCommand(party.id, player)) + sdk.fire(SavePartyCommand(partyId = party.id, players = listOf(player))) sdk.fire(DeletePlayerCommand(party.id, player.id)) } exercise { sdk().fire(GqlQuery(PartyRetiredPlayersDetailsQuery(party.id))) @@ -139,9 +138,9 @@ class SdkPlayerTest { } }, ) { - sdk.fire(SavePlayerCommand(party.id, player)) + sdk.fire(SavePartyCommand(partyId = party.id, players = listOf(player))) sdk.fire(DeletePlayerCommand(party.id, player.id)) - sdk.fire(SavePlayerCommand(party.id, similarPlayer)) + sdk.fire(SavePartyCommand(partyId = party.id, players = listOf(similarPlayer))) sdk.fire(DeletePlayerCommand(party.id, similarPlayer.id)) } exercise { sdk().fire(GqlQuery(PartyRetiredPlayersDetailsQuery(party.id))) @@ -166,9 +165,9 @@ class SdkPlayerTest { } }, ) { - sdk.fire(SavePlayerCommand(party.id, player)) + sdk.fire(SavePartyCommand(partyId = party.id, players = listOf(player))) sdk.fire(DeletePlayerCommand(party.id, player.id)) - sdk.fire(SavePlayerCommand(party.id, player2)) + sdk.fire(SavePartyCommand(partyId = party.id, players = listOf(player2))) sdk.fire(DeletePlayerCommand(party.id, player2.id)) } exercise { sdk().fire(GqlQuery(PartyRetiredPlayersDetailsQuery(party.id))) @@ -188,9 +187,9 @@ class SdkPlayerTest { val playerId = player.id } }) exercise { - sdk.fire(SavePlayerCommand(party.id, player)) + sdk.fire(SavePartyCommand(partyId = party.id, players = listOf(player))) sdk.fire(DeletePlayerCommand(party.id, playerId)) - sdk.fire(SavePlayerCommand(party.id, player)) + sdk.fire(SavePartyCommand(partyId = party.id, players = listOf(player))) sdk.fire(DeletePlayerCommand(party.id, playerId)) } verifyWithWait { sdk().fire(GqlQuery(PartyRetiredPlayersDetailsQuery(party.id))) @@ -208,7 +207,7 @@ class SdkPlayerTest { val players = stubPlayers(3) } }) { - players.forEach { setupScope.launch { sdk.fire(SavePlayerCommand(partyId, it)) } } + players.forEach { setupScope.launch { sdk.fire(SavePartyCommand(partyId = partyId, players = listOf(it))) } } } exercise { sdk().fire(GqlQuery(PartyPlayersDetailsQuery(partyId))) ?.party @@ -227,7 +226,7 @@ class SdkPlayerTest { } }) { coroutineScope { - players.forEach { launch { sdk.fire(SavePlayerCommand(partyId, it)) } } + players.forEach { launch { sdk.fire(SavePartyCommand(partyId = partyId, players = listOf(it))) } } } } exercise { sdk().fire(GqlQuery(PartySpinsUntilFullRotationQuery(partyId))) @@ -251,7 +250,7 @@ class SdkPlayerTest { ) } }) { - sdk.fire(SavePlayerCommand(party.id, player)) + sdk.fire(SavePartyCommand(partyId = party.id, players = listOf(player))) } exercise { sdk().fire(GqlQuery(PartyPlayersDetailsQuery(party.id))) ?.party @@ -273,8 +272,8 @@ class SdkPlayerTest { } }) { sdk.fire(SavePartyCommand(stubPartyDetails().copy(id = partyId2))) - sdk.fire(SavePlayerCommand(partyId, player1)) - sdk.fire(SavePlayerCommand(partyId2, player2)) + sdk.fire(SavePartyCommand(partyId = partyId, players = listOf(player1))) + sdk.fire(SavePartyCommand(partyId = partyId2, players = listOf(player2))) } exercise { sdk().fire(GqlQuery(PartyPlayersDetailsQuery(partyId))) ?.party @@ -294,7 +293,7 @@ class SdkPlayerTest { val player = stubPlayer() } }) exercise { - sdk.fire(SavePlayerCommand(party.id, player)) + sdk.fire(SavePartyCommand(partyId = party.id, players = listOf(player))) sdk.fire(DeletePlayerCommand(party.id, player.id)) sdk().fire(GqlQuery(PartyRetiredPlayersDataQuery(party.id))) ?.party @@ -317,7 +316,7 @@ class SdkPlayerTest { val player = stubPlayer() } }) exercise { - sdk.fire(SavePlayerCommand(party.id, player)) + sdk.fire(SavePartyCommand(partyId = party.id, players = listOf(player))) sdk().fire(GqlQuery(PartyPlayersDataQuery(party.id))) ?.party ?.playerList @@ -340,7 +339,7 @@ class SdkPlayerTest { val party = stubPartyDetails() }) { otherSdk.fire(SavePartyCommand(party)) - otherSdk.fire(SavePlayerCommand(party.id, stubPlayer())) + otherSdk.fire(SavePartyCommand(partyId = party.id, players = listOf(stubPlayer()))) } exercise { sdk.fire(GqlQuery(PartyPlayersDetailsQuery(party.id))) ?.party @@ -367,7 +366,7 @@ class SdkPlayerTest { }) { otherSdk.fire(SavePartyCommand(party)) } exercise { - sdk.fire(SavePlayerCommand(party.id, player)) + sdk.fire(SavePartyCommand(partyId = party.id, players = listOf(player))) otherSdk.fire(GqlQuery(PartyPlayersDetailsQuery(party.id))) ?.party ?.playerList diff --git a/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkUserTest.kt b/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkUserTest.kt index ee52e6b75d..1611bd9ad6 100644 --- a/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkUserTest.kt +++ b/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SdkUserTest.kt @@ -2,8 +2,6 @@ package com.zegreatrob.coupling.sdk import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.fire -import com.zegreatrob.coupling.action.player.SavePlayerCommand -import com.zegreatrob.coupling.action.player.fire import com.zegreatrob.coupling.action.user.ConnectUserCommand import com.zegreatrob.coupling.action.user.CreateConnectUserSecretCommand import com.zegreatrob.coupling.action.user.DisconnectUserCommand @@ -41,7 +39,7 @@ class SdkUserTest { val player = stubPlayer().copy(email = PRIMARY_AUTHORIZED_USER_EMAIL) }) { sdk().fire(SavePartyCommand(party)) - sdk().fire(SavePlayerCommand(party.id, player)) + sdk().fire(SavePartyCommand(partyId = party.id, players = listOf(player))) } exercise { sdk().fire(GqlQuery(UserPlayersQuery())) } verify { result -> diff --git a/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SpinTest.kt b/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SpinTest.kt index e37c238764..6956ac22c3 100644 --- a/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SpinTest.kt +++ b/sdk/src/commonTest/kotlin/com/zegreatrob/coupling/sdk/SpinTest.kt @@ -7,10 +7,6 @@ import com.zegreatrob.coupling.action.pairassignmentdocument.fire import com.zegreatrob.coupling.action.party.DeletePartyCommand import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.fire -import com.zegreatrob.coupling.action.pin.SavePinCommand -import com.zegreatrob.coupling.action.pin.fire -import com.zegreatrob.coupling.action.player.SavePlayerCommand -import com.zegreatrob.coupling.action.player.fire import com.zegreatrob.coupling.model.forEach import com.zegreatrob.coupling.model.get import com.zegreatrob.coupling.model.map @@ -63,7 +59,7 @@ class SpinTest { }) { coroutineScope { sdk.fire(SavePartyCommand(party)) - players.forEach { launch { sdk.fire(SavePlayerCommand(party.id, it)) } } + players.forEach { launch { sdk.fire(SavePartyCommand(partyId = party.id, players = listOf(it))) } } } } exercise { sdk.fire(SpinCommand(party.id, players.map { it.id }, emptyList())) @@ -232,9 +228,9 @@ class SpinTest { ) = coroutineScope { with(sdk) { fire(SavePartyCommand(party)) - players.forEach { launch { fire(SavePlayerCommand(party.id, it)) } } + players.forEach { launch { fire(SavePartyCommand(partyId = party.id, players = listOf(it))) } } history.forEach { launch { sdk.fire(SavePairAssignmentsCommand(party.id, it)) } } - pins.forEach { launch { sdk.fire(SavePinCommand(party.id, it)) } } + pins.forEach { launch { sdk.fire(SavePartyCommand(partyId = party.id, pins = listOf(it))) } } } } } diff --git a/server/actionz/src/jsMain/kotlin/com/zegreatrob/coupling/server/action/party/ServerSavePartyCommandDispatcher.kt b/server/actionz/src/jsMain/kotlin/com/zegreatrob/coupling/server/action/party/ServerSavePartyCommandDispatcher.kt index 1f5989006e..0cf8143333 100644 --- a/server/actionz/src/jsMain/kotlin/com/zegreatrob/coupling/server/action/party/ServerSavePartyCommandDispatcher.kt +++ b/server/actionz/src/jsMain/kotlin/com/zegreatrob/coupling/server/action/party/ServerSavePartyCommandDispatcher.kt @@ -5,6 +5,9 @@ import com.zegreatrob.coupling.action.VoidResult import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.model.PartyRecord import com.zegreatrob.coupling.model.party.PartyDetails +import com.zegreatrob.coupling.model.party.PartyElement +import com.zegreatrob.coupling.model.party.PartyId +import com.zegreatrob.coupling.model.pin.Pin import com.zegreatrob.coupling.model.player.Player import com.zegreatrob.coupling.model.user.CurrentUserProvider import com.zegreatrob.coupling.model.user.UserDetails @@ -12,6 +15,8 @@ import com.zegreatrob.coupling.repository.await import com.zegreatrob.coupling.repository.party.PartyIdGetSyntax import com.zegreatrob.coupling.repository.party.PartyRepository import com.zegreatrob.coupling.repository.party.PartySaveSyntax +import com.zegreatrob.coupling.repository.pin.PinSave +import com.zegreatrob.coupling.repository.player.PlayerSave import com.zegreatrob.coupling.server.action.user.UserSaveSyntax import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope @@ -29,6 +34,8 @@ interface ServerSavePartyCommandDispatcher : CurrentUserProvider { override val partyRepository: PartyRepository + val playerSaveRepository: PlayerSave + val pinSaveRepository: PinSave override suspend fun perform(command: SavePartyCommand) = if (command.isAuthorizedToSave()) { command.savePartyAndUser() @@ -38,11 +45,15 @@ interface ServerSavePartyCommandDispatcher : } private suspend fun SavePartyCommand.savePartyAndUser() = withContext(currentCoroutineContext()) { - launch { party.save() } - launch { - currentUser.copy(authorizedPartyIds = currentUser.authorizedPartyIds + party.id) - .saveIfUserChanged() + party?.let { partyDetails -> + launch { partyDetails.save() } + launch { + currentUser.copy(authorizedPartyIds = currentUser.authorizedPartyIds + partyDetails.id) + .saveIfUserChanged() + } } + launch { players.forEach { playerSaveRepository.save(PartyElement(partyId, it)) } } + launch { pins.forEach { pinSaveRepository.save(PartyElement(partyId, it)) } } } private suspend fun UserDetails.saveIfUserChanged() = if (this != currentUser) save() else Unit @@ -51,11 +62,15 @@ interface ServerSavePartyCommandDispatcher : val (connectedUsers, loadedParty, players) = coroutineScope { await( async { loadCurrentConnectedUsers() }, - async { party.id.get() }, + async { partyId.get() }, async { currentUser.loadPlayers() }, ) } - return loadedParty.partyIsNew() || authorizedPartyIds(connectedUsers, players).contains(party.id) + val isNewParty = loadedParty.partyIsNew() + if (isNewParty && party == null) { + return false + } + return isNewParty || authorizedPartyIds(connectedUsers, players).contains(partyId) } private fun authorizedPartyIds(users: List, players: List>) = players.map { it.data.partyId } + users.flatMap { it.authorizedPartyIds } diff --git a/server/actionz/src/jsTest/kotlin/com/zegreatrob/coupling/server/action/party/SavePartyCommandTest.kt b/server/actionz/src/jsTest/kotlin/com/zegreatrob/coupling/server/action/party/SavePartyCommandTest.kt new file mode 100644 index 0000000000..b2453da35f --- /dev/null +++ b/server/actionz/src/jsTest/kotlin/com/zegreatrob/coupling/server/action/party/SavePartyCommandTest.kt @@ -0,0 +1,95 @@ +package com.zegreatrob.coupling.server.action.party + +import com.zegreatrob.coupling.action.VoidResult +import com.zegreatrob.coupling.action.party.SavePartyCommand +import com.zegreatrob.coupling.model.Record +import com.zegreatrob.coupling.model.party.PartyDetails +import com.zegreatrob.coupling.model.party.PartyElement +import com.zegreatrob.coupling.model.party.PartyId +import com.zegreatrob.coupling.model.party.PartyIntegration +import com.zegreatrob.coupling.model.pin.Pin +import com.zegreatrob.coupling.model.player.Badge +import com.zegreatrob.coupling.model.player.Player +import com.zegreatrob.coupling.model.user.UserDetails +import com.zegreatrob.coupling.repository.party.PartyRepository +import com.zegreatrob.coupling.repository.pin.PinSave +import com.zegreatrob.coupling.repository.player.PlayerListGetByEmail +import com.zegreatrob.coupling.repository.player.PlayerSave +import com.zegreatrob.coupling.repository.user.UserRepository +import com.zegreatrob.coupling.stubmodel.stubPlayer +import com.zegreatrob.coupling.stubmodel.stubUserDetails +import com.zegreatrob.minassert.assertIsEqualTo +import com.zegreatrob.minspy.Spy +import com.zegreatrob.minspy.SpyData +import com.zegreatrob.minspy.spyFunction +import com.zegreatrob.testmints.async.asyncSetup +import kotools.types.text.NotBlankString +import kotlin.test.Test +import kotlin.time.Clock + +class SavePartyCommandTest { + + @Test + fun willSavePlayersToRepository() = asyncSetup(object : ServerSavePartyCommandDispatcher { + val partyId = PartyId("woo") + val player = stubPlayer().copy( + badge = Badge.Default, + name = "Tim", + email = "tim@tim.meat", + callSignAdjective = "Spicy", + callSignNoun = "Meatball", + imageURL = "italian.jpg", + avatarType = null, + ) + + override val currentUser: UserDetails = stubUserDetails().copy(authorizedPartyIds = setOf(partyId)) + override val userId = currentUser.id + + override val playerSaveRepository = PlayerSaverSpy().apply { whenever(PartyElement(partyId, player), Unit) } + override val pinSaveRepository = PinSaverSpy() + override val playerRepository = EmptyPlayerRepository + override val partyRepository = ExistingPartyRepository() + override val userRepository = EmptyUserRepository + }) exercise { + perform(SavePartyCommand(partyId = partyId, players = listOf(player))) + } verify { result -> + result.assertIsEqualTo(VoidResult.Accepted) + playerSaveRepository.spyReceivedValues + .assertIsEqualTo(listOf(PartyElement(partyId, player))) + } + + private class PlayerSaverSpy : + PlayerSave, + Spy, Unit> by SpyData() { + override suspend fun save(partyPlayer: PartyElement) = spyFunction(partyPlayer) + } + + private class PinSaverSpy : PinSave { + override suspend fun save(partyPin: PartyElement) = Unit + } + + private object EmptyPlayerRepository : PlayerListGetByEmail { + override suspend fun getPlayersByEmail(emails: List) = emptyList>() + } + + private object EmptyUserRepository : UserRepository { + override suspend fun save(user: UserDetails) = Unit + override suspend fun getUser(): Record? = null + override suspend fun getUsersWithEmail(email: NotBlankString) = emptyList>() + } + + private class ExistingPartyRepository : PartyRepository { + override suspend fun getDetails(partyId: PartyId) = Record( + data = PartyDetails(id = partyId), + modifyingUserId = null, + isDeleted = false, + timestamp = Clock.System.now(), + ) + + override suspend fun getIntegration(partyId: PartyId): Record? = null + override suspend fun loadParties(partyIds: Set) = emptyList>() + override suspend fun save(party: PartyDetails) = Unit + override suspend fun save(integration: PartyElement) = Unit + override suspend fun deleteIt(partyId: PartyId) = false + } +} diff --git a/server/actionz/src/jsTest/kotlin/com/zegreatrob/coupling/server/action/player/SavePlayerCommandTest.kt b/server/actionz/src/jsTest/kotlin/com/zegreatrob/coupling/server/action/player/SavePlayerCommandTest.kt deleted file mode 100644 index 0975a9445a..0000000000 --- a/server/actionz/src/jsTest/kotlin/com/zegreatrob/coupling/server/action/player/SavePlayerCommandTest.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.zegreatrob.coupling.server.action.player - -import com.zegreatrob.coupling.action.VoidResult -import com.zegreatrob.coupling.action.player.SavePlayerCommand -import com.zegreatrob.coupling.model.party.PartyElement -import com.zegreatrob.coupling.model.party.PartyId -import com.zegreatrob.coupling.model.party.with -import com.zegreatrob.coupling.model.player.Badge -import com.zegreatrob.coupling.model.player.Player -import com.zegreatrob.coupling.repository.player.PlayerSave -import com.zegreatrob.coupling.stubmodel.stubPlayer -import com.zegreatrob.minassert.assertIsEqualTo -import com.zegreatrob.minspy.Spy -import com.zegreatrob.minspy.SpyData -import com.zegreatrob.minspy.spyFunction -import com.zegreatrob.testmints.async.asyncSetup -import kotlin.test.Test - -class SavePlayerCommandTest { - - @Test - fun willSaveToRepository() = asyncSetup(object : ServerSavePlayerCommandDispatcher { - override val currentPartyId = PartyId("woo") - val player = stubPlayer().copy( - badge = Badge.Default, - name = "Tim", - email = "tim@tim.meat", - callSignAdjective = "Spicy", - callSignNoun = "Meatball", - imageURL = "italian.jpg", - avatarType = null, - ) - override val playerRepository = PlayerSaverSpy().apply { whenever(currentPartyId.with(player), Unit) } - }) exercise { - perform(SavePlayerCommand(currentPartyId, player)) - } verify { result -> - result.assertIsEqualTo(VoidResult.Accepted) - playerRepository.spyReceivedValues - .assertIsEqualTo(listOf(currentPartyId.with(player))) - } - - class PlayerSaverSpy : - PlayerSave, - Spy, Unit> by SpyData() { - override suspend fun save(partyPlayer: PartyElement) = spyFunction(partyPlayer) - } -} diff --git a/server/src/jsMain/kotlin/com/zegreatrob/coupling/server/CommandDispatcher.kt b/server/src/jsMain/kotlin/com/zegreatrob/coupling/server/CommandDispatcher.kt index bc34e4932c..13c2ff85be 100644 --- a/server/src/jsMain/kotlin/com/zegreatrob/coupling/server/CommandDispatcher.kt +++ b/server/src/jsMain/kotlin/com/zegreatrob/coupling/server/CommandDispatcher.kt @@ -45,7 +45,6 @@ import com.zegreatrob.coupling.server.action.party.PartyIntegrationQuery import com.zegreatrob.coupling.server.action.party.ServerDeletePartyCommandDispatcher import com.zegreatrob.coupling.server.action.pin.PinsQuery import com.zegreatrob.coupling.server.action.pin.ServerDeletePinCommandDispatcher -import com.zegreatrob.coupling.server.action.pin.ServerSavePinCommandDispatcher import com.zegreatrob.coupling.server.action.player.PairAssignmentHistoryQuery import com.zegreatrob.coupling.server.action.player.PairListQuery import com.zegreatrob.coupling.server.action.player.PairQuery @@ -54,7 +53,6 @@ import com.zegreatrob.coupling.server.action.player.RecentTimesPairedQuery import com.zegreatrob.coupling.server.action.player.RetiredPlayersQuery import com.zegreatrob.coupling.server.action.player.ServerDeletePlayerCommandDispatcher import com.zegreatrob.coupling.server.action.player.ServerPairCountQueryDispatcher -import com.zegreatrob.coupling.server.action.player.ServerSavePlayerCommandDispatcher import com.zegreatrob.coupling.server.action.player.ServerSpinsSinceLastPairedQueryDispatcher import com.zegreatrob.coupling.server.action.player.SpinsUntilFullRotationQuery import com.zegreatrob.coupling.server.action.player.UserPlayersQuery @@ -137,6 +135,8 @@ class CommandDispatcher( TraceIdProvider, BroadcastAction.Dispatcher, ConnectPartyUserCommand.Dispatcher { + override val playerSaveRepository get() = playerRepository + override val pinSaveRepository get() = pinRepository override val cannon: ActionCannon = ActionCannon(this, LoggingActionPipe(traceId)) override val secretGenerator = object : JwtSecretHandler { override val secretIssuer: String = Config.publicUrl @@ -181,7 +181,6 @@ class CurrentPartyDispatcher( ServerSpinCommandDispatcher, ServerSaveSlackIntegrationCommandDispatcher, ServerCreateSecretCommandDispatcher, - ServerSavePlayerCommandDispatcher, ServerDeletePlayerCommandDispatcher, ServerDeleteSecretCommandDispatcher, RetiredPlayersQuery.Dispatcher, @@ -189,7 +188,6 @@ class CurrentPartyDispatcher( ServerDeletePairAssignmentsCommandDispatcher, ServerDeletePartyCommandDispatcher, ServerDeletePinCommandDispatcher, - ServerSavePinCommandDispatcher, CurrentConnectedUsersProvider, CannonProvider { override val userId: UserId get() = commandDispatcher.userId diff --git a/server/src/jsMain/kotlin/com/zegreatrob/coupling/server/entity/party/SavePartyResolver.kt b/server/src/jsMain/kotlin/com/zegreatrob/coupling/server/entity/party/SavePartyResolver.kt index 6cddd3e3dd..f389eb3ab6 100644 --- a/server/src/jsMain/kotlin/com/zegreatrob/coupling/server/entity/party/SavePartyResolver.kt +++ b/server/src/jsMain/kotlin/com/zegreatrob/coupling/server/entity/party/SavePartyResolver.kt @@ -3,7 +3,7 @@ package com.zegreatrob.coupling.server.entity.party import com.zegreatrob.coupling.action.party.SavePartyCommand import com.zegreatrob.coupling.action.party.perform import com.zegreatrob.coupling.json.GqlSavePartyInput -import com.zegreatrob.coupling.json.toModel +import com.zegreatrob.coupling.json.toCommand import com.zegreatrob.coupling.server.entity.boost.requiredInput import com.zegreatrob.coupling.server.graphql.DispatcherProviders.command import com.zegreatrob.coupling.server.graphql.dispatch @@ -11,7 +11,7 @@ import kotlinx.serialization.json.JsonNull val savePartyResolver = dispatch( dispatcherFunc = command(), - commandFunc = requiredInput { _: JsonNull, input: GqlSavePartyInput -> SavePartyCommand(input.toModel()) }, + commandFunc = requiredInput { _: JsonNull, input: GqlSavePartyInput -> input.toCommand() }, fireFunc = ::perform, toSerializable = { true }, ) diff --git a/server/src/jsMain/kotlin/com/zegreatrob/coupling/server/entity/pin/SavePinResolver.kt b/server/src/jsMain/kotlin/com/zegreatrob/coupling/server/entity/pin/SavePinResolver.kt index 724b3c5832..bd3cc1dfaf 100644 --- a/server/src/jsMain/kotlin/com/zegreatrob/coupling/server/entity/pin/SavePinResolver.kt +++ b/server/src/jsMain/kotlin/com/zegreatrob/coupling/server/entity/pin/SavePinResolver.kt @@ -1,7 +1,7 @@ package com.zegreatrob.coupling.server.entity.pin -import com.zegreatrob.coupling.action.pin.SavePinCommand -import com.zegreatrob.coupling.action.pin.perform +import com.zegreatrob.coupling.action.party.SavePartyCommand +import com.zegreatrob.coupling.action.party.perform import com.zegreatrob.coupling.json.GqlSavePinInput import com.zegreatrob.coupling.model.pin.Pin import com.zegreatrob.coupling.server.entity.boost.requiredInput @@ -21,7 +21,10 @@ val savePinResolver = dispatch( toSerializable = { true }, ) -private fun GqlSavePinInput.toCommand() = SavePinCommand(partyId, toPin()) +private fun GqlSavePinInput.toCommand() = SavePartyCommand( + partyId = partyId, + pins = listOf(toPin()), +) private fun GqlSavePinInput.toPin() = Pin( id = pinId, diff --git a/server/src/jsMain/kotlin/com/zegreatrob/coupling/server/entity/player/SavePlayerResolver.kt b/server/src/jsMain/kotlin/com/zegreatrob/coupling/server/entity/player/SavePlayerResolver.kt index f5fa6595ec..9f8ca7b469 100644 --- a/server/src/jsMain/kotlin/com/zegreatrob/coupling/server/entity/player/SavePlayerResolver.kt +++ b/server/src/jsMain/kotlin/com/zegreatrob/coupling/server/entity/player/SavePlayerResolver.kt @@ -1,7 +1,7 @@ package com.zegreatrob.coupling.server.entity.player -import com.zegreatrob.coupling.action.player.SavePlayerCommand -import com.zegreatrob.coupling.action.player.perform +import com.zegreatrob.coupling.action.party.SavePartyCommand +import com.zegreatrob.coupling.action.party.perform import com.zegreatrob.coupling.json.GqlSavePlayerInput import com.zegreatrob.coupling.json.toModel import com.zegreatrob.coupling.server.entity.boost.requiredInput @@ -21,4 +21,7 @@ val savePlayerResolver = dispatch( toSerializable = { true }, ) -private fun GqlSavePlayerInput.command() = SavePlayerCommand(partyId, toModel()) +private fun GqlSavePlayerInput.command() = SavePartyCommand( + partyId = partyId, + players = listOf(toModel()), +) diff --git a/server/src/jsMain/resources/schema.graphqls b/server/src/jsMain/resources/schema.graphqls index b011588e73..eb5e866ddc 100644 --- a/server/src/jsMain/resources/schema.graphqls +++ b/server/src/jsMain/resources/schema.graphqls @@ -270,6 +270,12 @@ input SaveSlackIntegrationInput { input SavePartyInput { partyId: PartyId! + party: SavePartyDetailsInput + players: SavePartyPlayersInput + pins: SavePartyPinsInput +} + +input SavePartyDetailsInput { name: String email: String pairingRule: Int @@ -281,6 +287,32 @@ input SavePartyInput { animationSpeed: Float } +input SavePartyPlayersInput { + items: [SavePartyPlayerInput!]! +} + +input SavePartyPinsInput { + items: [SavePartyPinInput!]! +} + +input SavePartyPlayerInput { + playerId: PlayerId! + name: String! + email: String! + badge: Badge! + callSignAdjective: String! + callSignNoun: String! + imageURL: String + avatarType: String + unvalidatedEmails: [String!]! +} + +input SavePartyPinInput { + pinId: PinId! + icon: String! + name: String! +} + input SavePlayerInput { partyId: PartyId! playerId: PlayerId! @@ -392,9 +424,9 @@ type Mutation { saveParty(input: SavePartyInput!): Boolean deleteParty(input: DeletePartyInput!): Boolean saveSlackIntegration(input: SaveSlackIntegrationInput!): Boolean - savePin(input: SavePinInput!): Boolean + savePin(input: SavePinInput!): Boolean @deprecated(reason: "Use saveParty with pins input.") deletePin(input: DeletePinInput!): Boolean - savePlayer(input: SavePlayerInput!): Boolean + savePlayer(input: SavePlayerInput!): Boolean @deprecated(reason: "Use saveParty with players input.") deletePlayer(input: DeletePlayerInput!): Boolean savePairAssignments(input: SavePairAssignmentsInput!): Boolean deletePairAssignments(input: DeletePairAssignmentsInput!): Boolean