updates for zmanagement
All checks were successful
Build Org Backend / build (push) Successful in 20s
All checks were successful
Build Org Backend / build (push) Successful in 20s
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
package org.zaine.app.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.zaine.app.dto.CalendarEventDTO;
|
||||
import org.zaine.app.dto.CreateCalendarEventDTO;
|
||||
import org.zaine.app.service.CalendarService;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/calendar")
|
||||
@Tag(name = "Calendar", description = "Calendar events exposed to clients")
|
||||
public class CalendarController {
|
||||
|
||||
private final CalendarService calendarService;
|
||||
|
||||
public CalendarController(CalendarService calendarService) {
|
||||
this.calendarService = calendarService;
|
||||
}
|
||||
|
||||
@Operation(summary = "Get today's calendar events")
|
||||
@GetMapping("/today")
|
||||
public List<CalendarEventDTO> getTodayEvents() {
|
||||
return calendarService.getTodayEvents();
|
||||
}
|
||||
|
||||
@Operation(summary = "Get calendar events for a month")
|
||||
@GetMapping("/month")
|
||||
public List<CalendarEventDTO> getMonthEvents(
|
||||
@RequestParam int year,
|
||||
@RequestParam int month) {
|
||||
return calendarService.getMonthEvents(year, month);
|
||||
}
|
||||
|
||||
@Operation(summary = "Get calendar events for a date range")
|
||||
@GetMapping("/range")
|
||||
public List<CalendarEventDTO> getRangeEvents(
|
||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate from,
|
||||
@RequestParam @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate to) {
|
||||
return calendarService.getEventsForRange(from, to);
|
||||
}
|
||||
|
||||
@Operation(summary = "Get one calendar event")
|
||||
@GetMapping("/events/{id}")
|
||||
public CalendarEventDTO getEvent(@PathVariable Long id) {
|
||||
return calendarService.getEvent(id);
|
||||
}
|
||||
|
||||
@Operation(summary = "Create a calendar event")
|
||||
@PostMapping("/events")
|
||||
public ResponseEntity<CalendarEventDTO> createEvent(@RequestBody CreateCalendarEventDTO dto) {
|
||||
return ResponseEntity.ok(calendarService.createEvent(dto));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package org.zaine.app.controller;
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.zaine.app.dto.EveningCheckinRequest;
|
||||
import org.zaine.app.dto.MorningCheckinRequest;
|
||||
import org.zaine.app.dto.TodayStatusDTO;
|
||||
import org.zaine.app.service.CheckinService;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/checkin")
|
||||
@Tag(name = "Check-in", description = "Daily morning and evening check-ins")
|
||||
public class CheckinController {
|
||||
|
||||
private final CheckinService checkinService;
|
||||
|
||||
public CheckinController(CheckinService checkinService) {
|
||||
this.checkinService = checkinService;
|
||||
}
|
||||
|
||||
@Operation(summary = "Get today's check-in status")
|
||||
@GetMapping("/today-status")
|
||||
public TodayStatusDTO getTodayStatus() {
|
||||
return checkinService.getTodayStatus();
|
||||
}
|
||||
|
||||
@Operation(summary = "Submit morning check-in")
|
||||
@PostMapping("/morning")
|
||||
public ResponseEntity<TodayStatusDTO> submitMorning(@RequestBody MorningCheckinRequest request) {
|
||||
return ResponseEntity.ok(checkinService.saveMorning(request));
|
||||
}
|
||||
|
||||
@Operation(summary = "Submit evening check-in")
|
||||
@PostMapping("/evening")
|
||||
public ResponseEntity<TodayStatusDTO> submitEvening(@RequestBody EveningCheckinRequest request) {
|
||||
return ResponseEntity.ok(checkinService.saveEvening(request));
|
||||
}
|
||||
}
|
||||
15
src/main/java/org/zaine/app/dto/CalendarEventDTO.java
Normal file
15
src/main/java/org/zaine/app/dto/CalendarEventDTO.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package org.zaine.app.dto;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public record CalendarEventDTO(
|
||||
Long id,
|
||||
String externalId,
|
||||
String title,
|
||||
String description,
|
||||
String location,
|
||||
OffsetDateTime startsAt,
|
||||
OffsetDateTime endsAt,
|
||||
boolean allDay,
|
||||
String source) {
|
||||
}
|
||||
14
src/main/java/org/zaine/app/dto/CreateCalendarEventDTO.java
Normal file
14
src/main/java/org/zaine/app/dto/CreateCalendarEventDTO.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package org.zaine.app.dto;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public record CreateCalendarEventDTO(
|
||||
String externalId,
|
||||
String title,
|
||||
String description,
|
||||
String location,
|
||||
OffsetDateTime startsAt,
|
||||
OffsetDateTime endsAt,
|
||||
boolean allDay,
|
||||
String source) {
|
||||
}
|
||||
11
src/main/java/org/zaine/app/dto/EveningCheckinRequest.java
Normal file
11
src/main/java/org/zaine/app/dto/EveningCheckinRequest.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package org.zaine.app.dto;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record EveningCheckinRequest(
|
||||
LocalDate date,
|
||||
Integer mood,
|
||||
Integer stressLevel,
|
||||
String reflection,
|
||||
String bestThingToday) {
|
||||
}
|
||||
12
src/main/java/org/zaine/app/dto/MorningCheckinRequest.java
Normal file
12
src/main/java/org/zaine/app/dto/MorningCheckinRequest.java
Normal file
@@ -0,0 +1,12 @@
|
||||
package org.zaine.app.dto;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record MorningCheckinRequest(
|
||||
LocalDate date,
|
||||
BigDecimal sleepHours,
|
||||
Integer energyLevel,
|
||||
Integer mood,
|
||||
String note) {
|
||||
}
|
||||
18
src/main/java/org/zaine/app/dto/TodayStatusDTO.java
Normal file
18
src/main/java/org/zaine/app/dto/TodayStatusDTO.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package org.zaine.app.dto;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public record TodayStatusDTO(
|
||||
LocalDate date,
|
||||
boolean morningDone,
|
||||
boolean eveningDone,
|
||||
BigDecimal sleepHours,
|
||||
Integer morningEnergyLevel,
|
||||
Integer morningMoodLevel,
|
||||
Integer eveningMoodLevel,
|
||||
Integer stressLevel,
|
||||
OffsetDateTime morningCompletedAt,
|
||||
OffsetDateTime eveningCompletedAt) {
|
||||
}
|
||||
83
src/main/java/org/zaine/app/model/CalendarEvent.java
Normal file
83
src/main/java/org/zaine/app/model/CalendarEvent.java
Normal file
@@ -0,0 +1,83 @@
|
||||
package org.zaine.app.model;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.PreUpdate;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "calendar_events")
|
||||
public class CalendarEvent {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "external_id", unique = true)
|
||||
private String externalId;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String title;
|
||||
|
||||
@Column
|
||||
private String description;
|
||||
|
||||
@Column
|
||||
private String location;
|
||||
|
||||
@Column(name = "starts_at", nullable = false)
|
||||
private OffsetDateTime startsAt;
|
||||
|
||||
@Column(name = "ends_at", nullable = false)
|
||||
private OffsetDateTime endsAt;
|
||||
|
||||
@Column(name = "all_day", nullable = false)
|
||||
private boolean allDay;
|
||||
|
||||
@Column(nullable = false)
|
||||
private String source = "manual";
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private OffsetDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
@PrePersist
|
||||
void onInsert() {
|
||||
OffsetDateTime now = OffsetDateTime.now();
|
||||
this.createdAt = now;
|
||||
this.updatedAt = now;
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
void onUpdate() {
|
||||
this.updatedAt = OffsetDateTime.now();
|
||||
}
|
||||
|
||||
public Long getId() { return id; }
|
||||
public String getExternalId() { return externalId; }
|
||||
public void setExternalId(String externalId) { this.externalId = externalId; }
|
||||
public String getTitle() { return title; }
|
||||
public void setTitle(String title) { this.title = title; }
|
||||
public String getDescription() { return description; }
|
||||
public void setDescription(String description) { this.description = description; }
|
||||
public String getLocation() { return location; }
|
||||
public void setLocation(String location) { this.location = location; }
|
||||
public OffsetDateTime getStartsAt() { return startsAt; }
|
||||
public void setStartsAt(OffsetDateTime startsAt) { this.startsAt = startsAt; }
|
||||
public OffsetDateTime getEndsAt() { return endsAt; }
|
||||
public void setEndsAt(OffsetDateTime endsAt) { this.endsAt = endsAt; }
|
||||
public boolean isAllDay() { return allDay; }
|
||||
public void setAllDay(boolean allDay) { this.allDay = allDay; }
|
||||
public String getSource() { return source; }
|
||||
public void setSource(String source) { this.source = source; }
|
||||
public OffsetDateTime getCreatedAt() { return createdAt; }
|
||||
public OffsetDateTime getUpdatedAt() { return updatedAt; }
|
||||
}
|
||||
100
src/main/java/org/zaine/app/model/DailyCheckin.java
Normal file
100
src/main/java/org/zaine/app/model/DailyCheckin.java
Normal file
@@ -0,0 +1,100 @@
|
||||
package org.zaine.app.model;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import jakarta.persistence.Id;
|
||||
import jakarta.persistence.PrePersist;
|
||||
import jakarta.persistence.PreUpdate;
|
||||
import jakarta.persistence.Table;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
@Entity
|
||||
@Table(name = "daily_checkins")
|
||||
public class DailyCheckin {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(nullable = false, unique = true)
|
||||
private LocalDate date;
|
||||
|
||||
@Column(name = "sleep_hours", precision = 4, scale = 2)
|
||||
private BigDecimal sleepHours;
|
||||
|
||||
@Column(name = "morning_energy_level")
|
||||
private Integer morningEnergyLevel;
|
||||
|
||||
@Column(name = "morning_mood_level")
|
||||
private Integer morningMoodLevel;
|
||||
|
||||
@Column(name = "morning_note")
|
||||
private String morningNote;
|
||||
|
||||
@Column(name = "morning_completed_at")
|
||||
private OffsetDateTime morningCompletedAt;
|
||||
|
||||
@Column(name = "evening_mood_level")
|
||||
private Integer eveningMoodLevel;
|
||||
|
||||
@Column(name = "stress_level")
|
||||
private Integer stressLevel;
|
||||
|
||||
@Column(name = "reflection", columnDefinition = "text")
|
||||
private String reflection;
|
||||
|
||||
@Column(name = "best_thing_today")
|
||||
private String bestThingToday;
|
||||
|
||||
@Column(name = "evening_completed_at")
|
||||
private OffsetDateTime eveningCompletedAt;
|
||||
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private OffsetDateTime createdAt;
|
||||
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
private OffsetDateTime updatedAt;
|
||||
|
||||
@PrePersist
|
||||
void onInsert() {
|
||||
OffsetDateTime now = OffsetDateTime.now();
|
||||
this.createdAt = now;
|
||||
this.updatedAt = now;
|
||||
}
|
||||
|
||||
@PreUpdate
|
||||
void onUpdate() {
|
||||
this.updatedAt = OffsetDateTime.now();
|
||||
}
|
||||
|
||||
public Long getId() { return id; }
|
||||
public LocalDate getDate() { return date; }
|
||||
public void setDate(LocalDate date) { this.date = date; }
|
||||
public BigDecimal getSleepHours() { return sleepHours; }
|
||||
public void setSleepHours(BigDecimal sleepHours) { this.sleepHours = sleepHours; }
|
||||
public Integer getMorningEnergyLevel() { return morningEnergyLevel; }
|
||||
public void setMorningEnergyLevel(Integer morningEnergyLevel) { this.morningEnergyLevel = morningEnergyLevel; }
|
||||
public Integer getMorningMoodLevel() { return morningMoodLevel; }
|
||||
public void setMorningMoodLevel(Integer morningMoodLevel) { this.morningMoodLevel = morningMoodLevel; }
|
||||
public String getMorningNote() { return morningNote; }
|
||||
public void setMorningNote(String morningNote) { this.morningNote = morningNote; }
|
||||
public OffsetDateTime getMorningCompletedAt() { return morningCompletedAt; }
|
||||
public void setMorningCompletedAt(OffsetDateTime morningCompletedAt) { this.morningCompletedAt = morningCompletedAt; }
|
||||
public Integer getEveningMoodLevel() { return eveningMoodLevel; }
|
||||
public void setEveningMoodLevel(Integer eveningMoodLevel) { this.eveningMoodLevel = eveningMoodLevel; }
|
||||
public Integer getStressLevel() { return stressLevel; }
|
||||
public void setStressLevel(Integer stressLevel) { this.stressLevel = stressLevel; }
|
||||
public String getReflection() { return reflection; }
|
||||
public void setReflection(String reflection) { this.reflection = reflection; }
|
||||
public String getBestThingToday() { return bestThingToday; }
|
||||
public void setBestThingToday(String bestThingToday) { this.bestThingToday = bestThingToday; }
|
||||
public OffsetDateTime getEveningCompletedAt() { return eveningCompletedAt; }
|
||||
public void setEveningCompletedAt(OffsetDateTime eveningCompletedAt) { this.eveningCompletedAt = eveningCompletedAt; }
|
||||
public OffsetDateTime getCreatedAt() { return createdAt; }
|
||||
public OffsetDateTime getUpdatedAt() { return updatedAt; }
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package org.zaine.app.repositories;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.zaine.app.model.CalendarEvent;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
import java.util.List;
|
||||
|
||||
public interface CalendarEventRepository extends JpaRepository<CalendarEvent, Long> {
|
||||
List<CalendarEvent> findByStartsAtLessThanAndEndsAtGreaterThanOrderByStartsAtAsc(
|
||||
OffsetDateTime rangeEnd,
|
||||
OffsetDateTime rangeStart);
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package org.zaine.app.repositories;
|
||||
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.zaine.app.model.DailyCheckin;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.util.Optional;
|
||||
|
||||
public interface DailyCheckinRepository extends JpaRepository<DailyCheckin, Long> {
|
||||
Optional<DailyCheckin> findByDate(LocalDate date);
|
||||
}
|
||||
99
src/main/java/org/zaine/app/service/CalendarService.java
Normal file
99
src/main/java/org/zaine/app/service/CalendarService.java
Normal file
@@ -0,0 +1,99 @@
|
||||
package org.zaine.app.service;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.zaine.app.dto.CalendarEventDTO;
|
||||
import org.zaine.app.dto.CreateCalendarEventDTO;
|
||||
import org.zaine.app.model.CalendarEvent;
|
||||
import org.zaine.app.repositories.CalendarEventRepository;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class CalendarService {
|
||||
|
||||
private final CalendarEventRepository calendarEventRepository;
|
||||
|
||||
public CalendarService(CalendarEventRepository calendarEventRepository) {
|
||||
this.calendarEventRepository = calendarEventRepository;
|
||||
}
|
||||
|
||||
public List<CalendarEventDTO> getTodayEvents() {
|
||||
LocalDate today = LocalDate.now();
|
||||
return getEventsForRange(today, today.plusDays(1));
|
||||
}
|
||||
|
||||
public List<CalendarEventDTO> getMonthEvents(int year, int month) {
|
||||
LocalDate start = LocalDate.of(year, month, 1);
|
||||
LocalDate end = start.with(TemporalAdjusters.firstDayOfNextMonth());
|
||||
return getEventsForRange(start, end);
|
||||
}
|
||||
|
||||
public List<CalendarEventDTO> getEventsForRange(LocalDate from, LocalDate to) {
|
||||
if (to.isBefore(from)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "to must be on or after from");
|
||||
}
|
||||
|
||||
ZoneId zone = ZoneId.systemDefault();
|
||||
OffsetDateTime rangeStart = from.atStartOfDay(zone).toOffsetDateTime();
|
||||
OffsetDateTime rangeEnd = to.atStartOfDay(zone).toOffsetDateTime();
|
||||
|
||||
return calendarEventRepository
|
||||
.findByStartsAtLessThanAndEndsAtGreaterThanOrderByStartsAtAsc(rangeEnd, rangeStart)
|
||||
.stream()
|
||||
.map(this::toDto)
|
||||
.toList();
|
||||
}
|
||||
|
||||
public CalendarEventDTO getEvent(Long id) {
|
||||
CalendarEvent event = calendarEventRepository.findById(id)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Calendar event not found"));
|
||||
return toDto(event);
|
||||
}
|
||||
|
||||
public CalendarEventDTO createEvent(CreateCalendarEventDTO dto) {
|
||||
if (dto.title() == null || dto.title().isBlank()) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "title is required");
|
||||
}
|
||||
if (dto.startsAt() == null || dto.endsAt() == null) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "startsAt and endsAt are required");
|
||||
}
|
||||
if (!dto.endsAt().isAfter(dto.startsAt())) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "endsAt must be after startsAt");
|
||||
}
|
||||
|
||||
CalendarEvent event = new CalendarEvent();
|
||||
event.setExternalId(blankToNull(dto.externalId()));
|
||||
event.setTitle(dto.title().trim());
|
||||
event.setDescription(blankToNull(dto.description()));
|
||||
event.setLocation(blankToNull(dto.location()));
|
||||
event.setStartsAt(dto.startsAt());
|
||||
event.setEndsAt(dto.endsAt());
|
||||
event.setAllDay(dto.allDay());
|
||||
event.setSource(dto.source() == null || dto.source().isBlank() ? "manual" : dto.source().trim());
|
||||
|
||||
return toDto(calendarEventRepository.save(event));
|
||||
}
|
||||
|
||||
private CalendarEventDTO toDto(CalendarEvent event) {
|
||||
return new CalendarEventDTO(
|
||||
event.getId(),
|
||||
event.getExternalId(),
|
||||
event.getTitle(),
|
||||
event.getDescription(),
|
||||
event.getLocation(),
|
||||
event.getStartsAt(),
|
||||
event.getEndsAt(),
|
||||
event.isAllDay(),
|
||||
event.getSource());
|
||||
}
|
||||
|
||||
private String blankToNull(String value) {
|
||||
return value == null || value.isBlank() ? null : value.trim();
|
||||
}
|
||||
}
|
||||
97
src/main/java/org/zaine/app/service/CheckinService.java
Normal file
97
src/main/java/org/zaine/app/service/CheckinService.java
Normal file
@@ -0,0 +1,97 @@
|
||||
package org.zaine.app.service;
|
||||
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
import org.zaine.app.dto.EveningCheckinRequest;
|
||||
import org.zaine.app.dto.MorningCheckinRequest;
|
||||
import org.zaine.app.dto.TodayStatusDTO;
|
||||
import org.zaine.app.model.DailyCheckin;
|
||||
import org.zaine.app.repositories.DailyCheckinRepository;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
@Service
|
||||
public class CheckinService {
|
||||
|
||||
private final DailyCheckinRepository dailyCheckinRepository;
|
||||
|
||||
public CheckinService(DailyCheckinRepository dailyCheckinRepository) {
|
||||
this.dailyCheckinRepository = dailyCheckinRepository;
|
||||
}
|
||||
|
||||
public TodayStatusDTO getTodayStatus() {
|
||||
return toStatus(getOrCreate(LocalDate.now()));
|
||||
}
|
||||
|
||||
public TodayStatusDTO saveMorning(MorningCheckinRequest request) {
|
||||
LocalDate date = request.date() == null ? LocalDate.now() : request.date();
|
||||
validateScore(request.energyLevel(), "energyLevel");
|
||||
validateScore(request.mood(), "mood");
|
||||
validateSleepHours(request.sleepHours());
|
||||
|
||||
DailyCheckin checkin = getOrCreate(date);
|
||||
checkin.setSleepHours(request.sleepHours());
|
||||
checkin.setMorningEnergyLevel(request.energyLevel());
|
||||
checkin.setMorningMoodLevel(request.mood());
|
||||
checkin.setMorningNote(blankToNull(request.note()));
|
||||
checkin.setMorningCompletedAt(OffsetDateTime.now());
|
||||
|
||||
return toStatus(dailyCheckinRepository.save(checkin));
|
||||
}
|
||||
|
||||
public TodayStatusDTO saveEvening(EveningCheckinRequest request) {
|
||||
LocalDate date = request.date() == null ? LocalDate.now() : request.date();
|
||||
validateScore(request.mood(), "mood");
|
||||
validateScore(request.stressLevel(), "stressLevel");
|
||||
|
||||
DailyCheckin checkin = getOrCreate(date);
|
||||
checkin.setEveningMoodLevel(request.mood());
|
||||
checkin.setStressLevel(request.stressLevel());
|
||||
checkin.setReflection(blankToNull(request.reflection()));
|
||||
checkin.setBestThingToday(blankToNull(request.bestThingToday()));
|
||||
checkin.setEveningCompletedAt(OffsetDateTime.now());
|
||||
|
||||
return toStatus(dailyCheckinRepository.save(checkin));
|
||||
}
|
||||
|
||||
private DailyCheckin getOrCreate(LocalDate date) {
|
||||
return dailyCheckinRepository.findByDate(date).orElseGet(() -> {
|
||||
DailyCheckin checkin = new DailyCheckin();
|
||||
checkin.setDate(date);
|
||||
return checkin;
|
||||
});
|
||||
}
|
||||
|
||||
private TodayStatusDTO toStatus(DailyCheckin checkin) {
|
||||
return new TodayStatusDTO(
|
||||
checkin.getDate(),
|
||||
checkin.getMorningCompletedAt() != null,
|
||||
checkin.getEveningCompletedAt() != null,
|
||||
checkin.getSleepHours(),
|
||||
checkin.getMorningEnergyLevel(),
|
||||
checkin.getMorningMoodLevel(),
|
||||
checkin.getEveningMoodLevel(),
|
||||
checkin.getStressLevel(),
|
||||
checkin.getMorningCompletedAt(),
|
||||
checkin.getEveningCompletedAt());
|
||||
}
|
||||
|
||||
private void validateScore(Integer value, String fieldName) {
|
||||
if (value == null || value < 1 || value > 10) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, fieldName + " must be between 1 and 10");
|
||||
}
|
||||
}
|
||||
|
||||
private void validateSleepHours(BigDecimal sleepHours) {
|
||||
if (sleepHours == null || sleepHours.compareTo(BigDecimal.ZERO) < 0 || sleepHours.compareTo(new BigDecimal("24")) > 0) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "sleepHours must be between 0 and 24");
|
||||
}
|
||||
}
|
||||
|
||||
private String blankToNull(String value) {
|
||||
return value == null || value.isBlank() ? null : value.trim();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
CREATE TABLE IF NOT EXISTS calendar_events (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
external_id VARCHAR(255) UNIQUE,
|
||||
title VARCHAR(255) NOT NULL,
|
||||
description TEXT,
|
||||
location VARCHAR(255),
|
||||
starts_at TIMESTAMPTZ NOT NULL,
|
||||
ends_at TIMESTAMPTZ NOT NULL,
|
||||
all_day BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
source VARCHAR(64) NOT NULL DEFAULT 'manual',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_calendar_events_range
|
||||
ON calendar_events (starts_at, ends_at);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS daily_checkins (
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
date DATE NOT NULL UNIQUE,
|
||||
sleep_hours NUMERIC(4, 2),
|
||||
morning_energy_level INTEGER,
|
||||
morning_mood_level INTEGER,
|
||||
morning_note VARCHAR(255),
|
||||
morning_completed_at TIMESTAMPTZ,
|
||||
evening_mood_level INTEGER,
|
||||
stress_level INTEGER,
|
||||
reflection TEXT,
|
||||
best_thing_today VARCHAR(255),
|
||||
evening_completed_at TIMESTAMPTZ,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
CONSTRAINT chk_daily_checkins_sleep_hours CHECK (sleep_hours IS NULL OR (sleep_hours >= 0 AND sleep_hours <= 24)),
|
||||
CONSTRAINT chk_daily_checkins_morning_energy CHECK (morning_energy_level IS NULL OR (morning_energy_level BETWEEN 1 AND 10)),
|
||||
CONSTRAINT chk_daily_checkins_morning_mood CHECK (morning_mood_level IS NULL OR (morning_mood_level BETWEEN 1 AND 10)),
|
||||
CONSTRAINT chk_daily_checkins_evening_mood CHECK (evening_mood_level IS NULL OR (evening_mood_level BETWEEN 1 AND 10)),
|
||||
CONSTRAINT chk_daily_checkins_stress CHECK (stress_level IS NULL OR (stress_level BETWEEN 1 AND 10))
|
||||
);
|
||||
Reference in New Issue
Block a user