Expanded user-derived database models.
This commit is contained in:
parent
8c2a84755d
commit
9218a38850
|
@ -0,0 +1,34 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.auth.controller;
|
||||
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dto.UserPersonalDetailsPayload;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dto.UserPersonalDetailsResponse;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dto.UserResponse;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.service.UserService;
|
||||
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||
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.RestController;
|
||||
|
||||
@RestController
|
||||
public class UserController {
|
||||
private final UserService userService;
|
||||
|
||||
public UserController(UserService userService) {
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@GetMapping(path = "/auth/me/personal-details")
|
||||
public UserPersonalDetailsResponse getMyPersonalDetails(@AuthenticationPrincipal User user) {
|
||||
return userService.getPersonalDetails(user.getId());
|
||||
}
|
||||
|
||||
@PostMapping(path = "/auth/me/personal-details")
|
||||
public UserResponse updateMyPersonalDetails(
|
||||
@AuthenticationPrincipal User user,
|
||||
@RequestBody UserPersonalDetailsPayload payload
|
||||
) {
|
||||
return userService.updatePersonalDetails(user.getId(), payload);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.auth.dao;
|
||||
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.UserPersonalDetails;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface UserPersonalDetailsRepository extends JpaRepository<UserPersonalDetails, String> {}
|
|
@ -0,0 +1,10 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.auth.dto;
|
||||
|
||||
import java.time.LocalDate;
|
||||
|
||||
public record UserPersonalDetailsPayload(
|
||||
LocalDate birthDate,
|
||||
Float currentWeight,
|
||||
String currentWeightUnit,
|
||||
String sex
|
||||
) {}
|
|
@ -0,0 +1,25 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.auth.dto;
|
||||
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.UserPersonalDetails;
|
||||
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
public record UserPersonalDetailsResponse(
|
||||
String userId,
|
||||
String birthDate,
|
||||
float currentWeight,
|
||||
String currentWeightUnit,
|
||||
float currentMetricWeight,
|
||||
String sex
|
||||
) {
|
||||
public UserPersonalDetailsResponse(UserPersonalDetails pd) {
|
||||
this(
|
||||
pd.getUserId(),
|
||||
pd.getBirthDate().format(DateTimeFormatter.ISO_LOCAL_DATE),
|
||||
pd.getCurrentWeight().floatValue(),
|
||||
pd.getCurrentWeightUnit().name(),
|
||||
pd.getCurrentMetricWeight().floatValue(),
|
||||
pd.getSex().name()
|
||||
);
|
||||
}
|
||||
}
|
|
@ -40,6 +40,9 @@ public class User {
|
|||
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, optional = false, fetch = FetchType.LAZY)
|
||||
private UserPersonalDetails personalDetails;
|
||||
|
||||
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, optional = false, fetch = FetchType.LAZY)
|
||||
private UserPreferences preferences;
|
||||
|
||||
public User() {}
|
||||
|
||||
public User(String id, boolean activated, String email, String passwordHash, String name) {
|
||||
|
@ -50,6 +53,7 @@ public class User {
|
|||
this.name = name;
|
||||
this.roles = new HashSet<>();
|
||||
this.personalDetails = new UserPersonalDetails(this);
|
||||
this.preferences = new UserPreferences(this);
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
|
@ -91,4 +95,8 @@ public class User {
|
|||
public UserPersonalDetails getPersonalDetails() {
|
||||
return personalDetails;
|
||||
}
|
||||
|
||||
public UserPreferences getPreferences() {
|
||||
return preferences;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,19 @@ public class UserPersonalDetails {
|
|||
public enum PersonSex {
|
||||
MALE,
|
||||
FEMALE,
|
||||
UNKNOWN
|
||||
UNKNOWN;
|
||||
|
||||
public static PersonSex parse(String s) {
|
||||
if (s != null && !s.isBlank()) {
|
||||
s = s.strip().toUpperCase();
|
||||
if (s.equals("M") || s.equals("MALE") || s.equals("MAN")) {
|
||||
return MALE;
|
||||
} else if (s.equals("F") || s.equals("FEMALE") || s.equals("WOMAN")) {
|
||||
return FEMALE;
|
||||
}
|
||||
}
|
||||
return UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
@Id
|
||||
|
@ -50,6 +62,10 @@ public class UserPersonalDetails {
|
|||
this.userId = user.getId();
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
package nl.andrewlalis.gymboard_api.domains.auth.model;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
|
||||
@Entity
|
||||
@Table(name = "auth_user_preferences")
|
||||
public class UserPreferences {
|
||||
@Id
|
||||
@Column(name = "user_id", length = 26)
|
||||
private String userId;
|
||||
|
||||
@OneToOne(optional = false, fetch = FetchType.LAZY)
|
||||
@PrimaryKeyJoinColumn(name = "user_id", referencedColumnName = "id")
|
||||
private User user;
|
||||
|
||||
/**
|
||||
* Flag which, if true, indicates that the user's account is private, and
|
||||
* only approved users may view certain information about them.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
private boolean accountPrivate = false;
|
||||
|
||||
/**
|
||||
* The user's preferred locale. This should always refer to one of the
|
||||
* available locales offered by the front-end app.
|
||||
*/
|
||||
@Column(nullable = false)
|
||||
private String locale = "en-US";
|
||||
|
||||
public UserPreferences() {}
|
||||
|
||||
public UserPreferences(User user) {
|
||||
this.user = user;
|
||||
this.userId = user.getId();
|
||||
}
|
||||
|
||||
public String getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public User getUser() {
|
||||
return user;
|
||||
}
|
||||
|
||||
public boolean isAccountPrivate() {
|
||||
return accountPrivate;
|
||||
}
|
||||
|
||||
public void setAccountPrivate(boolean accountPrivate) {
|
||||
this.accountPrivate = accountPrivate;
|
||||
}
|
||||
|
||||
public String getLocale() {
|
||||
return locale;
|
||||
}
|
||||
|
||||
public void setLocale(String locale) {
|
||||
this.locale = locale;
|
||||
}
|
||||
}
|
|
@ -2,13 +2,16 @@ package nl.andrewlalis.gymboard_api.domains.auth.service;
|
|||
|
||||
import jakarta.mail.MessagingException;
|
||||
import jakarta.mail.internet.MimeMessage;
|
||||
import nl.andrewlalis.gymboard_api.domains.api.model.WeightUnit;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dao.PasswordResetCodeRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dto.*;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserActivationCodeRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserPersonalDetailsRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dao.UserRepository;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.dto.*;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.PasswordResetCode;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.UserActivationCode;
|
||||
import nl.andrewlalis.gymboard_api.domains.auth.model.UserPersonalDetails;
|
||||
import nl.andrewlalis.gymboard_api.util.StringGenerator;
|
||||
import nl.andrewlalis.gymboard_api.util.ULID;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -23,6 +26,7 @@ import org.springframework.stereotype.Service;
|
|||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.server.ResponseStatusException;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.security.SecureRandom;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Random;
|
||||
|
@ -33,6 +37,7 @@ public class UserService {
|
|||
private static final Logger log = LoggerFactory.getLogger(UserService.class);
|
||||
|
||||
private final UserRepository userRepository;
|
||||
private final UserPersonalDetailsRepository userPersonalDetailsRepository;
|
||||
private final UserActivationCodeRepository activationCodeRepository;
|
||||
private final PasswordResetCodeRepository passwordResetCodeRepository;
|
||||
private final ULID ulid;
|
||||
|
@ -44,13 +49,14 @@ public class UserService {
|
|||
|
||||
public UserService(
|
||||
UserRepository userRepository,
|
||||
UserActivationCodeRepository activationCodeRepository,
|
||||
UserPersonalDetailsRepository userPersonalDetailsRepository, UserActivationCodeRepository activationCodeRepository,
|
||||
PasswordResetCodeRepository passwordResetCodeRepository,
|
||||
ULID ulid,
|
||||
PasswordEncoder passwordEncoder,
|
||||
JavaMailSender mailSender
|
||||
) {
|
||||
this.userRepository = userRepository;
|
||||
this.userPersonalDetailsRepository = userPersonalDetailsRepository;
|
||||
this.activationCodeRepository = activationCodeRepository;
|
||||
this.passwordResetCodeRepository = passwordResetCodeRepository;
|
||||
this.ulid = ulid;
|
||||
|
@ -220,4 +226,36 @@ public class UserService {
|
|||
LocalDateTime activationCodeCutoff = LocalDateTime.now().minus(UserActivationCode.VALID_FOR);
|
||||
activationCodeRepository.deleteAllByCreatedAtBefore(activationCodeCutoff);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public UserResponse updatePersonalDetails(String id, UserPersonalDetailsPayload payload) {
|
||||
User user = userRepository.findById(id)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
var pd = user.getPersonalDetails();
|
||||
|
||||
pd.setBirthDate(payload.birthDate());
|
||||
BigDecimal currentWeight = payload.currentWeight() == null ? null : BigDecimal.valueOf(payload.currentWeight());
|
||||
WeightUnit currentWeightUnit = WeightUnit.parse(payload.currentWeightUnit());
|
||||
BigDecimal currentMetricWeight = null;
|
||||
if (currentWeight != null) {
|
||||
if (currentWeightUnit == WeightUnit.POUNDS) {
|
||||
currentMetricWeight = WeightUnit.toKilograms(currentWeight);
|
||||
} else {
|
||||
currentMetricWeight = new BigDecimal(currentWeight.toString());
|
||||
}
|
||||
}
|
||||
pd.setCurrentWeight(currentWeight);
|
||||
pd.setCurrentWeightUnit(currentWeightUnit);
|
||||
pd.setCurrentMetricWeight(currentMetricWeight);
|
||||
pd.setSex(UserPersonalDetails.PersonSex.parse(payload.sex()));
|
||||
user = userRepository.save(user);
|
||||
return new UserResponse(user);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public UserPersonalDetailsResponse getPersonalDetails(String id) {
|
||||
var pd = userPersonalDetailsRepository.findById(id)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
return new UserPersonalDetailsResponse(pd);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package nl.andrewlalis.gymboardsearch.config;
|
|||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
|
Loading…
Reference in New Issue