DefaultPlayerInventoryManagementService.kt

package io.github.lishangbu.avalon.game.player

import io.github.lishangbu.avalon.dataset.api.service.ItemReader
import io.github.lishangbu.avalon.game.entity.PlayerInventoryItem
import io.github.lishangbu.avalon.game.repository.PlayerInventoryItemRepository
import io.github.lishangbu.avalon.game.repository.PlayerRepository
import org.babyfish.jimmer.sql.ast.mutation.SaveMode
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import java.time.Instant

@Service
class DefaultPlayerInventoryManagementService(
    private val playerRepository: PlayerRepository,
    private val playerInventoryItemRepository: PlayerInventoryItemRepository,
    private val itemReader: ItemReader,
) : PlayerInventoryManagementService {
    override fun listByPlayerId(playerId: String): List<PlayerInventoryItemView> {
        val parsedPlayerId = playerId.toLongOrNull() ?: error("playerId must be a valid long value.")
        val inventoryItems =
            playerInventoryItemRepository
                .findAll()
                .filter { item -> item.playerId == parsedPlayerId }
        if (inventoryItems.isEmpty()) {
            return emptyList()
        }
        val itemsById = itemReader.findItemsByIds(inventoryItems.map { inventoryItem -> inventoryItem.itemId }.toSet())
        return inventoryItems
            .sortedBy { inventoryItem -> inventoryItem.itemId }
            .map { inventoryItem ->
                val item =
                    requireNotNull(itemsById[inventoryItem.itemId]) {
                        "Item '${inventoryItem.itemId}' was not found."
                    }
                PlayerInventoryItemView(
                    playerId = inventoryItem.playerId.toString(),
                    itemId = item.id.toString(),
                    itemInternalName = item.internalName,
                    itemName = item.name,
                    quantity = inventoryItem.quantity,
                )
            }
    }

    @Transactional(rollbackFor = [Exception::class])
    override fun grant(command: GrantInventoryItemCommand): PlayerInventoryItemView {
        require(command.quantity > 0) { "Grant quantity must be greater than 0." }
        val playerId = command.playerId.toLongOrNull() ?: error("playerId must be a valid long value.")
        requireNotNull(playerRepository.findNullable(playerId)) {
            "Player '$playerId' was not found."
        }
        val itemData = itemReader.findItemByInternalName(command.itemInternalName) ?: error("Item '${command.itemInternalName}' was not found.")
        val itemId = itemData.id
        val existing =
            playerInventoryItemRepository
                .findAll()
                .firstOrNull { inventoryItem -> inventoryItem.playerId == playerId && inventoryItem.itemId == itemId }
        val saved =
            if (existing == null) {
                playerInventoryItemRepository.save(
                    PlayerInventoryItem {
                        this.playerId = playerId
                        this.itemId = itemId
                        quantity = command.quantity
                        updatedAt = Instant.now()
                    },
                    SaveMode.INSERT_ONLY,
                )
            } else {
                playerInventoryItemRepository.save(
                    PlayerInventoryItem(existing) {
                        quantity = existing.quantity + command.quantity
                        updatedAt = Instant.now()
                    },
                )
            }

        return PlayerInventoryItemView(
            playerId = saved.playerId.toString(),
            itemId = itemId.toString(),
            itemInternalName = itemData.internalName,
            itemName = itemData.name,
            quantity = saved.quantity,
        )
    }
}