UserCryptoService.java
package com.distasilucas.cryptobalancetracker.service;
import com.distasilucas.cryptobalancetracker.entity.UserCrypto;
import com.distasilucas.cryptobalancetracker.exception.DuplicatedCryptoPlatFormException;
import com.distasilucas.cryptobalancetracker.exception.UserCryptoNotFoundException;
import com.distasilucas.cryptobalancetracker.model.request.usercrypto.UserCryptoRequest;
import com.distasilucas.cryptobalancetracker.repository.UserCryptoRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import static com.distasilucas.cryptobalancetracker.constants.Constants.USER_CRYPTOS_CACHE;
import static com.distasilucas.cryptobalancetracker.constants.Constants.USER_CRYPTOS_COINGECKO_CRYPTO_ID_CACHE;
import static com.distasilucas.cryptobalancetracker.constants.Constants.USER_CRYPTOS_PAGE_CACHE;
import static com.distasilucas.cryptobalancetracker.constants.Constants.USER_CRYPTOS_PLATFORM_ID_CACHE;
import static com.distasilucas.cryptobalancetracker.constants.Constants.USER_CRYPTO_ID_CACHE;
import static com.distasilucas.cryptobalancetracker.constants.ExceptionConstants.DUPLICATED_CRYPTO_PLATFORM;
import static com.distasilucas.cryptobalancetracker.constants.ExceptionConstants.USER_CRYPTO_ID_NOT_FOUND;
import static com.distasilucas.cryptobalancetracker.model.CacheType.GOALS_CACHES;
import static com.distasilucas.cryptobalancetracker.model.CacheType.INSIGHTS_CACHES;
import static com.distasilucas.cryptobalancetracker.model.CacheType.USER_CRYPTOS_CACHES;
@Slf4j
@Service
@RequiredArgsConstructor
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class UserCryptoService {
private final UserCryptoRepository userCryptoRepository;
private final PlatformService platformService;
private final CryptoService cryptoService;
private final CacheService cacheService;
private final UserCryptoService self;
@Cacheable(cacheNames = USER_CRYPTO_ID_CACHE, key = "#userCryptoId")
public UserCrypto findUserCryptoById(String userCryptoId) {
log.info("Retrieving user crypto with id {}", userCryptoId);
return userCryptoRepository.findById(userCryptoId)
.orElseThrow(() -> new UserCryptoNotFoundException(USER_CRYPTO_ID_NOT_FOUND.formatted(userCryptoId)));
}
@Cacheable(cacheNames = USER_CRYPTOS_PAGE_CACHE, key = "#page")
public Page<UserCrypto> retrieveUserCryptosByPage(int page) {
log.info("Retrieving user cryptos for page {}", page);
var pageRequest = PageRequest.of(page, 10);
return userCryptoRepository.findAll(pageRequest);
}
public UserCrypto saveUserCrypto(UserCryptoRequest userCryptoRequest) {
var coingeckoCrypto = cryptoService.retrieveCoingeckoCryptoInfoByNameOrId(userCryptoRequest.cryptoName());
var platform = platformService.retrievePlatformById(userCryptoRequest.platformId());
userCryptoRepository.findByCoingeckoCryptoIdAndPlatformId(
coingeckoCrypto.id(),
userCryptoRequest.platformId()
).ifPresent(userCrypto -> {
String message = DUPLICATED_CRYPTO_PLATFORM.formatted(coingeckoCrypto.name(), platform.getName());
throw new DuplicatedCryptoPlatFormException(message);
});
var crypto = cryptoService.retrieveCryptoInfoById(coingeckoCrypto.id());
var userCrypto = new UserCrypto(userCryptoRequest.quantity(), platform, crypto);
userCryptoRepository.save(userCrypto);
log.info("Saved user crypto {}", userCrypto.toSavedUserCryptoString());
cacheService.invalidate(USER_CRYPTOS_CACHES, GOALS_CACHES, INSIGHTS_CACHES);
return userCrypto;
}
public UserCrypto updateUserCrypto(String userCryptoId, UserCryptoRequest userCryptoRequest) {
var userCrypto = self.findUserCryptoById(userCryptoId);
var platform = userCrypto.getPlatform();
var requestPlatform = platformService.retrievePlatformById(userCryptoRequest.platformId());
var coingeckoCrypto = cryptoService.retrieveCoingeckoCryptoInfoByNameOrId(userCrypto.getCrypto().getId());
if (didChangePlatform(requestPlatform.getId(), platform.getId())) {
userCryptoRepository.findByCoingeckoCryptoIdAndPlatformId(coingeckoCrypto.id(), userCryptoRequest.platformId())
.ifPresent(uc -> {
String message = DUPLICATED_CRYPTO_PLATFORM.formatted(coingeckoCrypto.name(), requestPlatform.getName());
throw new DuplicatedCryptoPlatFormException(message);
});
platform = platformService.retrievePlatformById(userCryptoRequest.platformId());
}
var updatedUserCrypto = userCrypto.toUpdatedUserCrypto(userCryptoRequest.quantity(), platform);
log.info("Updating user crypto. Before: {} | After: {}", userCrypto.toUpdatedUserCryptoString(), updatedUserCrypto.toUpdatedUserCryptoString());
userCryptoRepository.save(updatedUserCrypto);
cacheService.invalidate(USER_CRYPTOS_CACHES, GOALS_CACHES, INSIGHTS_CACHES);
return updatedUserCrypto;
}
public void deleteUserCrypto(String userCryptoId) {
var userCrypto = self.findUserCryptoById(userCryptoId);
userCryptoRepository.deleteById(userCryptoId);
cryptoService.deleteCryptoIfNotUsed(userCrypto.getCrypto().getId());
cacheService.invalidate(USER_CRYPTOS_CACHES, GOALS_CACHES, INSIGHTS_CACHES);
log.info("Deleted user crypto {} from platform {}", userCrypto.getCrypto().getCryptoInfo().getName(), userCrypto.getPlatform().getName());
}
public void deleteUserCryptos(List<UserCrypto> userCryptos) {
if (!userCryptos.isEmpty()) {
var coingeckoCryptoIds = userCryptos.stream().map(userCrypto -> userCrypto.getCrypto().getId()).toList();
userCryptoRepository.deleteAll(userCryptos);
cryptoService.deleteCryptosIfNotUsed(coingeckoCryptoIds);
cacheService.invalidate(USER_CRYPTOS_CACHES, GOALS_CACHES, INSIGHTS_CACHES);
log.info("Deleted user cryptos {}", coingeckoCryptoIds);
}
}
@Cacheable(cacheNames = USER_CRYPTOS_COINGECKO_CRYPTO_ID_CACHE, key = "#coingeckoCryptoId")
public List<UserCrypto> findAllByCoingeckoCryptoId(String coingeckoCryptoId) {
log.info("Retrieving all user cryptos matching coingecko crypto id {}", coingeckoCryptoId);
return userCryptoRepository.findAllByCoingeckoCryptoId(coingeckoCryptoId);
}
public Optional<UserCrypto> findByCoingeckoCryptoIdAndPlatformId(String cryptoId, String platformId) {
return userCryptoRepository.findByCoingeckoCryptoIdAndPlatformId(cryptoId, platformId);
}
public void saveOrUpdateAll(List<UserCrypto> userCryptos) {
userCryptoRepository.saveAll(userCryptos);
cacheService.invalidate(USER_CRYPTOS_CACHES, GOALS_CACHES, INSIGHTS_CACHES);
}
@Cacheable(cacheNames = USER_CRYPTOS_CACHE)
public List<UserCrypto> findAll() {
log.info("Retrieving all user cryptos");
return userCryptoRepository.findAll();
}
@Cacheable(cacheNames = USER_CRYPTOS_PLATFORM_ID_CACHE, key = "#platformId")
public List<UserCrypto> findAllByPlatformId(String platformId) {
log.info("Retrieving all user cryptos for platformId {}", platformId);
return userCryptoRepository.findAllByPlatformId(platformId);
}
private boolean didChangePlatform(String newPlatform, String originalPlatform) {
return !newPlatform.equalsIgnoreCase(originalPlatform);
}
}