SleepController.java
package com.seebie.server.controller;
import com.seebie.server.dto.*;
import com.seebie.server.service.HistogramCalculator;
import com.seebie.server.service.SleepService;
import com.seebie.server.validation.ValidDurations;
import jakarta.validation.Valid;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.web.PageableDefault;
import org.springframework.data.web.PagedModel;
import org.springframework.http.MediaType;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
import java.util.UUID;
// if we use server.servlet.context-path=/api, static content and API all come from the same base
// so we can use that for api-only requests only if the UI is served separately
@RestController
@RequestMapping(path="/api", produces = MediaType.APPLICATION_JSON_VALUE)
public class SleepController {
    private static Logger LOG = LoggerFactory.getLogger(SleepController.class);
    private final HistogramCalculator histogramCalculator = new HistogramCalculator();
    private final SleepService sleepService;
    // if there's only one constructor, can omit Autowired and Inject
    public SleepController(SleepService sleepService) {
        this.sleepService = sleepService;
    }
    @PreAuthorize("hasRole('ROLE_ADMIN') || #publicId == authentication.principal.publicId")
    @PostMapping("/user/{publicId}/sleep")
    public void saveSleepSession(@Valid @ValidDurations @RequestBody SleepData dto, @PathVariable UUID publicId) {
        sleepService.saveNew(publicId, dto);
    }
    @PreAuthorize("hasRole('ROLE_ADMIN') || #publicId == authentication.principal.publicId")
    @GetMapping("/user/{publicId}/sleep")
    public PagedModel<SleepDetails> getSleepList(@PathVariable UUID publicId, @PageableDefault(page = 0, size = 10, sort = {"stopTime"}, direction=Sort.Direction.DESC) Pageable page) {
        return sleepService.listSleepData(publicId, page);
    }
    @PreAuthorize("hasRole('ROLE_ADMIN') || #publicId == authentication.principal.publicId")
    @GetMapping("/user/{publicId}/sleep/chart")
    public List<SleepDataPoint> getChartData(@PathVariable UUID publicId, @RequestParam LocalDate from, @RequestParam LocalDate to) {
        if(to.isBefore(from)) {
            throw new IllegalArgumentException("Request parameter \"from\" must be before \"to\"");
        }
        LOG.info("Requesting chart data with range " + from + " " + to);
        return sleepService.listChartData(publicId, from, to);
    }
    /**
     * This does not make any changes to the server, but we're using POST instead of GET so that
     * the request body can be used in a standard way. You shouldn't send a request body with a GET request
     * or at the very least it's somewhat controversial.
     *
     * @param publicId
     * @param request
     * @return
     */
    @PreAuthorize("hasRole('ROLE_ADMIN') || #publicId == authentication.principal.publicId")
    @PostMapping("/user/{publicId}/sleep/histogram")
    public StackedHistograms getHistogramData(@Valid @RequestBody HistogramRequest request, @PathVariable UUID publicId) {
        var listsSleepAmounts = sleepService.listSleepAmounts(publicId, request.filters().dataFilters());
        return histogramCalculator.buildNormalizedHistogram(request.binSize(), listsSleepAmounts);
    }
    @PreAuthorize("hasRole('ROLE_ADMIN') || #publicId == authentication.principal.publicId")
    @GetMapping("/user/{publicId}/sleep/count")
    public RecordCount getSleepRecordCount(@PathVariable UUID publicId) {
        return sleepService.countSleepRecords(publicId);
    }
    @PreAuthorize("hasRole('ROLE_ADMIN') || #publicId == authentication.principal.publicId")
    @GetMapping( "/user/{publicId}/sleep/{sleepId}")
    public SleepDetails getSleepSession(@PathVariable UUID publicId, @PathVariable Long sleepId) {
        return sleepService.retrieve(publicId, sleepId);
    }
    @PreAuthorize("hasRole('ROLE_ADMIN') || #publicId == authentication.principal.publicId")
    @PutMapping("/user/{publicId}/sleep/{sleepId}")
    public void updateSleepSession(@Valid @ValidDurations @RequestBody SleepData sleepData, @PathVariable UUID publicId, @PathVariable Long sleepId) {
        sleepService.update(publicId, sleepId, sleepData);
    }
    @PreAuthorize("hasRole('ROLE_ADMIN') || #publicId == authentication.principal.publicId")
    @DeleteMapping("/user/{publicId}/sleep/{sleepId}")
    public void delete(@PathVariable UUID publicId, @PathVariable Long sleepId) {
        sleepService.remove(publicId, sleepId);
    }
}