package com.danielbohry.stocks.service.stock; import com.danielbohry.stocks.domain.StockHistory; import com.danielbohry.stocks.exception.BadRequestException; import com.danielbohry.stocks.repository.stock.StockHistoryRepository; import lombok.AllArgsConstructor; import net.javacrumbs.shedlock.spring.annotation.SchedulerLock; import org.springframework.cache.annotation.Cacheable; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import java.time.Instant; import java.time.LocalDate; import java.time.ZoneOffset; import java.util.List; import static java.time.Instant.now; import static java.time.ZoneOffset.UTC; import static java.time.temporal.ChronoUnit.DAYS; @Service @AllArgsConstructor public class StockHistoryService { private final StockService service; private final StockHistoryRepository repository; public static final String ONCE_PER_DAY_AT_2AM = "0 0 2 * * *"; @Scheduled(cron = ONCE_PER_DAY_AT_2AM) @SchedulerLock(name = "StockQuoteService_saveQuotes", lockAtLeastFor = "PT5M", lockAtMostFor = "PT15M") public void saveQuotes() { LocalDate today = LocalDate.now(UTC); Instant startOfDay = today.atStartOfDay(UTC).toInstant(); Instant endOfDay = today.plusDays(1).atStartOfDay(UTC).toInstant(); List stocksToSave = service.getAll().stream() .filter(stock -> !repository.existsByCodeAndCreatedAtBetween(stock.getCode(), startOfDay, endOfDay)) .map(stock -> new StockHistory( stock.getCode(), stock.getCurrency(), stock.getPrice(), now() )) .toList(); repository.saveAll(stocksToSave); } @Cacheable(value = "stockHistory", key = "#code + '-' + #range") public List get(String code, String range) { Instant end = now(); Instant start = switch (range) { case "5d" -> end.minus(5, DAYS); case "30d" -> end.minus(30, DAYS); case "6m" -> end.minus(180, DAYS); case "1y" -> end.minus(365, DAYS); default -> throw new BadRequestException("Unsupported range: " + range); }; return repository.findByCodeAndCreatedAtBetween(code, start, end); } }