From 41ce5d93e989758b3350380896ccc991c45a993d Mon Sep 17 00:00:00 2001 From: Lobo Date: Sat, 4 Apr 2026 09:58:09 +0200 Subject: [PATCH] Implemented user service --- pom.xml | 4 ++ .../configuration/CacheConfig.java | 37 ++++++++++++++++ .../configuration/DevCacheConfig.java | 37 ++++++++++++++++ .../login/controller/LoginController.java | 4 +- .../controller/LoginExceptionController.java | 2 +- .../{dtos => dto}/request/LoginRequest.java | 2 +- .../{dtos => dto}/response/ErrorResponse.java | 2 +- .../{dtos => dto}/response/LoginResponse.java | 2 +- .../login/controller/mapper/ApiMapper.java | 2 +- .../persistence/MinimalUserRepository.java | 18 ++++++++ ...pl.java => MinimalUserRepositoryImpl.java} | 17 ++++---- .../entities/MinimalUserEntity.java | 30 +++++++++++++ .../persistence/mapper/EntityMapper.java | 4 +- .../domain/login/service/LoginService.java | 6 +-- .../user/controller/UserController.java | 22 +++++----- .../controller/UserExceptionController.java | 42 +++++++++++++++++++ .../dto/response/ErrorResponse.java | 4 ++ .../dto/response/UserProfileResponse.java | 7 ++++ .../user/controller/mapper/ApiMapper.java | 17 ++++++++ .../user/exception/UserNotFoundException.java | 7 ++++ .../auth_service/domain/user/model/User.java | 12 ++++++ .../persistence/UserRepository.java | 5 ++- .../user/persistence/UserRepositoryImpl.java | 33 +++++++++++++++ .../persistence/entities/UserEntity.java | 2 +- .../user/persistence/mapper/EntityMapper.java | 22 ++++++++++ .../domain/user/service/UserService.java | 26 ++++++++++++ src/main/resources/application.yaml | 2 + 27 files changed, 332 insertions(+), 36 deletions(-) create mode 100644 src/main/java/auth_service/configuration/CacheConfig.java create mode 100644 src/main/java/auth_service/configuration/DevCacheConfig.java rename src/main/java/auth_service/domain/login/controller/{dtos => dto}/request/LoginRequest.java (81%) rename src/main/java/auth_service/domain/login/controller/{dtos => dto}/response/ErrorResponse.java (50%) rename src/main/java/auth_service/domain/login/controller/{dtos => dto}/response/LoginResponse.java (73%) create mode 100644 src/main/java/auth_service/domain/login/persistence/MinimalUserRepository.java rename src/main/java/auth_service/domain/login/persistence/{UserRepositoryImpl.java => MinimalUserRepositoryImpl.java} (53%) create mode 100644 src/main/java/auth_service/domain/login/persistence/entities/MinimalUserEntity.java create mode 100644 src/main/java/auth_service/domain/user/controller/UserExceptionController.java create mode 100644 src/main/java/auth_service/domain/user/controller/dto/response/ErrorResponse.java create mode 100644 src/main/java/auth_service/domain/user/controller/dto/response/UserProfileResponse.java create mode 100644 src/main/java/auth_service/domain/user/controller/mapper/ApiMapper.java create mode 100644 src/main/java/auth_service/domain/user/exception/UserNotFoundException.java create mode 100644 src/main/java/auth_service/domain/user/model/User.java rename src/main/java/auth_service/domain/{login => user}/persistence/UserRepository.java (79%) create mode 100644 src/main/java/auth_service/domain/user/persistence/UserRepositoryImpl.java rename src/main/java/auth_service/domain/{login => user}/persistence/entities/UserEntity.java (90%) create mode 100644 src/main/java/auth_service/domain/user/persistence/mapper/EntityMapper.java create mode 100644 src/main/java/auth_service/domain/user/service/UserService.java diff --git a/pom.xml b/pom.xml index af21ca3..521ef2a 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,10 @@ org.springframework.boot spring-boot-starter-webmvc + + org.springframework.boot + spring-boot-starter-cache + com.github.ben-manes.caffeine diff --git a/src/main/java/auth_service/configuration/CacheConfig.java b/src/main/java/auth_service/configuration/CacheConfig.java new file mode 100644 index 0000000..eb43847 --- /dev/null +++ b/src/main/java/auth_service/configuration/CacheConfig.java @@ -0,0 +1,37 @@ +package auth_service.configuration; + +import com.github.benmanes.caffeine.cache.Caffeine; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.caffeine.CaffeineCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +import java.util.concurrent.TimeUnit; + +/** + * Global configuration to enable Spring Boot caching (backed by Caffeine). + */ +@Configuration +@EnableCaching +@Profile("!dev") +@Slf4j +public class CacheConfig { + @Bean + public CacheManager cacheManager() { + log.info("########### Productive cache active ###########"); + CaffeineCacheManager cacheManager = new CaffeineCacheManager("usersByEmail"); + cacheManager.setCaffeine(caffeineCacheBuilder()); + return cacheManager; + } + + Caffeine caffeineCacheBuilder() { + return Caffeine.newBuilder() + .initialCapacity(100) + .maximumSize(500) + .expireAfterAccess(10, TimeUnit.MINUTES) + .recordStats(); + } +} \ No newline at end of file diff --git a/src/main/java/auth_service/configuration/DevCacheConfig.java b/src/main/java/auth_service/configuration/DevCacheConfig.java new file mode 100644 index 0000000..a20aff0 --- /dev/null +++ b/src/main/java/auth_service/configuration/DevCacheConfig.java @@ -0,0 +1,37 @@ +package auth_service.configuration; + +import com.github.benmanes.caffeine.cache.Caffeine; +import lombok.extern.slf4j.Slf4j; +import org.springframework.cache.CacheManager; +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.cache.caffeine.CaffeineCacheManager; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Profile; + +import java.util.concurrent.TimeUnit; + +/** + * Global configuration to enable Spring Boot caching (backed by Caffeine). + */ +@Configuration +@EnableCaching +@Profile("dev") +@Slf4j +public class DevCacheConfig { + @Bean + public CacheManager cacheManager() { + log.error("########### DEVELOPMENT CACHE ACTIVE ###########"); + CaffeineCacheManager cacheManager = new CaffeineCacheManager("usersByEmail"); + cacheManager.setCaffeine(caffeineCacheBuilder()); + return cacheManager; + } + + Caffeine caffeineCacheBuilder() { + return Caffeine.newBuilder() + .initialCapacity(100) + .maximumSize(500) + .expireAfterAccess(1, TimeUnit.SECONDS) + .recordStats(); + } +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/login/controller/LoginController.java b/src/main/java/auth_service/domain/login/controller/LoginController.java index 4042178..33e41a7 100644 --- a/src/main/java/auth_service/domain/login/controller/LoginController.java +++ b/src/main/java/auth_service/domain/login/controller/LoginController.java @@ -1,7 +1,7 @@ package auth_service.domain.login.controller; -import auth_service.domain.login.controller.dtos.request.LoginRequest; -import auth_service.domain.login.controller.dtos.response.LoginResponse; +import auth_service.domain.login.controller.dto.request.LoginRequest; +import auth_service.domain.login.controller.dto.response.LoginResponse; import auth_service.domain.login.controller.mapper.ApiMapper; import auth_service.domain.login.service.LoginService; import io.micrometer.core.annotation.Timed; diff --git a/src/main/java/auth_service/domain/login/controller/LoginExceptionController.java b/src/main/java/auth_service/domain/login/controller/LoginExceptionController.java index fb82b4c..d41eabc 100644 --- a/src/main/java/auth_service/domain/login/controller/LoginExceptionController.java +++ b/src/main/java/auth_service/domain/login/controller/LoginExceptionController.java @@ -1,6 +1,6 @@ package auth_service.domain.login.controller; -import auth_service.domain.login.controller.dtos.response.ErrorResponse; +import auth_service.domain.login.controller.dto.response.ErrorResponse; import auth_service.domain.login.exception.InvalidCredentialsException; import auth_service.domain.login.exception.UserNotFoundException; import lombok.NonNull; diff --git a/src/main/java/auth_service/domain/login/controller/dtos/request/LoginRequest.java b/src/main/java/auth_service/domain/login/controller/dto/request/LoginRequest.java similarity index 81% rename from src/main/java/auth_service/domain/login/controller/dtos/request/LoginRequest.java rename to src/main/java/auth_service/domain/login/controller/dto/request/LoginRequest.java index bdbd029..3826aa0 100644 --- a/src/main/java/auth_service/domain/login/controller/dtos/request/LoginRequest.java +++ b/src/main/java/auth_service/domain/login/controller/dto/request/LoginRequest.java @@ -1,4 +1,4 @@ -package auth_service.domain.login.controller.dtos.request; +package auth_service.domain.login.controller.dto.request; import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; diff --git a/src/main/java/auth_service/domain/login/controller/dtos/response/ErrorResponse.java b/src/main/java/auth_service/domain/login/controller/dto/response/ErrorResponse.java similarity index 50% rename from src/main/java/auth_service/domain/login/controller/dtos/response/ErrorResponse.java rename to src/main/java/auth_service/domain/login/controller/dto/response/ErrorResponse.java index e4dad29..2ef4c63 100644 --- a/src/main/java/auth_service/domain/login/controller/dtos/response/ErrorResponse.java +++ b/src/main/java/auth_service/domain/login/controller/dto/response/ErrorResponse.java @@ -1,4 +1,4 @@ -package auth_service.domain.login.controller.dtos.response; +package auth_service.domain.login.controller.dto.response; public record ErrorResponse(String code, String message) { } \ No newline at end of file diff --git a/src/main/java/auth_service/domain/login/controller/dtos/response/LoginResponse.java b/src/main/java/auth_service/domain/login/controller/dto/response/LoginResponse.java similarity index 73% rename from src/main/java/auth_service/domain/login/controller/dtos/response/LoginResponse.java rename to src/main/java/auth_service/domain/login/controller/dto/response/LoginResponse.java index d4c756f..1099dde 100644 --- a/src/main/java/auth_service/domain/login/controller/dtos/response/LoginResponse.java +++ b/src/main/java/auth_service/domain/login/controller/dto/response/LoginResponse.java @@ -1,4 +1,4 @@ -package auth_service.domain.login.controller.dtos.response; +package auth_service.domain.login.controller.dto.response; /** * DTO representing the outgoing login response. diff --git a/src/main/java/auth_service/domain/login/controller/mapper/ApiMapper.java b/src/main/java/auth_service/domain/login/controller/mapper/ApiMapper.java index 4a59ff1..d17ebf8 100644 --- a/src/main/java/auth_service/domain/login/controller/mapper/ApiMapper.java +++ b/src/main/java/auth_service/domain/login/controller/mapper/ApiMapper.java @@ -1,6 +1,6 @@ package auth_service.domain.login.controller.mapper; -import auth_service.domain.login.controller.dtos.request.LoginRequest; +import auth_service.domain.login.controller.dto.request.LoginRequest; import auth_service.domain.login.model.User; public abstract class ApiMapper { diff --git a/src/main/java/auth_service/domain/login/persistence/MinimalUserRepository.java b/src/main/java/auth_service/domain/login/persistence/MinimalUserRepository.java new file mode 100644 index 0000000..9af4592 --- /dev/null +++ b/src/main/java/auth_service/domain/login/persistence/MinimalUserRepository.java @@ -0,0 +1,18 @@ +package auth_service.domain.login.persistence; + +import auth_service.domain.login.model.User; + +import java.util.Optional; + +/** + * Repository for database operations regarding the User entity. + */ +public interface MinimalUserRepository { + /** + * Finds a user by their email address. + * * @param email The email to search for + * + * @return An Optional containing the user if found + */ + Optional findByEmail(String email); +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/login/persistence/UserRepositoryImpl.java b/src/main/java/auth_service/domain/login/persistence/MinimalUserRepositoryImpl.java similarity index 53% rename from src/main/java/auth_service/domain/login/persistence/UserRepositoryImpl.java rename to src/main/java/auth_service/domain/login/persistence/MinimalUserRepositoryImpl.java index f3268b0..0bbdf7d 100644 --- a/src/main/java/auth_service/domain/login/persistence/UserRepositoryImpl.java +++ b/src/main/java/auth_service/domain/login/persistence/MinimalUserRepositoryImpl.java @@ -1,33 +1,30 @@ package auth_service.domain.login.persistence; import auth_service.domain.login.model.User; -import auth_service.domain.login.persistence.entities.UserEntity; +import auth_service.domain.login.persistence.entities.MinimalUserEntity; import auth_service.domain.login.persistence.mapper.EntityMapper; import jakarta.persistence.EntityManager; import jakarta.persistence.PersistenceContext; import jakarta.persistence.TypedQuery; -import jakarta.persistence.criteria.CriteriaBuilder; -import jakarta.persistence.criteria.CriteriaQuery; -import jakarta.persistence.criteria.Root; import org.springframework.stereotype.Repository; import java.util.Optional; @Repository -public class UserRepositoryImpl implements UserRepository { +public class MinimalUserRepositoryImpl implements MinimalUserRepository { @PersistenceContext private EntityManager entityManager; @Override public Optional findByEmail(String email) { - CriteriaBuilder cb = entityManager.getCriteriaBuilder(); - CriteriaQuery query = cb.createQuery(UserEntity.class); - Root user = query.from(UserEntity.class); + var criteriaBuilder = entityManager.getCriteriaBuilder(); + var userEntityCriteriaQuery = criteriaBuilder.createQuery(MinimalUserEntity.class); + var userEntity = userEntityCriteriaQuery.from(MinimalUserEntity.class); // Build the query: SELECT u FROM UserEntity u WHERE u.email = :email - query.select(user).where(cb.equal(user.get("email"), email)); + userEntityCriteriaQuery.select(userEntity).where(criteriaBuilder.equal(userEntity.get("email"), email)); - TypedQuery typedQuery = entityManager.createQuery(query); + TypedQuery typedQuery = entityManager.createQuery(userEntityCriteriaQuery); return typedQuery.getResultStream() .findFirst() diff --git a/src/main/java/auth_service/domain/login/persistence/entities/MinimalUserEntity.java b/src/main/java/auth_service/domain/login/persistence/entities/MinimalUserEntity.java new file mode 100644 index 0000000..28532ba --- /dev/null +++ b/src/main/java/auth_service/domain/login/persistence/entities/MinimalUserEntity.java @@ -0,0 +1,30 @@ +package auth_service.domain.login.persistence.entities; + +import jakarta.persistence.*; +import lombok.*; + +/** + * Represents a user entity mapped to the 'users' table in PostgreSQL. + */ +@Entity +@Table(name = "users") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class MinimalUserEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(unique = true, nullable = false) + private String email; + + @Column(nullable = false) + private String password; + + @Column(nullable = false) + private boolean enabled; +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/login/persistence/mapper/EntityMapper.java b/src/main/java/auth_service/domain/login/persistence/mapper/EntityMapper.java index 47e84ed..1221fd8 100644 --- a/src/main/java/auth_service/domain/login/persistence/mapper/EntityMapper.java +++ b/src/main/java/auth_service/domain/login/persistence/mapper/EntityMapper.java @@ -1,14 +1,14 @@ package auth_service.domain.login.persistence.mapper; import auth_service.domain.login.model.User; -import auth_service.domain.login.persistence.entities.UserEntity; +import auth_service.domain.login.persistence.entities.MinimalUserEntity; public abstract class EntityMapper { private EntityMapper() { } - public static User mapToModel(UserEntity entity) { + public static User mapToModel(MinimalUserEntity entity) { User model = User.builder().build(); if (entity != null) { diff --git a/src/main/java/auth_service/domain/login/service/LoginService.java b/src/main/java/auth_service/domain/login/service/LoginService.java index eded0fa..2451c4e 100644 --- a/src/main/java/auth_service/domain/login/service/LoginService.java +++ b/src/main/java/auth_service/domain/login/service/LoginService.java @@ -4,7 +4,7 @@ import auth_service.configuration.security.JwtService; import auth_service.domain.login.exception.InvalidCredentialsException; import auth_service.domain.login.exception.UserNotFoundException; import auth_service.domain.login.model.User; -import auth_service.domain.login.persistence.UserRepository; +import auth_service.domain.login.persistence.MinimalUserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; @@ -14,12 +14,12 @@ import org.springframework.stereotype.Service; @RequiredArgsConstructor @Slf4j public class LoginService { - private final UserRepository userRepository; + private final MinimalUserRepository minimalUserRepository; private final PasswordEncoder passwordEncoder; private final JwtService jwtService; public String login(User userToLogin) { - var userEntity = userRepository.findByEmail(userToLogin.getEmail()).orElseThrow(() -> new UserNotFoundException("User not found with given email")); + var userEntity = minimalUserRepository.findByEmail(userToLogin.getEmail()).orElseThrow(() -> new UserNotFoundException("User not found with given email")); log.info("Login attempt for user {}", passwordEncoder.encode(userToLogin.getEncryptedPassword()) + " " + userEntity.getEncryptedPassword()); diff --git a/src/main/java/auth_service/domain/user/controller/UserController.java b/src/main/java/auth_service/domain/user/controller/UserController.java index 7fb4646..df3f1d6 100644 --- a/src/main/java/auth_service/domain/user/controller/UserController.java +++ b/src/main/java/auth_service/domain/user/controller/UserController.java @@ -1,6 +1,10 @@ package auth_service.domain.user.controller; +import auth_service.domain.user.controller.dto.response.UserProfileResponse; +import auth_service.domain.user.controller.mapper.ApiMapper; +import auth_service.domain.user.service.UserService; import io.micrometer.core.annotation.Timed; +import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -13,8 +17,11 @@ import java.security.Principal; */ @RestController @RequestMapping("/api/users") +@RequiredArgsConstructor public class UserController { + private final UserService userService; + /** * Version 1 of the 'me' endpoint, accessed via /api/users/me?v=1 * * @return The user's dashboard data @@ -23,16 +30,9 @@ public class UserController { @Timed(value = "user.me.v1.requests.time", description = "Time taken to process v1 user profile requests") public ResponseEntity getCurrentUserV1(Principal principal) { - // principal.getName() enthält die E-Mail, die der Türsteher aus dem Token gelesen hat! - String authenticatedEmail = principal.getName(); - - return ResponseEntity.ok(new UserProfileResponse( - authenticatedEmail, - "USER", - "Welcome to the v1 Dashboard, " + authenticatedEmail + "!" - )); - } - - public record UserProfileResponse(String email, String role, String dashboardMessage) { + // this is the email from the login, if a user is not authenticated, it will not even come to this point. + var authenticatedEmail = principal.getName(); + var profileResponse = this.userService.getUserByEmail(authenticatedEmail); + return ResponseEntity.ok(ApiMapper.toApi(profileResponse)); } } \ No newline at end of file diff --git a/src/main/java/auth_service/domain/user/controller/UserExceptionController.java b/src/main/java/auth_service/domain/user/controller/UserExceptionController.java new file mode 100644 index 0000000..3cece3a --- /dev/null +++ b/src/main/java/auth_service/domain/user/controller/UserExceptionController.java @@ -0,0 +1,42 @@ +package auth_service.domain.user.controller; + +import auth_service.domain.user.controller.dto.response.ErrorResponse; +import auth_service.domain.user.exception.UserNotFoundException; +import lombok.NonNull; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.annotation.Order; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +@Order(value = 1) +@Slf4j +public class UserExceptionController { + private static @NonNull ResponseEntity response500(ErrorResponse body) { + log.error(body.message(), body.code()); + return ResponseEntity.status(500) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON) + .body(body); + } + + private static @NonNull ResponseEntity response400(ErrorResponse body) { + return ResponseEntity.status(400) + .contentType(org.springframework.http.MediaType.APPLICATION_JSON) + .body(body); + } + + /* + ######## CLIENT ERRORS (500-599) + */ + + /* + ######## CLIENT ERRORS (400-499) + */ + @ExceptionHandler(UserNotFoundException.class) + public ResponseEntity clientError(UserNotFoundException ex) { + var body = new ErrorResponse("USER_NOT_FOUND", ex.getMessage()); + return response400(body); + } + +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/user/controller/dto/response/ErrorResponse.java b/src/main/java/auth_service/domain/user/controller/dto/response/ErrorResponse.java new file mode 100644 index 0000000..13d4488 --- /dev/null +++ b/src/main/java/auth_service/domain/user/controller/dto/response/ErrorResponse.java @@ -0,0 +1,4 @@ +package auth_service.domain.user.controller.dto.response; + +public record ErrorResponse(String code, String message) { +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/user/controller/dto/response/UserProfileResponse.java b/src/main/java/auth_service/domain/user/controller/dto/response/UserProfileResponse.java new file mode 100644 index 0000000..1b347dc --- /dev/null +++ b/src/main/java/auth_service/domain/user/controller/dto/response/UserProfileResponse.java @@ -0,0 +1,7 @@ +package auth_service.domain.user.controller.dto.response; + +import lombok.Builder; + +@Builder +public record UserProfileResponse(long id, String email, String role) { +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/user/controller/mapper/ApiMapper.java b/src/main/java/auth_service/domain/user/controller/mapper/ApiMapper.java new file mode 100644 index 0000000..fc5c63d --- /dev/null +++ b/src/main/java/auth_service/domain/user/controller/mapper/ApiMapper.java @@ -0,0 +1,17 @@ +package auth_service.domain.user.controller.mapper; + + +import auth_service.domain.user.controller.dto.response.UserProfileResponse; +import auth_service.domain.user.model.User; + +public abstract class ApiMapper { + private ApiMapper() { + } + + public static UserProfileResponse toApi(User user) { + return UserProfileResponse.builder() + .id(user.getId()) + .email(user.getEmail()) + .role("USER").build(); + } +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/user/exception/UserNotFoundException.java b/src/main/java/auth_service/domain/user/exception/UserNotFoundException.java new file mode 100644 index 0000000..b733496 --- /dev/null +++ b/src/main/java/auth_service/domain/user/exception/UserNotFoundException.java @@ -0,0 +1,7 @@ +package auth_service.domain.user.exception; + +public class UserNotFoundException extends RuntimeException { + public UserNotFoundException(String message) { + super(message); + } +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/user/model/User.java b/src/main/java/auth_service/domain/user/model/User.java new file mode 100644 index 0000000..d13cb92 --- /dev/null +++ b/src/main/java/auth_service/domain/user/model/User.java @@ -0,0 +1,12 @@ +package auth_service.domain.user.model; + +import lombok.Builder; +import lombok.Data; + +@Builder +@Data +public class User { + private long id; + private String email; + private String encryptedPassword; +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/login/persistence/UserRepository.java b/src/main/java/auth_service/domain/user/persistence/UserRepository.java similarity index 79% rename from src/main/java/auth_service/domain/login/persistence/UserRepository.java rename to src/main/java/auth_service/domain/user/persistence/UserRepository.java index 626c313..95af5d7 100644 --- a/src/main/java/auth_service/domain/login/persistence/UserRepository.java +++ b/src/main/java/auth_service/domain/user/persistence/UserRepository.java @@ -1,6 +1,7 @@ -package auth_service.domain.login.persistence; +package auth_service.domain.user.persistence; -import auth_service.domain.login.model.User; + +import auth_service.domain.user.model.User; import java.util.Optional; diff --git a/src/main/java/auth_service/domain/user/persistence/UserRepositoryImpl.java b/src/main/java/auth_service/domain/user/persistence/UserRepositoryImpl.java new file mode 100644 index 0000000..1d5c545 --- /dev/null +++ b/src/main/java/auth_service/domain/user/persistence/UserRepositoryImpl.java @@ -0,0 +1,33 @@ +package auth_service.domain.user.persistence; + +import auth_service.domain.user.model.User; +import auth_service.domain.user.persistence.entities.UserEntity; +import auth_service.domain.user.persistence.mapper.EntityMapper; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import jakarta.persistence.TypedQuery; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public class UserRepositoryImpl implements UserRepository { + @PersistenceContext + private EntityManager entityManager; + + @Override + public Optional findByEmail(String email) { + var criteriaBuilder = entityManager.getCriteriaBuilder(); + var userEntityCriteriaQuery = criteriaBuilder.createQuery(UserEntity.class); + var userEntity = userEntityCriteriaQuery.from(UserEntity.class); + + // Build the query: SELECT u FROM UserEntity u WHERE u.email = :email + userEntityCriteriaQuery.select(userEntity).where(criteriaBuilder.equal(userEntity.get("email"), email)); + + TypedQuery typedQuery = entityManager.createQuery(userEntityCriteriaQuery); + + return typedQuery.getResultStream() + .findFirst() + .map(EntityMapper::mapToModel); + } +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/login/persistence/entities/UserEntity.java b/src/main/java/auth_service/domain/user/persistence/entities/UserEntity.java similarity index 90% rename from src/main/java/auth_service/domain/login/persistence/entities/UserEntity.java rename to src/main/java/auth_service/domain/user/persistence/entities/UserEntity.java index 0c72ec2..49ec533 100644 --- a/src/main/java/auth_service/domain/login/persistence/entities/UserEntity.java +++ b/src/main/java/auth_service/domain/user/persistence/entities/UserEntity.java @@ -1,4 +1,4 @@ -package auth_service.domain.login.persistence.entities; +package auth_service.domain.user.persistence.entities; import jakarta.persistence.*; import lombok.*; diff --git a/src/main/java/auth_service/domain/user/persistence/mapper/EntityMapper.java b/src/main/java/auth_service/domain/user/persistence/mapper/EntityMapper.java new file mode 100644 index 0000000..8f631fa --- /dev/null +++ b/src/main/java/auth_service/domain/user/persistence/mapper/EntityMapper.java @@ -0,0 +1,22 @@ +package auth_service.domain.user.persistence.mapper; + +import auth_service.domain.user.model.User; +import auth_service.domain.user.persistence.entities.UserEntity; + +public abstract class EntityMapper { + + private EntityMapper() { + } + + public static User mapToModel(UserEntity entity) { + User model = User.builder().build(); + + if (entity != null) { + model.setId(entity.getId()); + model.setEmail(entity.getEmail()); + model.setEncryptedPassword(entity.getPassword()); + } + + return model; + } +} \ No newline at end of file diff --git a/src/main/java/auth_service/domain/user/service/UserService.java b/src/main/java/auth_service/domain/user/service/UserService.java new file mode 100644 index 0000000..8993540 --- /dev/null +++ b/src/main/java/auth_service/domain/user/service/UserService.java @@ -0,0 +1,26 @@ +package auth_service.domain.user.service; + +import auth_service.domain.user.exception.UserNotFoundException; +import auth_service.domain.user.model.User; +import auth_service.domain.user.persistence.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class UserService { + + private final UserRepository userRepository; + + /** + * Fetches the user by email. Results are cached to ensure blazing-fast UI rendering. + * + * @param email The authenticated user's email + * @return Optional containing the User model if found + */ + @Cacheable(value = "usersByEmail", key = "#email") + public User getUserByEmail(String email) { + return userRepository.findByEmail(email).orElseThrow(() -> new UserNotFoundException("Could not find user with email " + email)); + } +} \ No newline at end of file diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index 1230820..60d82c2 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -6,6 +6,8 @@ spring: name: tbp-auth-service profiles: active: dev # Default profile for local startup + jpa: + open-in-view: false flyway: # Force Flyway to be enabled enabled: true