PortalStipendServiceImpl.java

package cl.prgm.portaltrip.application.service;

import java.math.BigDecimal;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.List;
import java.util.UUID;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import cl.prgm.portaltrip.domain.exception.PortalStipendCooldownException;
import cl.prgm.portaltrip.domain.exception.ResourceNotFoundException;
import cl.prgm.portaltrip.domain.model.PortalStipendBeat;
import cl.prgm.portaltrip.domain.model.PortalStipendResult;
import cl.prgm.portaltrip.domain.model.UserAccount;
import cl.prgm.portaltrip.domain.service.PortalStipendCalculator;
import cl.prgm.portaltrip.infrastructure.persistence.PortalStipendEntity;
import cl.prgm.portaltrip.infrastructure.persistence.UserEntity;
import cl.prgm.portaltrip.infrastructure.persistence.repository.PortalStipendJpaRepository;
import cl.prgm.portaltrip.infrastructure.persistence.repository.UserJpaRepository;

@Service
public class PortalStipendServiceImpl implements PortalStipendService {

	private final UserJpaRepository userJpaRepository;
	private final PortalStipendJpaRepository portalStipendJpaRepository;
	private final PortalStipendCalculator calculator;

	public PortalStipendServiceImpl(
			UserJpaRepository userJpaRepository,
			PortalStipendJpaRepository portalStipendJpaRepository,
			PortalStipendCalculator calculator) {
		this.userJpaRepository = userJpaRepository;
		this.portalStipendJpaRepository = portalStipendJpaRepository;
		this.calculator = calculator;
	}

	@Override
	@Transactional
	public PortalStipendResult claim(UUID userId, double movement, long activeMs) {
		UserEntity userEntity = userJpaRepository.findByIdForUpdate(userId)
				.orElseThrow(() -> new ResourceNotFoundException("User", userId));
		OffsetDateTime now = OffsetDateTime.now();
		OffsetDateTime since = now.minus(Duration.ofMillis(PortalStipendCalculator.EARN_WINDOW_MS));
		List<PortalStipendBeat> recent = portalStipendJpaRepository
				.findByUser_IdAndCreatedAtGreaterThanEqualOrderByCreatedAtAsc(userId, since)
				.stream()
				.map(PortalStipendEntity::toBeat)
				.toList();
		if (calculator.coolingDown(recent, now)) {
			throw new PortalStipendCooldownException();
		}
		BigDecimal payout = calculator.sample(recent, now, movement, activeMs);
		portalStipendJpaRepository.save(PortalStipendEntity.create(userEntity, payout, now));
		UserAccount credited = userEntity.toDomain().credit(payout, now);
		userEntity.apply(credited);
		return new PortalStipendResult(payout, credited);
	}

}