diff --git a/src/main/java/alpl/backend/web/AplpBackendWebApplication.java b/src/main/java/aplp/backend/web/AplpBackendWebApplication.java similarity index 100% rename from src/main/java/alpl/backend/web/AplpBackendWebApplication.java rename to src/main/java/aplp/backend/web/AplpBackendWebApplication.java diff --git a/src/main/java/alpl/backend/web/common/api/GlobalExceptionHandler.java b/src/main/java/aplp/backend/web/common/api/GlobalExceptionHandler.java similarity index 100% rename from src/main/java/alpl/backend/web/common/api/GlobalExceptionHandler.java rename to src/main/java/aplp/backend/web/common/api/GlobalExceptionHandler.java diff --git a/src/main/java/alpl/backend/web/common/config/CommonConfig.java b/src/main/java/aplp/backend/web/common/config/CommonConfig.java similarity index 100% rename from src/main/java/alpl/backend/web/common/config/CommonConfig.java rename to src/main/java/aplp/backend/web/common/config/CommonConfig.java diff --git a/src/main/java/alpl/backend/web/common/config/OpenApiConfig.java b/src/main/java/aplp/backend/web/common/config/OpenApiConfig.java similarity index 100% rename from src/main/java/alpl/backend/web/common/config/OpenApiConfig.java rename to src/main/java/aplp/backend/web/common/config/OpenApiConfig.java diff --git a/src/main/java/alpl/backend/web/common/security/AuthenticatedUser.java b/src/main/java/aplp/backend/web/common/security/AuthenticatedUser.java similarity index 100% rename from src/main/java/alpl/backend/web/common/security/AuthenticatedUser.java rename to src/main/java/aplp/backend/web/common/security/AuthenticatedUser.java diff --git a/src/main/java/alpl/backend/web/common/security/CorsProperties.java b/src/main/java/aplp/backend/web/common/security/CorsProperties.java similarity index 100% rename from src/main/java/alpl/backend/web/common/security/CorsProperties.java rename to src/main/java/aplp/backend/web/common/security/CorsProperties.java diff --git a/src/main/java/alpl/backend/web/common/security/JwtAuthenticationFilter.java b/src/main/java/aplp/backend/web/common/security/JwtAuthenticationFilter.java similarity index 100% rename from src/main/java/alpl/backend/web/common/security/JwtAuthenticationFilter.java rename to src/main/java/aplp/backend/web/common/security/JwtAuthenticationFilter.java diff --git a/src/main/java/alpl/backend/web/common/security/JwtClaims.java b/src/main/java/aplp/backend/web/common/security/JwtClaims.java similarity index 100% rename from src/main/java/alpl/backend/web/common/security/JwtClaims.java rename to src/main/java/aplp/backend/web/common/security/JwtClaims.java diff --git a/src/main/java/alpl/backend/web/common/security/JwtProperties.java b/src/main/java/aplp/backend/web/common/security/JwtProperties.java similarity index 100% rename from src/main/java/alpl/backend/web/common/security/JwtProperties.java rename to src/main/java/aplp/backend/web/common/security/JwtProperties.java diff --git a/src/main/java/alpl/backend/web/common/security/JwtTokenProvider.java b/src/main/java/aplp/backend/web/common/security/JwtTokenProvider.java similarity index 100% rename from src/main/java/alpl/backend/web/common/security/JwtTokenProvider.java rename to src/main/java/aplp/backend/web/common/security/JwtTokenProvider.java diff --git a/src/main/java/alpl/backend/web/common/security/SecurityConfig.java b/src/main/java/aplp/backend/web/common/security/SecurityConfig.java similarity index 100% rename from src/main/java/alpl/backend/web/common/security/SecurityConfig.java rename to src/main/java/aplp/backend/web/common/security/SecurityConfig.java diff --git a/src/main/java/alpl/backend/web/common/security/SecurityErrorHandlers.java b/src/main/java/aplp/backend/web/common/security/SecurityErrorHandlers.java similarity index 100% rename from src/main/java/alpl/backend/web/common/security/SecurityErrorHandlers.java rename to src/main/java/aplp/backend/web/common/security/SecurityErrorHandlers.java diff --git a/src/main/java/alpl/backend/web/common/security/SecurityUtils.java b/src/main/java/aplp/backend/web/common/security/SecurityUtils.java similarity index 100% rename from src/main/java/alpl/backend/web/common/security/SecurityUtils.java rename to src/main/java/aplp/backend/web/common/security/SecurityUtils.java diff --git a/src/main/java/alpl/backend/web/common/web/RequestIdFilter.java b/src/main/java/aplp/backend/web/common/web/RequestIdFilter.java similarity index 100% rename from src/main/java/alpl/backend/web/common/web/RequestIdFilter.java rename to src/main/java/aplp/backend/web/common/web/RequestIdFilter.java diff --git a/src/main/java/alpl/backend/web/identity/api/controller/AuthController.java b/src/main/java/aplp/backend/web/identity/api/controller/AuthController.java similarity index 89% rename from src/main/java/alpl/backend/web/identity/api/controller/AuthController.java rename to src/main/java/aplp/backend/web/identity/api/controller/AuthController.java index b4078cb..1929024 100644 --- a/src/main/java/alpl/backend/web/identity/api/controller/AuthController.java +++ b/src/main/java/aplp/backend/web/identity/api/controller/AuthController.java @@ -26,12 +26,12 @@ public class AuthController { @PostMapping("/register") @ResponseStatus(HttpStatus.CREATED) public AuthResponse register(@Valid @RequestBody RegisterRequest request) { - return authService.register(request.email(), request.password(), request.displayName()); + return authService.register(request.username(), request.email(), request.password(), request.displayName()); } @PostMapping("/login") public AuthResponse login(@Valid @RequestBody LoginRequest request) { - return authService.login(request.email(), request.password()); + return authService.login(request.username(), request.password()); } @PostMapping("/refresh") diff --git a/src/main/java/alpl/backend/web/identity/application/dtos/AuthResponse.java b/src/main/java/aplp/backend/web/identity/application/dtos/AuthResponse.java similarity index 100% rename from src/main/java/alpl/backend/web/identity/application/dtos/AuthResponse.java rename to src/main/java/aplp/backend/web/identity/application/dtos/AuthResponse.java diff --git a/src/main/java/alpl/backend/web/identity/application/dtos/LoginRequest.java b/src/main/java/aplp/backend/web/identity/application/dtos/LoginRequest.java similarity index 58% rename from src/main/java/alpl/backend/web/identity/application/dtos/LoginRequest.java rename to src/main/java/aplp/backend/web/identity/application/dtos/LoginRequest.java index 0178012..5093c7c 100644 --- a/src/main/java/alpl/backend/web/identity/application/dtos/LoginRequest.java +++ b/src/main/java/aplp/backend/web/identity/application/dtos/LoginRequest.java @@ -1,10 +1,10 @@ package aplp.backend.web.identity.application.dtos; -import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; public record LoginRequest( - @NotBlank @Email(message = "must be a valid email") String email, + @NotBlank @Size(min = 3, max = 50) String username, @NotBlank String password ) { } diff --git a/src/main/java/alpl/backend/web/identity/application/dtos/RefreshTokenRequest.java b/src/main/java/aplp/backend/web/identity/application/dtos/RefreshTokenRequest.java similarity index 100% rename from src/main/java/alpl/backend/web/identity/application/dtos/RefreshTokenRequest.java rename to src/main/java/aplp/backend/web/identity/application/dtos/RefreshTokenRequest.java diff --git a/src/main/java/alpl/backend/web/identity/application/dtos/RegisterRequest.java b/src/main/java/aplp/backend/web/identity/application/dtos/RegisterRequest.java similarity index 88% rename from src/main/java/alpl/backend/web/identity/application/dtos/RegisterRequest.java rename to src/main/java/aplp/backend/web/identity/application/dtos/RegisterRequest.java index bfc5894..0dc54c4 100644 --- a/src/main/java/alpl/backend/web/identity/application/dtos/RegisterRequest.java +++ b/src/main/java/aplp/backend/web/identity/application/dtos/RegisterRequest.java @@ -5,6 +5,7 @@ import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.Size; public record RegisterRequest( + @NotBlank @Size(min = 3, max = 50) String username, @NotBlank @Email(message = "must be a valid email") @Size(max = 320) String email, @NotBlank @Size(min = 8, max = 128, message = "must be between 8 and 128 characters") String password, @NotBlank @Size(max = 100) String displayName diff --git a/src/main/java/alpl/backend/web/identity/application/mappers/AuthMapper.java b/src/main/java/aplp/backend/web/identity/application/mappers/AuthMapper.java similarity index 100% rename from src/main/java/alpl/backend/web/identity/application/mappers/AuthMapper.java rename to src/main/java/aplp/backend/web/identity/application/mappers/AuthMapper.java diff --git a/src/main/java/alpl/backend/web/identity/application/services/AuthService.java b/src/main/java/aplp/backend/web/identity/application/services/AuthService.java similarity index 90% rename from src/main/java/alpl/backend/web/identity/application/services/AuthService.java rename to src/main/java/aplp/backend/web/identity/application/services/AuthService.java index 0160950..f28a602 100644 --- a/src/main/java/alpl/backend/web/identity/application/services/AuthService.java +++ b/src/main/java/aplp/backend/web/identity/application/services/AuthService.java @@ -9,6 +9,7 @@ import aplp.backend.web.identity.application.dtos.AuthResponse; import aplp.backend.web.identity.application.mappers.AuthMapper; import aplp.backend.web.identity.domain.exceptions.EmailAlreadyExistsException; import aplp.backend.web.identity.domain.exceptions.InvalidCredentialsException; +import aplp.backend.web.identity.domain.exceptions.UsernameAlreadyExistsException; import aplp.backend.web.identity.domain.entities.RefreshToken; import aplp.backend.web.identity.domain.exceptions.RefreshTokenInvalidException; import aplp.backend.web.identity.domain.repositories.RefreshTokenRepository; @@ -53,22 +54,24 @@ public class AuthService { } @Transactional - public AuthResponse register(String email, String rawPassword, String displayName) { + public AuthResponse register(String username, String email, String rawPassword, String displayName) { Instant now = clock.instant(); String normalizedEmail = email.trim().toLowerCase(); if (userRepository.existsByEmail(normalizedEmail)) { throw new EmailAlreadyExistsException(normalizedEmail); } - User user = User.register(normalizedEmail, passwordEncoder.encode(rawPassword), displayName, now); + if (userRepository.existsByUsername(username)) { + throw new UsernameAlreadyExistsException(username); + } + User user = User.register(username, normalizedEmail, passwordEncoder.encode(rawPassword), displayName, now); User saved = userRepository.save(user); learnerProvisioner.provision(saved.id(), displayName); return issueTokens(saved); } @Transactional - public AuthResponse login(String email, String rawPassword) { - String normalizedEmail = email.trim().toLowerCase(); - User user = userRepository.findByEmail(normalizedEmail).orElseThrow(InvalidCredentialsException::new); + public AuthResponse login(String username, String rawPassword) { + User user = userRepository.findByUsername(username).orElseThrow(InvalidCredentialsException::new); if (!passwordEncoder.matches(rawPassword, user.passwordHash())) { throw new InvalidCredentialsException(); } diff --git a/src/main/java/alpl/backend/web/identity/domain/entities/RefreshToken.java b/src/main/java/aplp/backend/web/identity/domain/entities/RefreshToken.java similarity index 100% rename from src/main/java/alpl/backend/web/identity/domain/entities/RefreshToken.java rename to src/main/java/aplp/backend/web/identity/domain/entities/RefreshToken.java diff --git a/src/main/java/alpl/backend/web/identity/domain/entities/User.java b/src/main/java/aplp/backend/web/identity/domain/entities/User.java similarity index 76% rename from src/main/java/alpl/backend/web/identity/domain/entities/User.java rename to src/main/java/aplp/backend/web/identity/domain/entities/User.java index 16a194a..9538518 100644 --- a/src/main/java/alpl/backend/web/identity/domain/entities/User.java +++ b/src/main/java/aplp/backend/web/identity/domain/entities/User.java @@ -21,6 +21,9 @@ public class User { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column(nullable = false, unique = true, length = 50) + private String username; + @Column(nullable = false, unique = true, length = 320) private String email; @@ -43,9 +46,10 @@ public class User { protected User() { } - private User(Long id, String email, String passwordHash, String displayName, + private User(Long id, String username, String email, String passwordHash, String displayName, UserStatus status, Instant createdAt, Instant updatedAt) { this.id = id; + this.username = username; this.email = email; this.passwordHash = passwordHash; this.displayName = displayName; @@ -54,13 +58,13 @@ public class User { this.updatedAt = updatedAt; } - public static User register(String email, String passwordHash, String displayName, Instant now) { - return new User(null, normalizeEmail(email), passwordHash, displayName, UserStatus.ACTIVE, now, now); + public static User register(String username, String email, String passwordHash, String displayName, Instant now) { + return new User(null, username, normalizeEmail(email), passwordHash, displayName, UserStatus.ACTIVE, now, now); } - public static User reconstruct(Long id, String email, String passwordHash, String displayName, + public static User reconstruct(Long id, String username, String email, String passwordHash, String displayName, UserStatus status, Instant createdAt, Instant updatedAt) { - return new User(id, email, passwordHash, displayName, status, createdAt, updatedAt); + return new User(id, username, email, passwordHash, displayName, status, createdAt, updatedAt); } public void updatePasswordHash(String newHash, Instant now) { @@ -81,6 +85,10 @@ public class User { return id; } + public String username() { + return username; + } + public String email() { return email; } diff --git a/src/main/java/alpl/backend/web/identity/domain/entities/UserStatus.java b/src/main/java/aplp/backend/web/identity/domain/entities/UserStatus.java similarity index 100% rename from src/main/java/alpl/backend/web/identity/domain/entities/UserStatus.java rename to src/main/java/aplp/backend/web/identity/domain/entities/UserStatus.java diff --git a/src/main/java/alpl/backend/web/identity/domain/exceptions/EmailAlreadyExistsException.java b/src/main/java/aplp/backend/web/identity/domain/exceptions/EmailAlreadyExistsException.java similarity index 100% rename from src/main/java/alpl/backend/web/identity/domain/exceptions/EmailAlreadyExistsException.java rename to src/main/java/aplp/backend/web/identity/domain/exceptions/EmailAlreadyExistsException.java diff --git a/src/main/java/alpl/backend/web/identity/domain/exceptions/InvalidCredentialsException.java b/src/main/java/aplp/backend/web/identity/domain/exceptions/InvalidCredentialsException.java similarity index 100% rename from src/main/java/alpl/backend/web/identity/domain/exceptions/InvalidCredentialsException.java rename to src/main/java/aplp/backend/web/identity/domain/exceptions/InvalidCredentialsException.java diff --git a/src/main/java/alpl/backend/web/identity/domain/exceptions/RefreshTokenInvalidException.java b/src/main/java/aplp/backend/web/identity/domain/exceptions/RefreshTokenInvalidException.java similarity index 100% rename from src/main/java/alpl/backend/web/identity/domain/exceptions/RefreshTokenInvalidException.java rename to src/main/java/aplp/backend/web/identity/domain/exceptions/RefreshTokenInvalidException.java diff --git a/src/main/java/aplp/backend/web/identity/domain/exceptions/UsernameAlreadyExistsException.java b/src/main/java/aplp/backend/web/identity/domain/exceptions/UsernameAlreadyExistsException.java new file mode 100644 index 0000000..c71fc12 --- /dev/null +++ b/src/main/java/aplp/backend/web/identity/domain/exceptions/UsernameAlreadyExistsException.java @@ -0,0 +1,11 @@ +package aplp.backend.web.identity.domain.exceptions; + +import aplp.backend.core.common.exception.DomainException; +import aplp.backend.core.common.exception.ErrorCode; + +public class UsernameAlreadyExistsException extends DomainException { + + public UsernameAlreadyExistsException(String username) { + super(ErrorCode.USERNAME_ALREADY_EXISTS, "Username is already taken: " + username); + } +} diff --git a/src/main/java/alpl/backend/web/identity/domain/repositories/RefreshTokenRepository.java b/src/main/java/aplp/backend/web/identity/domain/repositories/RefreshTokenRepository.java similarity index 100% rename from src/main/java/alpl/backend/web/identity/domain/repositories/RefreshTokenRepository.java rename to src/main/java/aplp/backend/web/identity/domain/repositories/RefreshTokenRepository.java diff --git a/src/main/java/alpl/backend/web/identity/domain/repositories/UserRepository.java b/src/main/java/aplp/backend/web/identity/domain/repositories/UserRepository.java similarity index 76% rename from src/main/java/alpl/backend/web/identity/domain/repositories/UserRepository.java rename to src/main/java/aplp/backend/web/identity/domain/repositories/UserRepository.java index caad2e3..dffb166 100644 --- a/src/main/java/alpl/backend/web/identity/domain/repositories/UserRepository.java +++ b/src/main/java/aplp/backend/web/identity/domain/repositories/UserRepository.java @@ -8,9 +8,13 @@ public interface UserRepository { Optional findByEmail(String email); + Optional findByUsername(String username); + Optional findById(Long id); boolean existsByEmail(String email); + boolean existsByUsername(String username); + User save(User user); } diff --git a/src/main/java/alpl/backend/web/identity/infrastructure/persistence/RefreshTokenJpaRepository.java b/src/main/java/aplp/backend/web/identity/infrastructure/persistence/RefreshTokenJpaRepository.java similarity index 100% rename from src/main/java/alpl/backend/web/identity/infrastructure/persistence/RefreshTokenJpaRepository.java rename to src/main/java/aplp/backend/web/identity/infrastructure/persistence/RefreshTokenJpaRepository.java diff --git a/src/main/java/alpl/backend/web/identity/infrastructure/persistence/RefreshTokenRepositoryImpl.java b/src/main/java/aplp/backend/web/identity/infrastructure/persistence/RefreshTokenRepositoryImpl.java similarity index 100% rename from src/main/java/alpl/backend/web/identity/infrastructure/persistence/RefreshTokenRepositoryImpl.java rename to src/main/java/aplp/backend/web/identity/infrastructure/persistence/RefreshTokenRepositoryImpl.java diff --git a/src/main/java/alpl/backend/web/identity/infrastructure/persistence/UserJpaRepository.java b/src/main/java/aplp/backend/web/identity/infrastructure/persistence/UserJpaRepository.java similarity index 76% rename from src/main/java/alpl/backend/web/identity/infrastructure/persistence/UserJpaRepository.java rename to src/main/java/aplp/backend/web/identity/infrastructure/persistence/UserJpaRepository.java index ea21d3a..8325be4 100644 --- a/src/main/java/alpl/backend/web/identity/infrastructure/persistence/UserJpaRepository.java +++ b/src/main/java/aplp/backend/web/identity/infrastructure/persistence/UserJpaRepository.java @@ -7,5 +7,9 @@ public interface UserJpaRepository extends JpaRepository { boolean existsByEmail(String email); + boolean existsByUsername(String username); + java.util.Optional findByEmail(String email); + + java.util.Optional findByUsername(String username); } \ No newline at end of file diff --git a/src/main/java/alpl/backend/web/identity/infrastructure/persistence/UserRepositoryImpl.java b/src/main/java/aplp/backend/web/identity/infrastructure/persistence/UserRepositoryImpl.java similarity index 78% rename from src/main/java/alpl/backend/web/identity/infrastructure/persistence/UserRepositoryImpl.java rename to src/main/java/aplp/backend/web/identity/infrastructure/persistence/UserRepositoryImpl.java index 83da823..aaf332d 100644 --- a/src/main/java/alpl/backend/web/identity/infrastructure/persistence/UserRepositoryImpl.java +++ b/src/main/java/aplp/backend/web/identity/infrastructure/persistence/UserRepositoryImpl.java @@ -20,6 +20,11 @@ public class UserRepositoryImpl implements UserRepository { return jpaRepository.findByEmail(email); } + @Override + public Optional findByUsername(String username) { + return jpaRepository.findByUsername(username); + } + @Override public Optional findById(Long id) { return jpaRepository.findById(id); @@ -30,6 +35,11 @@ public class UserRepositoryImpl implements UserRepository { return jpaRepository.existsByEmail(email); } + @Override + public boolean existsByUsername(String username) { + return jpaRepository.existsByUsername(username); + } + @Override public User save(User user) { return jpaRepository.save(user); diff --git a/src/main/java/alpl/backend/web/learner/api/controller/LearnerController.java b/src/main/java/aplp/backend/web/learner/api/controller/LearnerController.java similarity index 100% rename from src/main/java/alpl/backend/web/learner/api/controller/LearnerController.java rename to src/main/java/aplp/backend/web/learner/api/controller/LearnerController.java diff --git a/src/main/java/alpl/backend/web/learner/application/dtos/LearnerProfileResponse.java b/src/main/java/aplp/backend/web/learner/application/dtos/LearnerProfileResponse.java similarity index 100% rename from src/main/java/alpl/backend/web/learner/application/dtos/LearnerProfileResponse.java rename to src/main/java/aplp/backend/web/learner/application/dtos/LearnerProfileResponse.java diff --git a/src/main/java/alpl/backend/web/learner/application/dtos/UpdateLearnerProfileRequest.java b/src/main/java/aplp/backend/web/learner/application/dtos/UpdateLearnerProfileRequest.java similarity index 100% rename from src/main/java/alpl/backend/web/learner/application/dtos/UpdateLearnerProfileRequest.java rename to src/main/java/aplp/backend/web/learner/application/dtos/UpdateLearnerProfileRequest.java diff --git a/src/main/java/alpl/backend/web/learner/application/mappers/LearnerMapper.java b/src/main/java/aplp/backend/web/learner/application/mappers/LearnerMapper.java similarity index 100% rename from src/main/java/alpl/backend/web/learner/application/mappers/LearnerMapper.java rename to src/main/java/aplp/backend/web/learner/application/mappers/LearnerMapper.java diff --git a/src/main/java/alpl/backend/web/learner/application/services/LearnerProvisioner.java b/src/main/java/aplp/backend/web/learner/application/services/LearnerProvisioner.java similarity index 100% rename from src/main/java/alpl/backend/web/learner/application/services/LearnerProvisioner.java rename to src/main/java/aplp/backend/web/learner/application/services/LearnerProvisioner.java diff --git a/src/main/java/alpl/backend/web/learner/application/services/LearnerService.java b/src/main/java/aplp/backend/web/learner/application/services/LearnerService.java similarity index 100% rename from src/main/java/alpl/backend/web/learner/application/services/LearnerService.java rename to src/main/java/aplp/backend/web/learner/application/services/LearnerService.java diff --git a/src/main/java/alpl/backend/web/learner/domain/entities/Learner.java b/src/main/java/aplp/backend/web/learner/domain/entities/Learner.java similarity index 100% rename from src/main/java/alpl/backend/web/learner/domain/entities/Learner.java rename to src/main/java/aplp/backend/web/learner/domain/entities/Learner.java diff --git a/src/main/java/alpl/backend/web/learner/domain/exceptions/LearnerNotFoundException.java b/src/main/java/aplp/backend/web/learner/domain/exceptions/LearnerNotFoundException.java similarity index 100% rename from src/main/java/alpl/backend/web/learner/domain/exceptions/LearnerNotFoundException.java rename to src/main/java/aplp/backend/web/learner/domain/exceptions/LearnerNotFoundException.java diff --git a/src/main/java/alpl/backend/web/learner/domain/repositories/LearnerRepository.java b/src/main/java/aplp/backend/web/learner/domain/repositories/LearnerRepository.java similarity index 100% rename from src/main/java/alpl/backend/web/learner/domain/repositories/LearnerRepository.java rename to src/main/java/aplp/backend/web/learner/domain/repositories/LearnerRepository.java diff --git a/src/main/java/alpl/backend/web/learner/infrastructure/persistence/LearnerJpaRepository.java b/src/main/java/aplp/backend/web/learner/infrastructure/persistence/LearnerJpaRepository.java similarity index 100% rename from src/main/java/alpl/backend/web/learner/infrastructure/persistence/LearnerJpaRepository.java rename to src/main/java/aplp/backend/web/learner/infrastructure/persistence/LearnerJpaRepository.java diff --git a/src/main/java/alpl/backend/web/learner/infrastructure/persistence/LearnerRepositoryImpl.java b/src/main/java/aplp/backend/web/learner/infrastructure/persistence/LearnerRepositoryImpl.java similarity index 100% rename from src/main/java/alpl/backend/web/learner/infrastructure/persistence/LearnerRepositoryImpl.java rename to src/main/java/aplp/backend/web/learner/infrastructure/persistence/LearnerRepositoryImpl.java diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index ba84567..83186e4 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -1,6 +1,6 @@ spring: datasource: - url: ${DB_URL:jdbc:postgresql://localhost:5432/aplp} + url: ${DB_URL:jdbc:postgresql://192.168.2.100:5432/aplp} username: ${DB_USERNAME:postgres} password: ${DB_PASSWORD:Pa55w0rd} hikari: diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 0844323..bed9fa6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -6,10 +6,15 @@ spring: jpa: hibernate: ddl-auto: validate + properties: + hibernate: + default_schema: web open-in-view: false flyway: enabled: true locations: classpath:db/migration + schemas: web + default-schema: web jackson: default-property-inclusion: non_null diff --git a/src/main/resources/db/migration/V1__init_schema.sql b/src/main/resources/db/migration/V1__init_schema.sql index 3c8416c..230cde2 100644 --- a/src/main/resources/db/migration/V1__init_schema.sql +++ b/src/main/resources/db/migration/V1__init_schema.sql @@ -1,12 +1,14 @@ CREATE TABLE users ( id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, email VARCHAR(320) NOT NULL, + username VARCHAR(50) NOT NULL, password_hash VARCHAR(255) NOT NULL, display_name VARCHAR(100) NOT NULL, status VARCHAR(20) NOT NULL DEFAULT 'ACTIVE', created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP, - CONSTRAINT uq_users_email UNIQUE (email) + CONSTRAINT uq_users_email UNIQUE (email), + CONSTRAINT uq_users_username UNIQUE (username) ); CREATE TABLE refresh_token ( @@ -32,3 +34,4 @@ CREATE TABLE learner ( CREATE INDEX idx_refresh_token_user_id ON refresh_token (user_id); CREATE INDEX idx_users_email ON users (email); +CREATE INDEX idx_users_username ON users (username); diff --git a/src/test/java/aplp/backend/web/AuthFlowIntegrationTest.java b/src/test/java/aplp/backend/web/AuthFlowIntegrationTest.java index 74f914c..0cbb706 100644 --- a/src/test/java/aplp/backend/web/AuthFlowIntegrationTest.java +++ b/src/test/java/aplp/backend/web/AuthFlowIntegrationTest.java @@ -38,10 +38,10 @@ class AuthFlowIntegrationTest { @Test void fullAuthFlow() throws Exception { - TokenPair registered = register(EMAIL, PASSWORD, "Learner One"); + TokenPair registered = register("learnerone", EMAIL, PASSWORD, "Learner One"); viewMyProfile(registered.accessToken()); - TokenPair loggedIn = login(EMAIL, PASSWORD); + TokenPair loggedIn = login("learnerone", PASSWORD); viewMyProfile(loggedIn.accessToken()); TokenPair refreshed = refresh(loggedIn.refreshToken()); @@ -54,7 +54,7 @@ class AuthFlowIntegrationTest { void registrationSetsXRequestIdAndRejectsDuplicate() throws Exception { MvcResult first = mockMvc.perform(post("/api/v1/auth/register") .contentType(MediaType.APPLICATION_JSON) - .content(json("email", "dup@aplp.dev", "password", PASSWORD, "displayName", "Dup"))) + .content(json("username", "dupuser", "email", "dup@aplp.dev", "password", PASSWORD, "displayName", "Dup"))) .andExpect(status().isCreated()) .andExpect(header().exists("X-Request-Id")) .andReturn(); @@ -62,7 +62,7 @@ class AuthFlowIntegrationTest { mockMvc.perform(post("/api/v1/auth/register") .contentType(MediaType.APPLICATION_JSON) - .content(json("email", "dup@aplp.dev", "password", PASSWORD, "displayName", "Dup"))) + .content(json("username", "dupuser2", "email", "dup@aplp.dev", "password", PASSWORD, "displayName", "Dup"))) .andExpect(status().isConflict()) .andExpect(jsonPath("$.code").value("EMAIL_ALREADY_EXISTS")); } @@ -80,10 +80,10 @@ class AuthFlowIntegrationTest { @Test void loginWithWrongPasswordIsRejected() throws Exception { - register("wrong@aplp.dev", PASSWORD, "Wrong"); + register("wronguser", "wrong@aplp.dev", PASSWORD, "Wrong"); mockMvc.perform(post("/api/v1/auth/login") .contentType(MediaType.APPLICATION_JSON) - .content(json("email", "wrong@aplp.dev", "password", "not-the-password"))) + .content(json("username", "wronguser", "password", "not-the-password"))) .andExpect(status().isUnauthorized()) .andExpect(jsonPath("$.code").value("INVALID_CREDENTIALS")); } @@ -92,14 +92,14 @@ class AuthFlowIntegrationTest { void validationErrorsReturnUniformShape() throws Exception { mockMvc.perform(post("/api/v1/auth/register") .contentType(MediaType.APPLICATION_JSON) - .content("{\"email\":\"not-an-email\",\"password\":\"x\",\"displayName\":\"\"}")) + .content("{\"username\":\"\",\"email\":\"not-an-email\",\"password\":\"x\",\"displayName\":\"\"}")) .andExpect(status().isBadRequest()) .andExpect(jsonPath("$.code").value("VALIDATION_FAILED")); } @Test void updateLearnerDisplayName() throws Exception { - TokenPair pair = register("update-me@aplp.dev", PASSWORD, "Old Name"); + TokenPair pair = register("updateme", "update-me@aplp.dev", PASSWORD, "Old Name"); mockMvc.perform(patch("/api/v1/learners/me") .header("Authorization", "Bearer " + pair.accessToken()) .contentType(MediaType.APPLICATION_JSON) @@ -110,7 +110,7 @@ class AuthFlowIntegrationTest { @Test void refreshWithRevokedTokenIsRejected() throws Exception { - TokenPair pair = loginOrRegister("revoked@aplp.dev"); + TokenPair pair = loginOrRegister("revokeduser", "revoked@aplp.dev"); mockMvc.perform(post("/api/v1/auth/logout") .header("Authorization", "Bearer " + pair.accessToken()) @@ -141,10 +141,10 @@ class AuthFlowIntegrationTest { .andExpect(jsonPath("$.status").value("UP")); } - private TokenPair register(String email, String password, String displayName) throws Exception { + private TokenPair register(String username, String email, String password, String displayName) throws Exception { MvcResult result = mockMvc.perform(post("/api/v1/auth/register") .contentType(MediaType.APPLICATION_JSON) - .content(json("email", email, "password", password, "displayName", displayName))) + .content(json("username", username, "email", email, "password", password, "displayName", displayName))) .andExpect(status().isCreated()) .andReturn(); JsonNode body = bodyOf(result); @@ -152,22 +152,21 @@ class AuthFlowIntegrationTest { return new TokenPair(body.get("accessToken").asText(), body.get("refreshToken").asText()); } - private TokenPair login(String email, String password) throws Exception { + private TokenPair login(String username, String password) throws Exception { MvcResult result = mockMvc.perform(post("/api/v1/auth/login") .contentType(MediaType.APPLICATION_JSON) - .content(json("email", email, "password", password))) + .content(json("username", username, "password", password))) .andExpect(status().isOk()) .andReturn(); JsonNode body = bodyOf(result); - assertThat(body.get("user").get("email").asText()).isEqualTo(email); return new TokenPair(body.get("accessToken").asText(), body.get("refreshToken").asText()); } - private TokenPair loginOrRegister(String email) throws Exception { + private TokenPair loginOrRegister(String username, String email) throws Exception { try { - return register(email, PASSWORD, "Revoked"); + return register(username, email, PASSWORD, "Revoked"); } catch (AssertionError ignored) { - return login(email, PASSWORD); + return login(username, PASSWORD); } } diff --git a/src/test/resources/application-test.yml b/src/test/resources/application-test.yml index 23adfb7..c763d36 100644 --- a/src/test/resources/application-test.yml +++ b/src/test/resources/application-test.yml @@ -7,8 +7,13 @@ spring: jpa: hibernate: ddl-auto: validate + properties: + hibernate: + default_schema: web flyway: enabled: true + schemas: web + default-schema: web app: jwt: