ApplicationUser.java

package org.petify.backend.models;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonManagedReference;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.JoinTable;
import jakarta.persistence.Lob;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import jakarta.persistence.Transient;
import jakarta.persistence.Version;
import lombok.Getter;
import lombok.Setter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

@Getter
@Setter
@Entity
@Table(name = "users")
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class ApplicationUser implements UserDetails {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer userId;

    @Column(unique = true)
    private String username;

    private String password;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @Column(name = "birth_date")
    private LocalDate birthDate;

    @Column(name = "gender")
    private String gender;

    @Column(name = "phone_number", unique = true)
    private String phoneNumber;

    @Column(name = "email", unique = true)
    private String email;

    @Enumerated(EnumType.STRING)
    @Column(name = "volunteer_status")
    private VolunteerStatus volunteerStatus = VolunteerStatus.NONE;

    @Column(name = "active")
    private boolean active = true;

    @Column(name = "deactivation_reason")
    private String deactivationReason;

    @Column(name = "created_at")
    private LocalDateTime createdAt = LocalDateTime.now();

    @Column(name = "xp_points")
    private Integer xpPoints = 0;

    @Column(name = "level")
    private Integer level = 1;

    @Column(name = "likes_count")
    private Integer likesCount = 0;

    @Column(name = "support_count")
    private Integer supportCount = 0;

    @Column(name = "badges_count")
    private Integer badgesCount = 0;

    @Column(name = "adoption_count")
    private Integer adoptionCount = 0;

    @Column(name = "city")
    private String city;

    @Column(name = "latitude")
    private Double latitude;

    @Column(name = "longitude")
    private Double longitude;

    @Column(name = "preferred_search_distance_km")
    private Double preferredSearchDistanceKm = 20.0;

    @Column(name = "auto_location_enabled")
    private Boolean autoLocationEnabled = false;

    @Column(name = "location_updated_at")
    private LocalDateTime locationUpdatedAt;

    @Column(name = "profile_image")
    @Lob
    @JsonIgnore
    private byte[] profileImage;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    @JsonManagedReference
    private Set<UserAchievement> achievements = new HashSet<>();

    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(
            name = "user_role_junction",
            joinColumns = {@JoinColumn(name = "user_id")},
            inverseJoinColumns = {@JoinColumn(name = "role_id")}
    )
    private Set<Role> authorities;

    @Version
    private Integer version = 0;

    @Transient
    public Integer getXpToNextLevel() {
        int currentLevel = this.level != null ? this.level : 1;
        int currentXp = this.xpPoints != null ? this.xpPoints : 0;

        int xpRequiredForNextLevel = currentLevel * 100;

        return Math.max(0, xpRequiredForNextLevel - currentXp);
    }

    @Transient
    public boolean hasLocation() {
        return latitude != null && longitude != null;
    }

    @Transient
    public boolean hasCompleteLocationProfile() {
        return city != null && !city.trim().isEmpty() && hasLocation();
    }

    @Transient
    @JsonIgnore
    public boolean hasProfileImage() {
        try {
            return profileImage != null && profileImage.length > 0;
        } catch (Exception e) {
            return false;
        }
    }

    public void setLocation(String city, Double latitude, Double longitude) {
        this.city = city;
        this.latitude = latitude;
        this.longitude = longitude;
        this.locationUpdatedAt = LocalDateTime.now();
    }

    public void clearLocation() {
        this.city = null;
        this.latitude = null;
        this.longitude = null;
        this.locationUpdatedAt = null;
    }

    public ApplicationUser() {
        super();
        authorities = new HashSet<>();
    }

    public ApplicationUser(Integer userId, String username, String password,
                           String firstName, String lastName, LocalDate birthDate,
                           String gender, String phoneNumber, String email, Set<Role> authorities) {
        super();
        this.userId = userId;
        this.username = username;
        this.password = password;
        this.firstName = firstName;
        this.lastName = lastName;
        this.birthDate = birthDate;
        this.gender = gender;
        this.phoneNumber = phoneNumber;
        this.email = email;
        this.authorities = authorities;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return this.authorities;
    }

    @Override
    public String getPassword() {
        return this.password;
    }

    @Override
    public String getUsername() {
        return this.username;
    }

    @Override
    public boolean isAccountNonExpired() {
        return true;
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }
}