Public Access
Implemented user service
This commit is contained in:
1 parent
8af7170c46
commit
41ce5d93e9
27 files changed
+332
-36
No files matched your search
@@ -34,6 +34,10 @@
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-webmvc</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-cache</artifactId>
|
||||
</dependency>
|
||||
<!-- Rest Caching -->
|
||||
<dependency>
|
||||
<groupId>com.github.ben-manes.caffeine</groupId>
|
||||
|
||||
@@ -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<Object, Object> caffeineCacheBuilder() {
|
||||
return Caffeine.newBuilder()
|
||||
.initialCapacity(100)
|
||||
.maximumSize(500)
|
||||
.expireAfterAccess(10, TimeUnit.MINUTES)
|
||||
.recordStats();
|
||||
}
|
||||
}
|
||||
@@ -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<Object, Object> caffeineCacheBuilder() {
|
||||
return Caffeine.newBuilder()
|
||||
.initialCapacity(100)
|
||||
.maximumSize(500)
|
||||
.expireAfterAccess(1, TimeUnit.SECONDS)
|
||||
.recordStats();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
+1
-1
@@ -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;
|
||||
+1
-1
@@ -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) {
|
||||
}
|
||||
+1
-1
@@ -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.
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<User> findByEmail(String email);
|
||||
}
|
||||
+7
-10
@@ -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<User> findByEmail(String email) {
|
||||
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
|
||||
CriteriaQuery<UserEntity> query = cb.createQuery(UserEntity.class);
|
||||
Root<UserEntity> 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<UserEntity> typedQuery = entityManager.createQuery(query);
|
||||
TypedQuery<MinimalUserEntity> typedQuery = entityManager.createQuery(userEntityCriteriaQuery);
|
||||
|
||||
return typedQuery.getResultStream()
|
||||
.findFirst()
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
|
||||
@@ -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<UserProfileResponse> 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));
|
||||
}
|
||||
}
|
||||
@@ -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<ErrorResponse> 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<ErrorResponse> 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<ErrorResponse> clientError(UserNotFoundException ex) {
|
||||
var body = new ErrorResponse("USER_NOT_FOUND", ex.getMessage());
|
||||
return response400(body);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
package auth_service.domain.user.controller.dto.response;
|
||||
|
||||
public record ErrorResponse(String code, String message) {
|
||||
}
|
||||
+7
@@ -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) {
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package auth_service.domain.user.exception;
|
||||
|
||||
public class UserNotFoundException extends RuntimeException {
|
||||
public UserNotFoundException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
+3
-2
@@ -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;
|
||||
|
||||
@@ -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<User> 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<UserEntity> typedQuery = entityManager.createQuery(userEntityCriteriaQuery);
|
||||
|
||||
return typedQuery.getResultStream()
|
||||
.findFirst()
|
||||
.map(EntityMapper::mapToModel);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
package auth_service.domain.login.persistence.entities;
|
||||
package auth_service.domain.user.persistence.entities;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.*;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user