SleepService.java

package com.seebie.server.service;

import com.seebie.server.dto.*;
import com.seebie.server.mapper.dtotoentity.UnsavedSleepListMapper;
import com.seebie.server.mapper.entitytodto.SleepMapper;
import com.seebie.server.repository.SleepRepository;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PagedModel;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;

import java.time.LocalDate;
import java.util.List;
import java.util.UUID;

import static com.seebie.server.dto.DateRange.atEndOfDay;
import static com.seebie.server.dto.DateRange.atStartOfDay;

@Service
public class SleepService {

    private final SleepRepository sleepRepository;
    private final UnsavedSleepListMapper entityMapper;

    private final SleepMapper sleepMapper = new SleepMapper();


    public SleepService(SleepRepository sleepRepository, UnsavedSleepListMapper entityMapper) {
        this.sleepRepository = sleepRepository;
        this.entityMapper = entityMapper;
    }

    @Transactional(readOnly = true)
    public PagedModel<SleepDetails> listSleepData(UUID publicId, Pageable page) {
        return new PagedModel<>(sleepRepository.loadSummaries(publicId, page));
    }

    @Transactional
    public SleepDetails saveNew(UUID publicId, SleepData dto) {

        // The computed value for timeAsleep isn't calculated until the transaction is closed
        // so the entity does not have the correct value here.
        var entity = sleepRepository.save(entityMapper.toUnsavedEntity(publicId, dto));
        return sleepMapper.apply(entity);
    }

    @Transactional
    public void remove(UUID publicId, Long sleepId) {

        var entity = sleepRepository.findBy(publicId, sleepId)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Sleep session not found"));

        sleepRepository.delete(entity);
    }

    @Transactional
    public void update(UUID publicId, Long sleepId, SleepData dto) {

        var entity = sleepRepository.findBy(publicId, sleepId)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Sleep session not found"));

        entity.setSleepData(dto.minutesAwake(), dto.notes(), dto.startTime(), dto.stopTime(), dto.minutesAsleep(), dto.zoneId());
    }

    @Transactional(readOnly = true)
    public SleepDetails retrieve(UUID publicId, Long sleepId) {

        return sleepRepository.findBy(publicId, sleepId)
                .map(sleepMapper)
                .orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Sleep session not found for user " + publicId + " and sleep id " + sleepId));
    }

    @Transactional(readOnly = true)
    public RecordCount countSleepRecords(UUID publicId) {
        return new RecordCount(sleepRepository.countByUser_PublicId(publicId));
    }

    @Transactional(readOnly = true)
    public List<SleepDataPoint> listChartData(UUID publicId, LocalDate from, LocalDate to) {
         return sleepRepository.loadChartData(publicId, atStartOfDay(from), atEndOfDay(to));
    }

    @Transactional(readOnly = true)
    public List<List<Long>> listSleepAmounts(UUID publicId, List<DateRange> filters) {

        return filters.stream()
                .map(dateRange -> sleepRepository.loadDurations(publicId,
                        atStartOfDay(dateRange.from()),
                        atEndOfDay(dateRange.to())))
                .toList();
    }

}