CryptoService.java
package com.distasilucas.cryptobalancetracker.service;
import com.distasilucas.cryptobalancetracker.entity.ChangePercentages;
import com.distasilucas.cryptobalancetracker.entity.Crypto;
import com.distasilucas.cryptobalancetracker.entity.CryptoInfo;
import com.distasilucas.cryptobalancetracker.entity.LastKnownPrices;
import com.distasilucas.cryptobalancetracker.entity.UserCrypto;
import com.distasilucas.cryptobalancetracker.entity.view.NonUsedCryptosView;
import com.distasilucas.cryptobalancetracker.exception.CoingeckoCryptoNotFoundException;
import com.distasilucas.cryptobalancetracker.model.response.coingecko.CoingeckoCrypto;
import com.distasilucas.cryptobalancetracker.repository.CryptoRepository;
import com.distasilucas.cryptobalancetracker.repository.view.NonUsedCryptosViewRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import static com.distasilucas.cryptobalancetracker.constants.Constants.CRYPTOS_CRYPTOS_IDS_CACHE;
import static com.distasilucas.cryptobalancetracker.constants.Constants.CRYPTO_COINGECKO_CRYPTO_ID_CACHE;
import static com.distasilucas.cryptobalancetracker.constants.ExceptionConstants.COINGECKO_CRYPTO_NOT_FOUND;
import static com.distasilucas.cryptobalancetracker.model.CacheType.CRYPTOS_CACHES;
import static com.distasilucas.cryptobalancetracker.model.CacheType.PRICE_TARGETS_CACHES;
@Slf4j
@Service
@RequiredArgsConstructor
public class CryptoService {
private final CoingeckoService coingeckoService;
private final CryptoRepository cryptoRepository;
private final NonUsedCryptosViewRepository nonUsedCryptosViewRepository;
private final CacheService cacheService;
private final Clock clock;
@Cacheable(cacheNames = CRYPTO_COINGECKO_CRYPTO_ID_CACHE, key = "#coingeckoCryptoId")
public Crypto retrieveCryptoInfoById(String coingeckoCryptoId) {
log.info("Retrieving crypto info for id {}", coingeckoCryptoId);
return cryptoRepository.findById(coingeckoCryptoId)
.orElseGet(() -> {
var crypto = getCrypto(coingeckoCryptoId);
cryptoRepository.save(crypto);
cacheService.invalidate(CRYPTOS_CACHES);
log.info("Saved crypto {}", crypto);
return crypto;
});
}
public CoingeckoCrypto retrieveCoingeckoCryptoInfoByNameOrId(String cryptoNameOrId) {
log.info("Retrieving info for coingecko crypto {}", cryptoNameOrId);
return coingeckoService.retrieveAllCryptos()
.stream()
.filter(coingeckoCrypto -> coingeckoCrypto.name().equalsIgnoreCase(cryptoNameOrId) ||
coingeckoCrypto.id().equalsIgnoreCase(cryptoNameOrId))
.findFirst()
.orElseThrow(() -> new CoingeckoCryptoNotFoundException(COINGECKO_CRYPTO_NOT_FOUND.formatted(cryptoNameOrId)));
}
public void deleteCryptoIfNotUsed(String coingeckoCryptoId) {
var nonUsedCryptos = nonUsedCryptosViewRepository.findNonUsedCryptosByCoingeckoCryptoId(coingeckoCryptoId);
nonUsedCryptos.ifPresent(nonUsedCrypto -> {
cryptoRepository.deleteById(nonUsedCrypto.getId());
cacheService.invalidate(CRYPTOS_CACHES);
log.info("Deleted crypto [{}] - ({}) {} because it was not used", nonUsedCrypto.getId(), nonUsedCrypto.getTicker(), nonUsedCrypto.getName());
});
}
public void deleteCryptosIfNotUsed(List<String> coingeckoCryptoIds) {
var nonUsedCryptos = nonUsedCryptosViewRepository.findNonUsedCryptosByCoingeckoCryptoIds(coingeckoCryptoIds);
if (!nonUsedCryptos.isEmpty()) {
var nonUsedCryptosIds = nonUsedCryptos.stream().map(NonUsedCryptosView::getId).toList();
cryptoRepository.deleteAllById(nonUsedCryptosIds);
cacheService.invalidate(CRYPTOS_CACHES);
log.info("Deleted cryptos {} because they were not used", nonUsedCryptosIds);
}
}
public List<Crypto> findOldestNCryptosByLastPriceUpdate(LocalDateTime localDateTime, int limit) {
log.info("Retrieving {} cryptos with date filter {}", limit, localDateTime);
return cryptoRepository.findOldestNCryptosByLastPriceUpdate(localDateTime, limit);
}
public void updateCryptos(List<Crypto> cryptosToUpdate) {
cryptoRepository.saveAll(cryptosToUpdate);
var cryptosNames = cryptosToUpdate.stream()
.map(crypto -> crypto.getCryptoInfo().getName())
.toList();
log.info("Updated cryptos: {}", cryptosNames);
}
@Cacheable(cacheNames = CRYPTOS_CRYPTOS_IDS_CACHE, key = "#ids")
public List<Crypto> findAllByIds(Collection<String> ids) {
log.info("Retrieving cryptos with ids {}", ids);
return cryptoRepository.findAllByIdIn(ids);
}
private Crypto getCrypto(String coingeckoCryptoId) {
var coingeckoCryptoInfo = coingeckoService.retrieveCryptoInfo(coingeckoCryptoId);
var marketData = coingeckoCryptoInfo.marketData();
var cryptoInfo = new CryptoInfo(coingeckoCryptoInfo);
var lastKnownPrices = new LastKnownPrices(marketData);
var changePercentages = new ChangePercentages(marketData);
return new Crypto(coingeckoCryptoId, cryptoInfo, lastKnownPrices, changePercentages, LocalDateTime.now(clock));
}
}