Started with:
Build, Test & Push Auth Service / quality-check (push) Successful in 19s
Build, Test & Push Auth Service / docker (push) Successful in 1m21s

- Registration logic for new players
- Extended User table with more information
This commit is contained in:
lobo committed 2026-04-04 17:11:13 +02:00
1 parent dbc76e6158
commit c224cbf679
22 files changed
+341 -5

No files matched your search

@@ -15,4 +15,6 @@ public interface MinimalUserRepository {
* @return An Optional containing the user if found
*/
Optional<User> findByEmail(String email);
void updateLoginDate(long userId);
}
@@ -8,6 +8,8 @@ import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;
import org.springframework.stereotype.Repository;
import java.time.Clock;
import java.time.LocalDateTime;
import java.util.Optional;
@Repository
@@ -30,4 +32,14 @@ public class MinimalUserRepositoryImpl implements MinimalUserRepository {
.findFirst()
.map(EntityMapper::mapToModel);
}
@Override
public void updateLoginDate(long userId) {
var userEntity = entityManager.find(MinimalUserEntity.class, userId);
if (userEntity != null) {
userEntity.setLastLoginDate(LocalDateTime.now(Clock.systemUTC()));
entityManager.flush();
}
}
}
@@ -3,6 +3,8 @@ package auth_service.domain.login.persistence.entities;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
/**
* Represents a user entity mapped to the 'users' table in PostgreSQL.
*/
@@ -27,4 +29,7 @@ public class MinimalUserEntity {
@Column(nullable = false)
private boolean enabled;
@Column(name = "last_login_date", nullable = false)
private LocalDateTime lastLoginDate;
}
@@ -22,12 +22,12 @@ public class LoginService {
* @return The authenticated User model
*/
public User login(User userToLogin) {
var userFromDatabase = minimalUserRepository.findByEmail(userToLogin.getEmail()).orElseThrow(() -> new UserNotFoundException("User not found with given email"));
var loginSuccessful = passwordEncoder.matches(userToLogin.getPassword(), userFromDatabase.getPassword());
var foundUser = minimalUserRepository.findByEmail(userToLogin.getEmail()).orElseThrow(() -> new UserNotFoundException("User not found with given email"));
var loginSuccessful = passwordEncoder.matches(userToLogin.getPassword(), foundUser.getPassword());
if (!loginSuccessful) {
throw new InvalidCredentialsException("Invalid credentials");
}
return userFromDatabase;
minimalUserRepository.updateLoginDate(foundUser.getId());
return foundUser;
}
}
@@ -0,0 +1,45 @@
package auth_service.domain.registration.controller;
import auth_service.domain.registration.controller.dto.response.ErrorResponse;
import auth_service.domain.registration.exception.EmailAlreadyInUseException;
import auth_service.domain.registration.exception.InvalidInvitationCodeException;
import auth_service.domain.registration.exception.SteamIdAlreadyRegisteredException;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
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 RegistrationExceptionController {
private static @NonNull ResponseEntity<ErrorResponse> response(ErrorResponse body, HttpStatus status) {
return ResponseEntity.status(status.value())
.contentType(org.springframework.http.MediaType.APPLICATION_JSON)
.body(body);
}
@ExceptionHandler(EmailAlreadyInUseException.class)
public ResponseEntity<ErrorResponse> clientError(EmailAlreadyInUseException ex) {
var body = new ErrorResponse("EMAIL_ALREADY_IN_USE", ex.getMessage());
return response(body, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(InvalidInvitationCodeException.class)
public ResponseEntity<ErrorResponse> clientError(InvalidInvitationCodeException ex) {
var body = new ErrorResponse("INVALID_INVITATION_CODE", ex.getMessage());
return response(body, HttpStatus.BAD_REQUEST);
}
@ExceptionHandler(SteamIdAlreadyRegisteredException.class)
public ResponseEntity<ErrorResponse> clientError(SteamIdAlreadyRegisteredException ex) {
var body = new ErrorResponse("STEAM_ID_ALREADY_IN_USE", ex.getMessage());
return response(body, HttpStatus.BAD_REQUEST);
}
}
@@ -0,0 +1,4 @@
package auth_service.domain.registration.controller.dto.response;
public record ErrorResponse(String code, String message) {
}
@@ -0,0 +1,7 @@
package auth_service.domain.registration.exception;
public class EmailAlreadyInUseException extends RuntimeException {
public EmailAlreadyInUseException(String message) {
super(message);
}
}
@@ -0,0 +1,7 @@
package auth_service.domain.registration.exception;
public class InvalidInvitationCodeException extends RuntimeException {
public InvalidInvitationCodeException(String message) {
super(message);
}
}
@@ -0,0 +1,7 @@
package auth_service.domain.registration.exception;
public class SteamIdAlreadyRegisteredException extends RuntimeException {
public SteamIdAlreadyRegisteredException(String message) {
super(message);
}
}
@@ -0,0 +1,7 @@
package auth_service.domain.registration.model;
public record RegisteredUser(
String id,
String email
) {
}
@@ -0,0 +1,14 @@
package auth_service.domain.registration.model;
import lombok.Builder;
import lombok.Data;
@Builder
@Data
public class Registration {
private String email;
private String rawPassword;
private String encryptedPassword;
private String inviteCode;
private long steamId;
}
@@ -0,0 +1,12 @@
package auth_service.domain.registration.persistence;
import auth_service.domain.registration.model.RegisteredUser;
import auth_service.domain.registration.model.Registration;
public interface RegisteredUserRepository {
boolean existsByEmail(String email);
boolean existsBySteamId(long steamId);
RegisteredUser save(Registration registration);
}
@@ -0,0 +1,23 @@
package auth_service.domain.registration.persistence;
import auth_service.domain.registration.model.RegisteredUser;
import auth_service.domain.registration.model.Registration;
import org.springframework.stereotype.Repository;
@Repository
public class RegisteredUserRepositoryImpl implements RegisteredUserRepository {
@Override
public boolean existsByEmail(String email) {
return false;
}
@Override
public boolean existsBySteamId(long steamId) {
return false;
}
@Override
public RegisteredUser save(Registration registration) {
return null;
}
}
@@ -0,0 +1,19 @@
package auth_service.domain.registration.service;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class CodeValidationService {
private static final String RANDOM_INVITATION_CODE = "Us*kksUuRpTE2CQRsv+^k0n)ksR}X}?#Fo2D4d8ZKG";
public boolean isCodeValid(String code) {
if (code == null || code.isEmpty()) {
return false;
}
return code.equals(RANDOM_INVITATION_CODE);
}
}
@@ -0,0 +1,39 @@
package auth_service.domain.registration.service;
import auth_service.domain.registration.exception.EmailAlreadyInUseException;
import auth_service.domain.registration.exception.InvalidInvitationCodeException;
import auth_service.domain.registration.exception.SteamIdAlreadyRegisteredException;
import auth_service.domain.registration.model.RegisteredUser;
import auth_service.domain.registration.model.Registration;
import auth_service.domain.registration.persistence.RegisteredUserRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class RegistrationService {
private final CodeValidationService codeValidationService;
private final RegisteredUserRepository registeredUserRepository;
private final PasswordEncoder passwordEncoder;
public RegisteredUser register(Registration registration) {
if (!codeValidationService.isCodeValid(registration.getInviteCode())) {
throw new InvalidInvitationCodeException("The provided invite code is invalid or has already been used.");
}
if (registeredUserRepository.existsByEmail(registration.getEmail())) {
throw new EmailAlreadyInUseException("The provided email address is already in use.");
}
if (registeredUserRepository.existsBySteamId(registration.getSteamId())) {
throw new SteamIdAlreadyRegisteredException("The steam id is already in use.");
}
var rawPassword = registration.getRawPassword();
registration.setEncryptedPassword(passwordEncoder.encode(rawPassword));
return registeredUserRepository.save(registration);
}
}
@@ -2,6 +2,8 @@ package auth_service.domain.user.controller.dto.response;
import lombok.Builder;
import java.time.LocalDateTime;
@Builder
public record UserProfileResponse(long id, String email, String role) {
public record UserProfileResponse(long id, String email, String role, LocalDateTime lastLogin, LocalDateTime createdDate, long steamId) {
}
@@ -12,6 +12,9 @@ public abstract class ApiMapper {
return UserProfileResponse.builder()
.id(user.getId())
.email(user.getEmail())
.createdDate(user.getCreatedDate())
.lastLogin(user.getLastLogin())
.steamId(user.getSteamId())
.role("USER").build();
}
}
@@ -3,10 +3,15 @@ package auth_service.domain.user.model;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
@Builder
@Data
public class User {
private long id;
private String email;
private String encryptedPassword;
private LocalDateTime lastLogin;
private LocalDateTime createdDate;
private long steamId;
}
@@ -3,6 +3,8 @@ package auth_service.domain.user.persistence.entities;
import jakarta.persistence.*;
import lombok.*;
import java.time.LocalDateTime;
/**
* Represents a user entity mapped to the 'users' table in PostgreSQL.
*/
@@ -27,4 +29,13 @@ public class UserEntity {
@Column(nullable = false)
private boolean enabled;
@Column(name = "steam_id", unique = true, nullable = false)
private Long steamId;
@Column(name = "registration_date", nullable = false)
private LocalDateTime registrationDate;
@Column(name = "last_login_date", nullable = false)
private LocalDateTime lastLoginDate;
}
@@ -15,6 +15,9 @@ public abstract class EntityMapper {
model.setId(entity.getId());
model.setEmail(entity.getEmail());
model.setEncryptedPassword(entity.getPassword());
model.setLastLogin(entity.getLastLoginDate());
model.setCreatedDate(entity.getRegistrationDate());
model.setSteamId(entity.getSteamId());
}
return model;
@@ -0,0 +1,7 @@
-- Create the users table
ALTER TABLE users
ADD COLUMN steam_id BIGINT,
ADD COLUMN registration_date TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
ADD COLUMN last_login_date TIMESTAMP;
ALTER TABLE users ADD CONSTRAINT uc_steam_id UNIQUE (steam_id);