12_3_add_calories_meals · JavaWebinar/topjava@0ad809f · GitHub
Skip to content

Commit 0ad809f

Browse files
committed
12_3_add_calories_meals
1 parent b4d766b commit 0ad809f

21 files changed

Lines changed: 656 additions & 24 deletions
Lines changed: 11 additions & 0 deletions
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package ru.javaops.topjava.user.model;
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnore;
4+
import io.swagger.v3.oas.annotations.media.Schema;
5+
import jakarta.persistence.*;
6+
import jakarta.validation.constraints.NotBlank;
7+
import jakarta.validation.constraints.NotNull;
8+
import jakarta.validation.constraints.Size;
9+
import lombok.*;
10+
import org.hibernate.validator.constraints.Range;
11+
import ru.javaops.topjava.common.model.BaseEntity;
12+
import ru.javaops.topjava.common.validation.NoHtml;
13+
14+
import java.time.LocalDate;
15+
import java.time.LocalDateTime;
16+
import java.time.LocalTime;
17+
18+
@Entity
19+
@Table(name = "meal", uniqueConstraints = {@UniqueConstraint(columnNames = {"user_id", "date_time"}, name = "meal_unique_user_datetime_idx")})
20+
@Getter
21+
@Setter
22+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
23+
@ToString(callSuper = true, exclude = {"user"})
24+
public class Meal extends BaseEntity {
25+
26+
@Column(name = "date_time", nullable = false)
27+
@NotNull
28+
private LocalDateTime dateTime;
29+
30+
@Column(name = "description", nullable = false)
31+
@NotBlank
32+
@Size(min = 2, max = 120)
33+
@NoHtml
34+
private String description;
35+
36+
@Column(name = "calories", nullable = false)
37+
@NotNull
38+
@Range(min = 10, max = 5000)
39+
private Integer calories;
40+
41+
@ManyToOne(fetch = FetchType.LAZY)
42+
@JoinColumn(name = "user_id", nullable = false)
43+
@JsonIgnore
44+
private User user;
45+
46+
public Meal(Integer id, LocalDateTime dateTime, String description, int calories) {
47+
super(id);
48+
this.dateTime = dateTime;
49+
this.description = description;
50+
this.calories = calories;
51+
}
52+
53+
@Schema(hidden = true)
54+
public LocalDate getDate() {
55+
return dateTime.toLocalDate();
56+
}
57+
58+
@Schema(hidden = true)
59+
public LocalTime getTime() {
60+
return dateTime.toLocalTime();
61+
}
62+
}

src/main/java/ru/javaops/topjava/user/model/User.java

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ru.javaops.topjava.user.model;
22

33
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import io.swagger.v3.oas.annotations.media.Schema;
45
import jakarta.persistence.*;
56
import jakarta.validation.constraints.Email;
67
import jakarta.validation.constraints.NotBlank;
@@ -11,12 +12,17 @@
1112
import lombok.NoArgsConstructor;
1213
import lombok.Setter;
1314
import org.jspecify.annotations.NonNull;
15+
import org.hibernate.annotations.OnDelete;
16+
import org.hibernate.annotations.OnDeleteAction;
17+
import org.hibernate.validator.constraints.Range;
1418
import ru.javaops.topjava.common.HasIdAndEmail;
1519
import ru.javaops.topjava.common.model.NamedEntity;
1620
import ru.javaops.topjava.common.validation.NoHtml;
1721

1822
import java.util.*;
1923

