feat: implement User and UserCredential models with repository and service layers

This commit is contained in:
2026-04-12 05:32:31 +08:00
parent bd1f2441f3
commit e65df08d1b
9 changed files with 499 additions and 0 deletions
@@ -0,0 +1,73 @@
package com.onixbyte.deltaforceguide.domain.entity;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "app_user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "username", nullable = false)
private String username;
@Column(name = "email", nullable = false)
private String email;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
private List<UserCredential> credentials = new ArrayList<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public List<UserCredential> getCredentials() {
return credentials;
}
public void setCredentials(List<UserCredential> credentials) {
this.credentials = credentials;
}
public void addCredential(UserCredential credential) {
this.credentials.add(credential);
credential.setUser(this);
}
public void removeCredential(UserCredential credential) {
this.credentials.remove(credential);
credential.setUser(null);
}
}
@@ -0,0 +1,83 @@
package com.onixbyte.deltaforceguide.domain.entity;
import jakarta.persistence.Column;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.AttributeOverrides;
import jakarta.persistence.EmbeddedId;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.ForeignKey;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.MapsId;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
@Entity
@Table(name = "app_user_credential")
public class UserCredential {
@EmbeddedId
@AttributeOverrides({
@AttributeOverride(name = "userId", column = @Column(name = "user_id")),
@AttributeOverride(name = "provider", column = @Column(name = "provider"))
})
private UserCredentialId id = new UserCredentialId();
@MapsId("userId")
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "user_id", nullable = false, foreignKey = @ForeignKey(name = "fk_user_credential_user"))
private User user;
@Column(name = "credential", nullable = false, length = 255)
private String credential;
public UserCredentialId getId() {
return id;
}
public void setId(UserCredentialId id) {
this.id = id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
if (this.id == null) {
this.id = new UserCredentialId();
}
this.id.setUserId(user == null ? null : user.getId());
}
public Long getUserId() {
return id == null ? null : id.getUserId();
}
public void setUserId(Long userId) {
if (this.id == null) {
this.id = new UserCredentialId();
}
this.id.setUserId(userId);
}
public String getProvider() {
return id == null ? null : id.getProvider();
}
public void setProvider(String provider) {
if (this.id == null) {
this.id = new UserCredentialId();
}
this.id.setProvider(provider);
}
public String getCredential() {
return credential;
}
public void setCredential(String credential) {
this.credential = credential;
}
}
@@ -0,0 +1,49 @@
package com.onixbyte.deltaforceguide.domain.entity;
import jakarta.persistence.Embeddable;
import java.io.Serializable;
import java.util.Objects;
@Embeddable
public class UserCredentialId implements Serializable {
private Long userId;
private String provider;
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
public String getProvider() {
return provider;
}
public void setProvider(String provider) {
this.provider = provider;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
UserCredentialId that = (UserCredentialId) o;
return Objects.equals(userId, that.userId) && Objects.equals(provider, that.provider);
}
@Override
public int hashCode() {
return Objects.hash(userId, provider);
}
}
@@ -0,0 +1,47 @@
package com.onixbyte.deltaforceguide.manager;
import com.onixbyte.deltaforceguide.domain.entity.UserCredential;
import com.onixbyte.deltaforceguide.repository.UserCredentialRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@Component
public class UserCredentialManager {
private final UserCredentialRepository userCredentialRepository;
public UserCredentialManager(UserCredentialRepository userCredentialRepository) {
this.userCredentialRepository = userCredentialRepository;
}
@Transactional(readOnly = true)
public List<UserCredential> findAllByUserId(Long userId) {
return userCredentialRepository.findAllByUserId(userId);
}
@Transactional(readOnly = true)
public Optional<UserCredential> findByUserIdAndProvider(Long userId, String provider) {
return userCredentialRepository.findByUserIdAndProvider(userId, provider);
}
@Transactional
public UserCredential save(UserCredential userCredential) {
return userCredentialRepository.save(userCredential);
}
@Transactional
public void deleteByUserIdAndProvider(Long userId, String provider) {
userCredentialRepository.deleteByUserIdAndProvider(userId, provider);
}
@Transactional
public void deleteAllByUserId(Long userId) {
userCredentialRepository.deleteAllByUserId(userId);
}
}
@@ -0,0 +1,50 @@
package com.onixbyte.deltaforceguide.manager;
import com.onixbyte.deltaforceguide.domain.entity.User;
import com.onixbyte.deltaforceguide.repository.UserRepository;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
@Component
public class UserManager {
private final UserRepository userRepository;
public UserManager(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Transactional(readOnly = true)
public Optional<User> findById(Long id) {
return userRepository.findById(id);
}
@Transactional(readOnly = true)
public List<User> findAll() {
return userRepository.findAll();
}
@Transactional(readOnly = true)
public Optional<User> findByUsername(String username) {
return userRepository.findByUsername(username);
}
@Transactional(readOnly = true)
public Optional<User> findByEmail(String email) {
return userRepository.findByEmail(email);
}
@Transactional
public User save(User user) {
return userRepository.save(user);
}
@Transactional
public void deleteById(Long id) {
userRepository.deleteById(id);
}
}
@@ -0,0 +1,52 @@
package com.onixbyte.deltaforceguide.repository;
import com.onixbyte.deltaforceguide.domain.entity.UserCredential;
import com.onixbyte.deltaforceguide.domain.entity.UserCredentialId;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface UserCredentialRepository extends JpaRepository<UserCredential, UserCredentialId> {
@EntityGraph(attributePaths = {"user"})
@Query("""
select uc
from UserCredential uc
where uc.user.id = :userId
""")
List<UserCredential> findAllByUserId(@Param("userId") Long userId);
@EntityGraph(attributePaths = {"user"})
@Query("""
select uc
from UserCredential uc
where uc.user.id = :userId
and uc.id.provider = :provider
""")
Optional<UserCredential> findByUserIdAndProvider(@Param("userId") Long userId, @Param("provider") String provider);
@Modifying
@Query("""
delete from UserCredential uc
where uc.user.id = :userId
and uc.id.provider = :provider
""")
void deleteByUserIdAndProvider(@Param("userId") Long userId, @Param("provider") String provider);
@Modifying
@Query("""
delete from UserCredential uc
where uc.user.id = :userId
""")
void deleteAllByUserId(@Param("userId") Long userId);
}
@@ -0,0 +1,29 @@
package com.onixbyte.deltaforceguide.repository;
import com.onixbyte.deltaforceguide.domain.entity.User;
import org.jspecify.annotations.NonNull;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
@Override
@EntityGraph(attributePaths = {"credentials"})
@NonNull
Optional<User> findById(@NonNull Long id);
@EntityGraph(attributePaths = {"credentials"})
Optional<User> findByUsername(String username);
@EntityGraph(attributePaths = {"credentials"})
Optional<User> findByEmail(String email);
boolean existsByUsername(String username);
boolean existsByEmail(String email);
}
@@ -0,0 +1,100 @@
package com.onixbyte.deltaforceguide.service;
import com.onixbyte.deltaforceguide.domain.entity.User;
import com.onixbyte.deltaforceguide.domain.entity.UserCredential;
import com.onixbyte.deltaforceguide.manager.UserCredentialManager;
import com.onixbyte.deltaforceguide.manager.UserManager;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.server.ResponseStatusException;
import java.util.List;
@Service
public class UserService {
private final UserManager userManager;
private final UserCredentialManager userCredentialManager;
public UserService(UserManager userManager, UserCredentialManager userCredentialManager) {
this.userManager = userManager;
this.userCredentialManager = userCredentialManager;
}
@Transactional(readOnly = true)
public List<User> findAll() {
return userManager.findAll();
}
@Transactional(readOnly = true)
public User queryById(Long id) {
return userManager.findById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found: " + id));
}
@Transactional(readOnly = true)
public User queryByUsername(String username) {
return userManager.findByUsername(username)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found: " + username));
}
@Transactional
public User create(User user) {
return userManager.save(user);
}
@Transactional
public User update(User user) {
if (user.getId() == null || userManager.findById(user.getId()).isEmpty()) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found: " + user.getId());
}
return userManager.save(user);
}
@Transactional(readOnly = true)
public List<UserCredential> findCredentials(Long userId) {
ensureUserExists(userId);
return userCredentialManager.findAllByUserId(userId);
}
@Transactional(readOnly = true)
public UserCredential queryCredential(Long userId, String provider) {
ensureUserExists(userId);
return userCredentialManager.findByUserIdAndProvider(userId, provider)
.orElseThrow(() -> new ResponseStatusException(
HttpStatus.NOT_FOUND,
"User credential not found: userId=" + userId + ", provider=" + provider));
}
@Transactional
public UserCredential upsertCredential(Long userId, String provider, String credential) {
User user = ensureUserExists(userId);
UserCredential userCredential = userCredentialManager.findByUserIdAndProvider(userId, provider)
.orElseGet(UserCredential::new);
userCredential.setUser(user);
userCredential.setProvider(provider);
userCredential.setCredential(credential);
return userCredentialManager.save(userCredential);
}
@Transactional
public void deleteCredential(Long userId, String provider) {
ensureUserExists(userId);
userCredentialManager.deleteByUserIdAndProvider(userId, provider);
}
@Transactional
public void deleteById(Long id) {
ensureUserExists(id);
userCredentialManager.deleteAllByUserId(id);
userManager.deleteById(id);
}
private User ensureUserExists(Long userId) {
return userManager.findById(userId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "User not found: " + userId));
}
}
@@ -0,0 +1,16 @@
DROP TABLE IF EXISTS app_user;
CREATE TABLE app_user
(
id BIGSERIAL NOT NULL PRIMARY KEY,
username VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL
);
DROP TABLE IF EXISTS app_user_credential;
CREATE TABLE app_user_credential
(
user_id BIGINT NOT NULL REFERENCES app_user (id),
provider VARCHAR(255) NOT NULL,
credential VARCHAR(255) NOT NULL,
CONSTRAINT app_user_credential_pkey PRIMARY KEY (user_id, provider)
);