PriceTargetService.java

package com.distasilucas.cryptobalancetracker.service;

import com.distasilucas.cryptobalancetracker.entity.PriceTarget;
import com.distasilucas.cryptobalancetracker.exception.DuplicatedPriceTargetException;
import com.distasilucas.cryptobalancetracker.exception.PriceTargetNotFoundException;
import com.distasilucas.cryptobalancetracker.model.request.pricetarget.PriceTargetRequest;
import com.distasilucas.cryptobalancetracker.repository.PriceTargetRepository;
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.math.BigDecimal;

import static com.distasilucas.cryptobalancetracker.constants.Constants.PRICE_TARGET_ID_CACHE;
import static com.distasilucas.cryptobalancetracker.constants.Constants.PRICE_TARGET_PAGE_CACHE;
import static com.distasilucas.cryptobalancetracker.model.CacheType.PRICE_TARGETS_CACHES;

@Slf4j
@Service
@RequiredArgsConstructor
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class PriceTargetService {

    private final PriceTargetRepository priceTargetRepository;
    private final CryptoService cryptoService;
    private final CacheService cacheService;
    private final PriceTargetService self;

    @Cacheable(cacheNames = PRICE_TARGET_ID_CACHE, key = "#priceTargetId")
    public PriceTarget retrievePriceTargetById(String priceTargetId) {
        log.info("Retrieving price target for id {}", priceTargetId);

        return priceTargetRepository.findById(priceTargetId)
            .orElseThrow(() -> new PriceTargetNotFoundException(String.format("Price target with id %s not found", priceTargetId)));
    }

    @Cacheable(cacheNames = PRICE_TARGET_PAGE_CACHE, key = "#page")
    public Page<PriceTarget> retrievePriceTargetsByPage(int page) {
        log.info("Retrieving price targets for page {}", page);
        var pageRequest = PageRequest.of(page, 10);

        return priceTargetRepository.findAll(pageRequest);
    }

    public PriceTarget savePriceTarget(PriceTargetRequest priceTargetRequest) {
        log.info("Saving price target {}", priceTargetRequest);

        var coingeckoCrypto = cryptoService.retrieveCoingeckoCryptoInfoByNameOrId(priceTargetRequest.cryptoNameOrId());
        validatePriceTargetIsNotDuplicated(coingeckoCrypto.id(), priceTargetRequest.priceTarget());
        var crypto = cryptoService.retrieveCryptoInfoById(coingeckoCrypto.id());
        var priceTargetEntity = priceTargetRequest.toEntity(crypto);

        var priceTarget = priceTargetRepository.save(priceTargetEntity);
        cacheService.invalidate(PRICE_TARGETS_CACHES);

        return priceTarget;
    }

    public PriceTarget updatePriceTarget(String priceTargetId, PriceTargetRequest priceTargetRequest) {
        log.info("Updating price target for id {}. New value: {}", priceTargetId, priceTargetRequest);

        var priceTarget = self.retrievePriceTargetById(priceTargetId);
        priceTarget.setTarget(priceTargetRequest.priceTarget());

        var coingeckoCryptoId = priceTarget.getCrypto().getId();
        validatePriceTargetIsNotDuplicated(coingeckoCryptoId, priceTargetRequest.priceTarget());

        var updatedPriceTarget = priceTargetRepository.save(priceTarget);
        cacheService.invalidate(PRICE_TARGETS_CACHES);

        return updatedPriceTarget;
    }

    public void deletePriceTarget(String priceTargetId) {
        log.info("Deleting price target for id {}", priceTargetId);
        var priceTarget = self.retrievePriceTargetById(priceTargetId);

        priceTargetRepository.delete(priceTarget);
        cryptoService.deleteCryptoIfNotUsed(priceTarget.getCrypto().getId());
        cacheService.invalidate(PRICE_TARGETS_CACHES);
    }

    private void validatePriceTargetIsNotDuplicated(String coingeckoCryptoId, BigDecimal target) {
        var message = String.format("You already have a price target for %s at that price", coingeckoCryptoId);
        var optionalPriceTarget = priceTargetRepository.findByCoingeckoCryptoIdAndTarget(coingeckoCryptoId, target);

        if (optionalPriceTarget.isPresent()) {
            throw new DuplicatedPriceTargetException(message);
        }
    }
}