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)
|
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, optional = false, fetch = FetchType.LAZY)
|
||||||
private UserPersonalDetails personalDetails;
|
private UserPersonalDetails personalDetails;
|
||||||
|
|
||||||
|
@OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, optional = false, fetch = FetchType.LAZY)
|
||||||
|
private UserPreferences preferences;
|
||||||
|
|
||||||
public User() {}
|
public User() {}
|
||||||
|
|
||||||
public User(String id, boolean activated, String email, String passwordHash, String name) {
|
public User(String id, boolean activated, String email, String passwordHash, String name) {
|
||||||
|
@ -50,6 +53,7 @@ public class User {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.roles = new HashSet<>();
|
this.roles = new HashSet<>();
|
||||||
this.personalDetails = new UserPersonalDetails(this);
|
this.personalDetails = new UserPersonalDetails(this);
|
||||||
|
this.preferences = new UserPreferences(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getId() {
|
public String getId() {
|
||||||
|
@ -91,4 +95,8 @@ public class User {
|
||||||
public UserPersonalDetails getPersonalDetails() {
|
public UserPersonalDetails getPersonalDetails() {
|
||||||
return personalDetails;
|
return personalDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public UserPreferences getPreferences() {
|
||||||
|
return preferences;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,19 @@ public class UserPersonalDetails {
|
||||||
public enum PersonSex {
|
public enum PersonSex {
|
||||||
MALE,
|
MALE,
|
||||||
FEMALE,
|
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
|
@Id
|
||||||
|
@ -50,6 +62,10 @@ public class UserPersonalDetails {
|
||||||
this.userId = user.getId();
|
this.userId = user.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getUserId() {
|
||||||
|
return userId;
|
||||||
|
}
|
||||||
|
|
||||||
public User getUser() {
|
public User getUser() {
|
||||||
return user;
|
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.MessagingException;
|
||||||
import jakarta.mail.internet.MimeMessage;
|
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.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.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.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.PasswordResetCode;
|
||||||
import nl.andrewlalis.gymboard_api.domains.auth.model.User;
|
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.UserActivationCode;
|
||||||
|
import nl.andrewlalis.gymboard_api.domains.auth.model.UserPersonalDetails;
|
||||||
import nl.andrewlalis.gymboard_api.util.StringGenerator;
|
import nl.andrewlalis.gymboard_api.util.StringGenerator;
|
||||||
import nl.andrewlalis.gymboard_api.util.ULID;
|
import nl.andrewlalis.gymboard_api.util.ULID;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -23,6 +26,7 @@ import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.web.server.ResponseStatusException;
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
@ -33,6 +37,7 @@ public class UserService {
|
||||||
private static final Logger log = LoggerFactory.getLogger(UserService.class);
|
private static final Logger log = LoggerFactory.getLogger(UserService.class);
|
||||||
|
|
||||||
private final UserRepository userRepository;
|
private final UserRepository userRepository;
|
||||||
|
private final UserPersonalDetailsRepository userPersonalDetailsRepository;
|
||||||
private final UserActivationCodeRepository activationCodeRepository;
|
private final UserActivationCodeRepository activationCodeRepository;
|
||||||
private final PasswordResetCodeRepository passwordResetCodeRepository;
|
private final PasswordResetCodeRepository passwordResetCodeRepository;
|
||||||
private final ULID ulid;
|
private final ULID ulid;
|
||||||
|
@ -44,13 +49,14 @@ public class UserService {
|
||||||
|
|
||||||
public UserService(
|
public UserService(
|
||||||
UserRepository userRepository,
|
UserRepository userRepository,
|
||||||
UserActivationCodeRepository activationCodeRepository,
|
UserPersonalDetailsRepository userPersonalDetailsRepository, UserActivationCodeRepository activationCodeRepository,
|
||||||
PasswordResetCodeRepository passwordResetCodeRepository,
|
PasswordResetCodeRepository passwordResetCodeRepository,
|
||||||
ULID ulid,
|
ULID ulid,
|
||||||
PasswordEncoder passwordEncoder,
|
PasswordEncoder passwordEncoder,
|
||||||
JavaMailSender mailSender
|
JavaMailSender mailSender
|
||||||
) {
|
) {
|
||||||
this.userRepository = userRepository;
|
this.userRepository = userRepository;
|
||||||
|
this.userPersonalDetailsRepository = userPersonalDetailsRepository;
|
||||||
this.activationCodeRepository = activationCodeRepository;
|
this.activationCodeRepository = activationCodeRepository;
|
||||||
this.passwordResetCodeRepository = passwordResetCodeRepository;
|
this.passwordResetCodeRepository = passwordResetCodeRepository;
|
||||||
this.ulid = ulid;
|
this.ulid = ulid;
|
||||||
|
@ -220,4 +226,36 @@ public class UserService {
|
||||||
LocalDateTime activationCodeCutoff = LocalDateTime.now().minus(UserActivationCode.VALID_FOR);
|
LocalDateTime activationCodeCutoff = LocalDateTime.now().minus(UserActivationCode.VALID_FOR);
|
||||||
activationCodeRepository.deleteAllByCreatedAtBefore(activationCodeCutoff);
|
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.beans.factory.annotation.Value;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.core.env.Environment;
|
|
||||||
import org.springframework.web.cors.CorsConfiguration;
|
import org.springframework.web.cors.CorsConfiguration;
|
||||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||||
import org.springframework.web.filter.CorsFilter;
|
import org.springframework.web.filter.CorsFilter;
|
||||||
|
|
Loading…
Reference in New Issue