Backend refactor #141

Merged
RecentRunner merged 15 commits from backend-refactor into main 2026-04-06 20:51:44 -06:00
4 changed files with 191 additions and 71 deletions
Showing only changes of commit 824ed7e5eb - Show all commits

View File

@@ -8,14 +8,20 @@ import jakarta.validation.constraints.Size;
import java.util.Objects;
public class UserRequest {
@NotBlank(message = "Username is required")
@Size(min = 3, max = 50, message = "Username must be between 3 and 50 characters")
private String username;
@Size(min = 6, message = "Password must be at least 6 characters")
private String password;
@NotBlank(message = "Full name is required")
@NotBlank(message = "First name is required")
@Size(max = 50)
private String firstName;
@NotBlank(message = "Last name is required")
@Size(max = 50)
private String lastName;
private String fullName;
@Email(message = "Invalid email format")
@@ -27,6 +33,10 @@ public class UserRequest {
@NotNull(message = "Role is required")
private User.Role role;
private String staffRole;
private Long primaryStoreId;
private Boolean active = true;
public String getUsername() {
@@ -45,6 +55,22 @@ public class UserRequest {
this.password = password;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFullName() {
return fullName;
}
@@ -77,6 +103,22 @@ public class UserRequest {
this.role = role;
}
public String getStaffRole() {
return staffRole;
}
public void setStaffRole(String staffRole) {
this.staffRole = staffRole;
}
public Long getPrimaryStoreId() {
return primaryStoreId;
}
public void setPrimaryStoreId(Long primaryStoreId) {
this.primaryStoreId = primaryStoreId;
}
public Boolean getActive() {
return active;
}
@@ -91,29 +133,20 @@ public class UserRequest {
if (o == null || getClass() != o.getClass()) return false;
UserRequest that = (UserRequest) o;
return Objects.equals(username, that.username) &&
Objects.equals(password, that.password) &&
Objects.equals(fullName, that.fullName) &&
Objects.equals(firstName, that.firstName) &&
Objects.equals(lastName, that.lastName) &&
Objects.equals(email, that.email) &&
Objects.equals(phone, that.phone) &&
role == that.role &&
Objects.equals(active, that.active);
role == that.role;
}
@Override
public int hashCode() {
return Objects.hash(username, password, fullName, email, phone, role, active);
return Objects.hash(username, firstName, lastName, email, role);
}
@Override
public String toString() {
return "UserRequest{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
", fullName='" + fullName + '\'' +
", email='" + email + '\'' +
", phone='" + phone + '\'' +
", role=" + role +
", active=" + active +
'}';
return "UserRequest{username='" + username + "', firstName='" + firstName +
"', lastName='" + lastName + "', role=" + role + '}';
}
}

View File

@@ -6,10 +6,15 @@ import java.util.Objects;
public class UserResponse {
private Long id;
private String username;
private String firstName;
private String lastName;
private String fullName;
private String email;
private String phone;
private String role;
private String staffRole;
private Long primaryStoreId;
private Integer loyaltyPoints;
private Boolean active;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@@ -17,18 +22,6 @@ public class UserResponse {
public UserResponse() {
}
public UserResponse(Long id, String username, String fullName, String email, String phone, String role, Boolean active, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.username = username;
this.fullName = fullName;
this.email = email;
this.phone = phone;
this.role = role;
this.active = active;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getId() {
return id;
}
@@ -45,6 +38,22 @@ public class UserResponse {
this.username = username;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFullName() {
return fullName;
}
@@ -77,6 +86,30 @@ public class UserResponse {
this.role = role;
}
public String getStaffRole() {
return staffRole;
}
public void setStaffRole(String staffRole) {
this.staffRole = staffRole;
}
public Long getPrimaryStoreId() {
return primaryStoreId;
}
public void setPrimaryStoreId(Long primaryStoreId) {
this.primaryStoreId = primaryStoreId;
}
public Integer getLoyaltyPoints() {
return loyaltyPoints;
}
public void setLoyaltyPoints(Integer loyaltyPoints) {
this.loyaltyPoints = loyaltyPoints;
}
public Boolean getActive() {
return active;
}
@@ -106,26 +139,17 @@ public class UserResponse {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
UserResponse that = (UserResponse) o;
return Objects.equals(id, that.id) && Objects.equals(username, that.username) && Objects.equals(fullName, that.fullName) && Objects.equals(email, that.email) && Objects.equals(phone, that.phone) && Objects.equals(role, that.role) && Objects.equals(active, that.active) && Objects.equals(createdAt, that.createdAt) && Objects.equals(updatedAt, that.updatedAt);
return Objects.equals(id, that.id);
}
@Override
public int hashCode() {
return Objects.hash(id, username, fullName, email, phone, role, active, createdAt, updatedAt);
return Objects.hash(id);
}
@Override
public String toString() {
return "UserResponse{" +
"id=" + id +
", username='" + username + '\'' +
", fullName='" + fullName + '\'' +
", email='" + email + '\'' +
", phone='" + phone + '\'' +
", role='" + role + '\'' +
", active=" + active +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
return "UserResponse{id=" + id + ", username='" + username + "', firstName='" + firstName +
"', lastName='" + lastName + "', role='" + role + "', active=" + active + '}';
}
}

View File

@@ -16,15 +16,21 @@ public class User {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true, length = 50)
@Column(unique = true, length = 50)
private String username;
@Column(nullable = false)
@Column
private String password;
@Column(unique = true, length = 100)
private String email;
@Column(nullable = false, length = 50)
private String firstName;
@Column(nullable = false, length = 50)
private String lastName;
@Column(length = 100)
private String fullName;
@@ -38,6 +44,16 @@ public class User {
@Column(nullable = false, length = 20, columnDefinition = "VARCHAR(20)")
private Role role;
@Column(length = 50)
private String staffRole;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "primaryStoreId")
private StoreLocation primaryStore;
@Column(nullable = false)
private Integer loyaltyPoints = 0;
@Column(nullable = false)
private Boolean active = true;
@@ -59,21 +75,6 @@ public class User {
public User() {
}
public User(Long id, String username, String password, String email, String fullName, String phone, String avatarUrl, Role role, Boolean active, Integer tokenVersion, LocalDateTime createdAt, LocalDateTime updatedAt) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
this.fullName = fullName;
this.phone = phone;
this.avatarUrl = avatarUrl;
this.role = role;
this.active = active;
this.tokenVersion = tokenVersion;
this.createdAt = createdAt;
this.updatedAt = updatedAt;
}
public Long getId() {
return id;
}
@@ -106,6 +107,22 @@ public class User {
this.email = email;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFullName() {
return fullName;
}
@@ -138,6 +155,30 @@ public class User {
this.role = role;
}
public String getStaffRole() {
return staffRole;
}
public void setStaffRole(String staffRole) {
this.staffRole = staffRole;
}
public StoreLocation getPrimaryStore() {
return primaryStore;
}
public void setPrimaryStore(StoreLocation primaryStore) {
this.primaryStore = primaryStore;
}
public Integer getLoyaltyPoints() {
return loyaltyPoints;
}
public void setLoyaltyPoints(Integer loyaltyPoints) {
this.loyaltyPoints = loyaltyPoints;
}
public Boolean getActive() {
return active;
}
@@ -188,16 +229,12 @@ public class User {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", password='" + password + '\'' +
", email='" + email + '\'' +
", firstName='" + firstName + '\'' +
", lastName='" + lastName + '\'' +
", fullName='" + fullName + '\'' +
", phone='" + phone + '\'' +
", avatarUrl='" + avatarUrl + '\'' +
", role=" + role +
", active=" + active +
", tokenVersion=" + tokenVersion +
", createdAt=" + createdAt +
", updatedAt=" + updatedAt +
'}';
}
}

View File

@@ -3,8 +3,10 @@ package com.petshop.backend.service;
import com.petshop.backend.dto.common.BulkDeleteRequest;
import com.petshop.backend.dto.user.UserRequest;
import com.petshop.backend.dto.user.UserResponse;
import com.petshop.backend.entity.StoreLocation;
import com.petshop.backend.entity.User;
import com.petshop.backend.exception.ResourceNotFoundException;
import com.petshop.backend.repository.StoreRepository;
import com.petshop.backend.repository.UserRepository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
@@ -14,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
import java.util.Locale;
import java.util.Objects;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import static org.springframework.http.HttpStatus.CONFLICT;
@@ -24,11 +27,13 @@ public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final UserBusinessLinkageService userBusinessLinkageService;
private final StoreRepository storeRepository;
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService) {
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder, UserBusinessLinkageService userBusinessLinkageService, StoreRepository storeRepository) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.userBusinessLinkageService = userBusinessLinkageService;
this.storeRepository = storeRepository;
}
public Page<UserResponse> getAllUsers(String query, String role, Pageable pageable) {
@@ -56,12 +61,18 @@ public class UserService {
@Transactional
public UserResponse createUser(UserRequest request) {
User user = new User();
user.setUsername(request.getUsername());
user.setPassword(passwordEncoder.encode(request.getPassword()));
user.setUsername(trimToNull(request.getUsername()));
if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) {
user.setPassword(passwordEncoder.encode(request.getPassword()));
}
user.setFirstName(request.getFirstName());
user.setLastName(request.getLastName());
user.setFullName(request.getFullName());
user.setEmail(request.getEmail());
user.setPhone(trimToNull(request.getPhone()));
user.setRole(request.getRole());
user.setStaffRole(trimToNull(request.getStaffRole()));
user.setPrimaryStore(resolveStore(request.getPrimaryStoreId()));
user.setActive(request.getActive() != null ? request.getActive() : true);
validateUniquePhone(user.getPhone(), null);
@@ -79,23 +90,27 @@ public class UserService {
.orElseThrow(() -> new ResourceNotFoundException("User not found with id: " + id));
boolean invalidateToken =
!user.getUsername().equals(request.getUsername())
!Objects.equals(user.getUsername(), request.getUsername())
|| user.getRole() != request.getRole()
|| !user.getActive().equals(request.getActive() != null ? request.getActive() : true);
user.setUsername(request.getUsername());
user.setUsername(trimToNull(request.getUsername()));
if (request.getPassword() != null && !request.getPassword().trim().isEmpty()) {
user.setPassword(passwordEncoder.encode(request.getPassword()));
invalidateToken = true;
}
user.setFirstName(request.getFirstName());
user.setLastName(request.getLastName());
user.setFullName(request.getFullName());
user.setEmail(request.getEmail());
String phone = trimToNull(request.getPhone());
if (!java.util.Objects.equals(user.getPhone(), phone)) {
if (!Objects.equals(user.getPhone(), phone)) {
validateUniquePhone(phone, user.getId());
}
user.setPhone(phone);
user.setRole(request.getRole());
user.setStaffRole(trimToNull(request.getStaffRole()));
user.setPrimaryStore(resolveStore(request.getPrimaryStoreId()));
user.setActive(request.getActive() != null ? request.getActive() : true);
if (invalidateToken) {
user.setTokenVersion(user.getTokenVersion() + 1);
@@ -123,16 +138,27 @@ public class UserService {
UserResponse response = new UserResponse();
response.setId(user.getId());
response.setUsername(user.getUsername());
response.setFirstName(user.getFirstName());
response.setLastName(user.getLastName());
response.setFullName(user.getFullName());
response.setEmail(user.getEmail());
response.setPhone(user.getPhone());
response.setRole(user.getRole().toString());
response.setStaffRole(user.getStaffRole());
response.setPrimaryStoreId(user.getPrimaryStore() != null ? user.getPrimaryStore().getStoreId() : null);
response.setLoyaltyPoints(user.getLoyaltyPoints());
response.setActive(user.getActive());
response.setCreatedAt(user.getCreatedAt());
response.setUpdatedAt(user.getUpdatedAt());
return response;
}
private StoreLocation resolveStore(Long storeId) {
if (storeId == null) return null;
return storeRepository.findById(storeId)
.orElseThrow(() -> new ResourceNotFoundException("Store not found with id: " + storeId));
}
private void validateUniquePhone(String phone, Long currentUserId) {
if (phone == null || phone.isBlank()) {
return;