1 package org.petify.backend.models; 2 3 import com.fasterxml.jackson.annotation.JsonIgnore; 4 import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 5 import com.fasterxml.jackson.annotation.JsonManagedReference; 6 import jakarta.persistence.CascadeType; 7 import jakarta.persistence.Column; 8 import jakarta.persistence.Entity; 9 import jakarta.persistence.EnumType; 10 import jakarta.persistence.Enumerated; 11 import jakarta.persistence.FetchType; 12 import jakarta.persistence.GeneratedValue; 13 import jakarta.persistence.GenerationType; 14 import jakarta.persistence.Id; 15 import jakarta.persistence.JoinColumn; 16 import jakarta.persistence.JoinTable; 17 import jakarta.persistence.Lob; 18 import jakarta.persistence.ManyToMany; 19 import jakarta.persistence.OneToMany; 20 import jakarta.persistence.Table; 21 import jakarta.persistence.Transient; 22 import jakarta.persistence.Version; 23 import lombok.Getter; 24 import lombok.Setter; 25 import org.springframework.security.core.GrantedAuthority; 26 import org.springframework.security.core.userdetails.UserDetails; 27 28 import java.time.LocalDate; 29 import java.time.LocalDateTime; 30 import java.util.Collection; 31 import java.util.HashSet; 32 import java.util.Set; 33 34 @Getter 35 @Setter 36 @Entity 37 @Table(name = "users") 38 @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) 39 public class ApplicationUser implements UserDetails { 40 @Id 41 @GeneratedValue(strategy = GenerationType.IDENTITY) 42 private Integer userId; 43 44 @Column(unique = true) 45 private String username; 46 47 private String password; 48 49 @Column(name = "first_name") 50 private String firstName; 51 52 @Column(name = "last_name") 53 private String lastName; 54 55 @Column(name = "birth_date") 56 private LocalDate birthDate; 57 58 @Column(name = "gender") 59 private String gender; 60 61 @Column(name = "phone_number", unique = true) 62 private String phoneNumber; 63 64 @Column(name = "email", unique = true) 65 private String email; 66 67 @Enumerated(EnumType.STRING) 68 @Column(name = "volunteer_status") 69 private VolunteerStatus volunteerStatus = VolunteerStatus.NONE; 70 71 @Column(name = "active") 72 private boolean active = true; 73 74 @Column(name = "deactivation_reason") 75 private String deactivationReason; 76 77 @Column(name = "created_at") 78 private LocalDateTime createdAt = LocalDateTime.now(); 79 80 @Column(name = "xp_points") 81 private Integer xpPoints = 0; 82 83 @Column(name = "level") 84 private Integer level = 1; 85 86 @Column(name = "likes_count") 87 private Integer likesCount = 0; 88 89 @Column(name = "support_count") 90 private Integer supportCount = 0; 91 92 @Column(name = "badges_count") 93 private Integer badgesCount = 0; 94 95 @Column(name = "adoption_count") 96 private Integer adoptionCount = 0; 97 98 @Column(name = "city") 99 private String city; 100 101 @Column(name = "latitude") 102 private Double latitude; 103 104 @Column(name = "longitude") 105 private Double longitude; 106 107 @Column(name = "preferred_search_distance_km") 108 private Double preferredSearchDistanceKm = 20.0; 109 110 @Column(name = "auto_location_enabled") 111 private Boolean autoLocationEnabled = false; 112 113 @Column(name = "location_updated_at") 114 private LocalDateTime locationUpdatedAt; 115 116 @Column(name = "profile_image") 117 @Lob 118 @JsonIgnore 119 private byte[] profileImage; 120 121 @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) 122 @JsonManagedReference 123 private Set<UserAchievement> achievements = new HashSet<>(); 124 125 @ManyToMany(fetch = FetchType.EAGER) 126 @JoinTable( 127 name = "user_role_junction", 128 joinColumns = {@JoinColumn(name = "user_id")}, 129 inverseJoinColumns = {@JoinColumn(name = "role_id")} 130 ) 131 private Set<Role> authorities; 132 133 @Version 134 private Integer version = 0; 135 136 @Transient 137 public Integer getXpToNextLevel() { 138 int currentLevel = this.level != null ? this.level : 1; 139 int currentXp = this.xpPoints != null ? this.xpPoints : 0; 140 141 int xpRequiredForNextLevel = currentLevel * 100; 142 143 return Math.max(0, xpRequiredForNextLevel - currentXp); 144 } 145 146 @Transient 147 public boolean hasLocation() { 148 return latitude != null && longitude != null; 149 } 150 151 @Transient 152 public boolean hasCompleteLocationProfile() { 153 return city != null && !city.trim().isEmpty() && hasLocation(); 154 } 155 156 @Transient 157 @JsonIgnore 158 public boolean hasProfileImage() { 159 try { 160 return profileImage != null && profileImage.length > 0; 161 } catch (Exception e) { 162 return false; 163 } 164 } 165 166 public void setLocation(String city, Double latitude, Double longitude) { 167 this.city = city; 168 this.latitude = latitude; 169 this.longitude = longitude; 170 this.locationUpdatedAt = LocalDateTime.now(); 171 } 172 173 public void clearLocation() { 174 this.city = null; 175 this.latitude = null; 176 this.longitude = null; 177 this.locationUpdatedAt = null; 178 } 179 180 public ApplicationUser() { 181 super(); 182 authorities = new HashSet<>(); 183 } 184 185 public ApplicationUser(Integer userId, String username, String password, 186 String firstName, String lastName, LocalDate birthDate, 187 String gender, String phoneNumber, String email, Set<Role> authorities) { 188 super(); 189 this.userId = userId; 190 this.username = username; 191 this.password = password; 192 this.firstName = firstName; 193 this.lastName = lastName; 194 this.birthDate = birthDate; 195 this.gender = gender; 196 this.phoneNumber = phoneNumber; 197 this.email = email; 198 this.authorities = authorities; 199 } 200 201 @Override 202 public Collection<? extends GrantedAuthority> getAuthorities() { 203 return this.authorities; 204 } 205 206 @Override 207 public String getPassword() { 208 return this.password; 209 } 210 211 @Override 212 public String getUsername() { 213 return this.username; 214 } 215 216 @Override 217 public boolean isAccountNonExpired() { 218 return true; 219 } 220 221 @Override 222 public boolean isAccountNonLocked() { 223 return true; 224 } 225 226 @Override 227 public boolean isCredentialsNonExpired() { 228 return true; 229 } 230 231 @Override 232 public boolean isEnabled() { 233 return true; 234 } 235 }