24+
import static ru.javaops.topjava.user.util.UsersUtil.DEFAULT_CALORIES_PER_DAY;
25+
2026
@Entity
2127
@Table(name = "users")
2228
@Getter
@@ -55,18 +61,30 @@ public class User extends NamedEntity implements HasIdAndEmail {
5561
@ElementCollection(fetch = FetchType.EAGER)
5662
private Set<Role> roles = EnumSet.noneOf(Role.class);
5763

64+
@Column(name = "calories_per_day", nullable = false, columnDefinition = "int default 2000")
65+
@Range(min = 10, max = 10000)
66+
private int caloriesPerDay = DEFAULT_CALORIES_PER_DAY;
67+
68+
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user")//, cascade = CascadeType.REMOVE, orphanRemoval = true)
69+
@OrderBy("dateTime DESC")
70+
@OnDelete(action = OnDeleteAction.CASCADE) //https://stackoverflow.com/a/44988100/548473
71+
@Schema(hidden = true)
72+
private List<Meal> meals;
73+
5874
public User(User u) {
59-
this(u.id, u.name, u.email, u.password, u.enabled, u.registered, u.roles);
75+
this(u.id, u.name, u.email, u.password, u.caloriesPerDay, u.enabled, u.registered, u.roles);
76+
this.meals = List.copyOf(u.meals);
6077
}
6178

62-
public User(Integer id, String name, String email, String password, Role... roles) {
63-
this(id, name, email, password, true, new Date(), Arrays.asList(roles));
79+
public User(Integer id, String name, String email, String password, int caloriesPerDay, Role... roles) {
80+
this(id, name, email, password, caloriesPerDay, true, new Date(), Arrays.asList(roles));
6481
}
6582

66-
public User(Integer id, String name, String email, String password, boolean enabled, Date registered, @NonNull Collection<Role> roles) {
83+
public User(Integer id, String name, String email, String password, int caloriesPerDay, boolean enabled, Date registered, @NonNull Collection<Role> roles) {
6784
super(id, name);
6885
this.email = email;
6986
this.password = password;
87+
this.caloriesPerDay = caloriesPerDay;
7088
this.enabled = enabled;
7189
this.registered = registered;
7290
setRoles(roles);
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
2+
package ru.javaops.topjava.user.repository;
3+
4+
import org.springframework.data.jpa.repository.Query;
5+
import org.springframework.transaction.annotation.Transactional;
6+
import ru.javaops.topjava.common.BaseRepository;
7+
import ru.javaops.topjava.common.error.DataConflictException;
8+
import ru.javaops.topjava.user.model.Meal;
9+
10+
import java.time.LocalDateTime;
11+
import java.util.List;
12+
import java.util.Optional;
13+
14+
@Transactional(readOnly = true)
15+
public interface MealRepository extends BaseRepository<Meal> {
16+
17+
@Query("SELECT m FROM Meal m WHERE m.user.id=:userId ORDER BY m.dateTime DESC")
18+
List<Meal> getAll(int userId);
19+
20+
@Query("SELECT m from Meal m WHERE m.user.id=:userId AND m.dateTime >= :startDate AND m.dateTime < :endDate ORDER BY m.dateTime DESC")
21+
List<Meal> getBetweenHalfOpen(int userId, LocalDateTime startDate, LocalDateTime endDate);
22+
23+
@Query("SELECT m FROM Meal m WHERE m.id = :id and m.user.id = :userId")
24+
Optional<Meal> get(int userId, int id);
25+
26+
default Meal getBelonged(int userId, int id) {
27+
return get(userId, id).orElseThrow(
28+
() -> new DataConflictException("Meal id=" + id + " is not exist or doesn't belong to User id=" + userId));
29+
}
30+
}

src/main/java/ru/javaops/topjava/user/repository/UserRepository.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ public interface UserRepository extends BaseRepository<User> {
1515
@Query("SELECT u FROM User u WHERE LOWER(u.email) = LOWER(:email)")
1616
Optional<User> findByEmailIgnoreCase(String email);
1717

18+
// https://stackoverflow.com/a/46013654/548473
19+
@Query("SELECT u FROM User u LEFT JOIN FETCH u.meals WHERE u.id=?1")
20+
Optional<User> getWithMeals(int id);
21+
1822
@Transactional
1923
default User prepareAndSave(User user) {
2024
user.setPassword(PASSWORD_ENCODER.encode(user.getPassword()));
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package ru.javaops.topjava.user.service;
2+
3+
import lombok.AllArgsConstructor;
4+
import org.springframework.stereotype.Service;
5+
import org.springframework.transaction.annotation.Transactional;
6+
import ru.javaops.topjava.user.model.Meal;
7+
import ru.javaops.topjava.user.repository.MealRepository;
8+
import ru.javaops.topjava.user.repository.UserRepository;
9+
10+
@Service
11+
@AllArgsConstructor
12+
public class MealService {
13+
private final MealRepository mealRepository;
14+
private final UserRepository userRepository;
15+
16+
@Transactional
17+
public Meal save(int userId, Meal meal) {
18+
meal.setUser(userRepository.getExisted(userId));
19+
return mealRepository.save(meal);
20+
}
21+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package ru.javaops.topjava.user.to;
2+
3+
import lombok.EqualsAndHashCode;
4+
import lombok.Value;
5+
import ru.javaops.topjava.common.to.BaseTo;
6+
7+
import java.time.LocalDateTime;
8+
9+
@Value
10+
@EqualsAndHashCode(callSuper = true)
11+
public class MealTo extends BaseTo {
12+
13+
LocalDateTime dateTime;
14+
15+
String description;
16+
17+
int calories;
18+
19+
boolean excess;
20+
21+
public MealTo(Integer id, LocalDateTime dateTime, String description, int calories, boolean excess) {
22+
super(id);
23+
this.dateTime = dateTime;
24+
this.description = description;
25+
this.calories = calories;
26+
this.excess = excess;
27+
}
28+
}

src/main/java/ru/javaops/topjava/user/to/UserTo.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22

33
import jakarta.validation.constraints.Email;
44
import jakarta.validation.constraints.NotBlank;
5+
import jakarta.validation.constraints.NotNull;
56
import jakarta.validation.constraints.Size;
67
import lombok.EqualsAndHashCode;
78
import lombok.Value;
9+
import org.hibernate.validator.constraints.Range;
810
import ru.javaops.topjava.common.HasIdAndEmail;
911
import ru.javaops.topjava.common.to.NamedTo;
1012
import ru.javaops.topjava.common.validation.NoHtml;
@@ -22,10 +24,15 @@ public class UserTo extends NamedTo implements HasIdAndEmail {
2224
@Size(min = 5, max = 32)
2325
String password;
2426

25-
public UserTo(Integer id, String name, String email, String password) {
27+
@Range(min = 10, max = 10000)
28+
@NotNull
29+
Integer caloriesPerDay;
30+
31+
public UserTo(Integer id, String name, String email, String password, int caloriesPerDay) {
2632
super(id, name);
2733
this.email = email;
2834
this.password = password;
35+
this.caloriesPerDay = caloriesPerDay;
2936
}
3037

3138
@Override
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package ru.javaops.topjava.user.util;
2+
3+
import lombok.experimental.UtilityClass;
4+
5+
import java.time.LocalDate;
6+
import java.time.LocalDateTime;
7+
8+
@UtilityClass
9+
public class DateTimeUtil {
10+
11+
// DB doesn't support LocalDate.MIN/MAX
12+
private static final LocalDateTime MIN_DATE = LocalDateTime.of(1, 1, 1, 0, 0);
13+
private static final LocalDateTime MAX_DATE = LocalDateTime.of(3000, 1, 1, 0, 0);
14+
15+
public static LocalDateTime atStartOfDayOrMin(LocalDate localDate) {
16+
return localDate != null ? localDate.atStartOfDay() : MIN_DATE;
17+
}
18+
19+
public static LocalDateTime atStartOfNextDayOrMax(LocalDate localDate) {
20+
return localDate != null ? localDate.plusDays(1).atStartOfDay() : MAX_DATE;
21+
}
22+
}
Lines changed: 43 additions & 0 deletions

0 commit comments

Comments
 (0)