UserCryptoService.kt
package com.distasilucas.cryptobalancetracker.service
import com.distasilucas.cryptobalancetracker.constants.DUPLICATED_CRYPTO_PLATFORM
import com.distasilucas.cryptobalancetracker.constants.USER_CRYPTOS_CACHE
import com.distasilucas.cryptobalancetracker.constants.USER_CRYPTOS_COINGECKO_CRYPTO_ID_CACHE
import com.distasilucas.cryptobalancetracker.constants.USER_CRYPTOS_PLATFORM_ID_CACHE
import com.distasilucas.cryptobalancetracker.constants.USER_CRYPTO_ID_CACHE
import com.distasilucas.cryptobalancetracker.constants.USER_CRYPTO_ID_NOT_FOUND
import com.distasilucas.cryptobalancetracker.constants.USER_CRYPTO_RESPONSE_USER_CRYPTO_ID_CACHE
import com.distasilucas.cryptobalancetracker.entity.UserCrypto
import com.distasilucas.cryptobalancetracker.model.request.crypto.UserCryptoRequest
import com.distasilucas.cryptobalancetracker.model.response.crypto.UserCryptoResponse
import com.distasilucas.cryptobalancetracker.repository.UserCryptoRepository
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.cache.annotation.Cacheable
import org.springframework.context.annotation.Scope
import org.springframework.context.annotation.ScopedProxyMode
import org.springframework.stereotype.Service
@Service
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
class UserCryptoService(
private val userCryptoRepository: UserCryptoRepository,
private val platformService: PlatformService,
private val cryptoService: CryptoService,
private val cacheService: CacheService,
private val _userCryptoService: UserCryptoService?
) {
private val logger = KotlinLogging.logger { }
@Cacheable(cacheNames = [USER_CRYPTO_ID_CACHE], key = "#userCryptoId")
fun findByUserCryptoId(userCryptoId: String): UserCrypto {
return userCryptoRepository.findById(userCryptoId)
.orElseThrow { throw UserCryptoNotFoundException(USER_CRYPTO_ID_NOT_FOUND.format(userCryptoId)) }
}
@Cacheable(cacheNames = [USER_CRYPTO_RESPONSE_USER_CRYPTO_ID_CACHE], key = "#userCryptoId")
fun retrieveUserCryptoResponseById(userCryptoId: String): UserCryptoResponse {
logger.info { "Retrieving user crypto with id $userCryptoId" }
val userCrypto = findByUserCryptoId(userCryptoId)
val crypto = cryptoService.retrieveCryptoInfoById(userCrypto.coingeckoCryptoId)
val platform = platformService.retrievePlatformById(userCrypto.platformId)
return userCrypto.toUserCryptoResponse(crypto.name, platform.name)
}
@Cacheable(cacheNames = [USER_CRYPTOS_COINGECKO_CRYPTO_ID_CACHE], key = "#coingeckoCryptoId")
fun findAllByCoingeckoCryptoId(coingeckoCryptoId: String): List<UserCrypto> {
logger.info { "Retrieving all user cryptos matching coingecko crypto id $coingeckoCryptoId" }
return userCryptoRepository.findAllByCoingeckoCryptoId(coingeckoCryptoId)
}
@Cacheable(cacheNames = [USER_CRYPTOS_PLATFORM_ID_CACHE], key = "#platformId")
fun findAllByPlatformId(platformId: String): List<UserCrypto> {
logger.info { "Retrieving all user cryptos for platformId $platformId" }
return userCryptoRepository.findAllByPlatformId(platformId)
}
@Cacheable(cacheNames = [USER_CRYPTOS_CACHE])
fun findAll(): List<UserCrypto> {
logger.info { "Retrieving all user cryptos" }
return userCryptoRepository.findAll()
}
fun findByCoingeckoCryptoIdAndPlatformId(cryptoId: String, platformId: String): UserCrypto? {
return userCryptoRepository.findByCoingeckoCryptoIdAndPlatformId(cryptoId, platformId)
}
fun saveUserCrypto(userCryptoRequest: UserCryptoRequest): UserCryptoResponse {
val coingeckoCrypto = cryptoService.retrieveCoingeckoCryptoInfoByNameOrId(userCryptoRequest.cryptoName!!)
val platform = platformService.retrievePlatformById(userCryptoRequest.platformId!!)
val existingUserCrypto: UserCrypto? =
userCryptoRepository.findByCoingeckoCryptoIdAndPlatformId(coingeckoCrypto.id, userCryptoRequest.platformId)
if (existingUserCrypto != null) {
throw DuplicatedCryptoPlatFormException(
DUPLICATED_CRYPTO_PLATFORM.format(coingeckoCrypto.name, platform.name)
)
}
val userCrypto = UserCrypto(
coingeckoCryptoId = coingeckoCrypto.id,
quantity = userCryptoRequest.quantity!!,
platformId = userCryptoRequest.platformId
)
cryptoService.saveCryptoIfNotExists(coingeckoCrypto.id)
userCryptoRepository.save(userCrypto)
logger.info { "Saved user crypto $userCrypto" }
cacheService.invalidate(CacheType.USER_CRYPTOS_CACHES, CacheType.GOALS_CACHES, CacheType.INSIGHTS_CACHES)
return userCrypto.toUserCryptoResponse(
cryptoName = coingeckoCrypto.name,
platformName = platform.name
)
}
fun updateUserCrypto(userCryptoId: String, userCryptoRequest: UserCryptoRequest): UserCryptoResponse {
val userCrypto = _userCryptoService!!.findByUserCryptoId(userCryptoId)
val requestPlatform = platformService.retrievePlatformById(userCryptoRequest.platformId!!)
val coingeckoCrypto = cryptoService.retrieveCoingeckoCryptoInfoByNameOrId(userCrypto.coingeckoCryptoId)
if (didChangePlatform(requestPlatform.id, userCrypto.platformId)) {
val existingUserCrypto: UserCrypto? =
userCryptoRepository.findByCoingeckoCryptoIdAndPlatformId(coingeckoCrypto.id, userCryptoRequest.platformId)
if (existingUserCrypto != null) {
throw DuplicatedCryptoPlatFormException(
DUPLICATED_CRYPTO_PLATFORM.format(coingeckoCrypto.name, requestPlatform.name)
)
}
}
val updatedUserCrypto = UserCrypto(
id = userCrypto.id,
coingeckoCryptoId = userCrypto.coingeckoCryptoId,
quantity = userCryptoRequest.quantity!!,
platformId = userCryptoRequest.platformId
)
userCryptoRepository.save(updatedUserCrypto)
cacheService.invalidate(CacheType.USER_CRYPTOS_CACHES, CacheType.GOALS_CACHES, CacheType.INSIGHTS_CACHES)
logger.info { "Updated user crypto. Before: $userCrypto | After: $updatedUserCrypto" }
return updatedUserCrypto.toUserCryptoResponse(
cryptoName = coingeckoCrypto.name,
platformName = requestPlatform.name
)
}
fun deleteUserCrypto(userCryptoId: String) {
val userCrypto = _userCryptoService!!.findByUserCryptoId(userCryptoId)
userCryptoRepository.deleteById(userCryptoId)
cacheService.invalidate(CacheType.USER_CRYPTOS_CACHES, CacheType.GOALS_CACHES, CacheType.INSIGHTS_CACHES)
cryptoService.deleteCryptoIfNotUsed(userCrypto.coingeckoCryptoId)
logger.info { "Deleted user crypto $userCryptoId" }
}
fun deleteUserCryptos(userCryptos: List<UserCrypto>) {
if (userCryptos.isNotEmpty()) {
val coingeckoCryptoIds = userCryptos.map { it.coingeckoCryptoId }
userCryptoRepository.deleteAll(userCryptos)
cryptoService.deleteCryptosIfNotUsed(coingeckoCryptoIds)
cacheService.invalidate(CacheType.USER_CRYPTOS_CACHES, CacheType.GOALS_CACHES, CacheType.INSIGHTS_CACHES)
logger.info { "Deleted user cryptos $coingeckoCryptoIds" }
}
}
fun saveOrUpdateAll(userCryptos: List<UserCrypto>) {
userCryptoRepository.saveAll(userCryptos)
cacheService.invalidate(CacheType.USER_CRYPTOS_CACHES, CacheType.GOALS_CACHES, CacheType.INSIGHTS_CACHES)
}
private fun didChangePlatform(newPlatform: String, originalPlatform: String) = newPlatform != originalPlatform
}
class UserCryptoNotFoundException(message: String) : RuntimeException(message)
class DuplicatedCryptoPlatFormException(message: String) : RuntimeException(message)