GoalService.java

package com.distasilucas.cryptobalancetracker.service;

import com.distasilucas.cryptobalancetracker.entity.Goal;
import com.distasilucas.cryptobalancetracker.exception.DuplicatedGoalException;
import com.distasilucas.cryptobalancetracker.exception.GoalNotFoundException;
import com.distasilucas.cryptobalancetracker.model.request.goal.GoalRequest;
import com.distasilucas.cryptobalancetracker.repository.GoalRepository;
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 static com.distasilucas.cryptobalancetracker.constants.Constants.GOAL_CACHE;
import static com.distasilucas.cryptobalancetracker.constants.Constants.PAGE_GOALS_CACHE;
import static com.distasilucas.cryptobalancetracker.constants.ExceptionConstants.DUPLICATED_GOAL;
import static com.distasilucas.cryptobalancetracker.constants.ExceptionConstants.GOAL_ID_NOT_FOUND;
import static com.distasilucas.cryptobalancetracker.model.CacheType.GOALS_CACHES;

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

    private final GoalRepository goalRepository;
    private final CryptoService cryptoService;
    private final CacheService cacheService;
    private final GoalService self;

    @Cacheable(cacheNames = GOAL_CACHE, key = "#goalId")
    public Goal retrieveGoalById(String goalId) {
        log.info("Retrieving goal for id {}", goalId);

        return goalRepository.findById(goalId)
            .orElseThrow(() -> new GoalNotFoundException(GOAL_ID_NOT_FOUND.formatted(goalId)));
    }

    @Cacheable(cacheNames = PAGE_GOALS_CACHE, key = "#page")
    public Page<Goal> retrieveGoalsForPage(int page) {
        log.info("Retrieving pageGoals for page {}", page);
        var pageRequest = PageRequest.of(page, 10);

        return goalRepository.findAll(pageRequest);
    }

    public Goal saveGoal(GoalRequest goalRequest) {
        var coingeckoCrypto = cryptoService.retrieveCoingeckoCryptoInfoByNameOrId(goalRequest.cryptoName());
        var existingGoal = goalRepository.findByCoingeckoCryptoId(coingeckoCrypto.id());

        if (existingGoal.isPresent()) {
            throw new DuplicatedGoalException(DUPLICATED_GOAL.formatted(coingeckoCrypto.name()));
        }

        var crypto = cryptoService.retrieveCryptoInfoById(coingeckoCrypto.id());
        var goalEntity = goalRequest.toEntity(crypto);
        var goal = goalRepository.save(goalEntity);
        cacheService.invalidate(GOALS_CACHES);

        log.info("Saved goal {}", goal);

        return goal;
    }

    public Goal updateGoal(String goalId, GoalRequest goalRequest) {
        var goal = self.retrieveGoalById(goalId);
        var updatedGoal = goal.withNewGoalQuantity(goalRequest.goalQuantity());

        log.info("Updating goal. Before: {} | After: {}", goal, updatedGoal);
        var goalUpdated = goalRepository.save(updatedGoal);
        cacheService.invalidate(GOALS_CACHES);

        return goalUpdated;
    }

    public void deleteGoal(String goalId) {
        var goal = self.retrieveGoalById(goalId);

        goalRepository.deleteById(goalId);
        cryptoService.deleteCryptoIfNotUsed(goal.getCrypto().getId());
        cacheService.invalidate(GOALS_CACHES);

        log.info("Deleted goal {}", goal);
    }
}