- V1__create_users_table.sql replaces placeholder: creates users table with id UUID PK, email UNIQUE NOT NULL, password_hash NOT NULL, subscription VARCHAR(20) DEFAULT 'none' with CHECK constraint (none/basic/pro), created_at/updated_at TIMESTAMPTZ DEFAULT CURRENT_TIMESTAMP. Compatible with both H2 and PostgreSQL. - SecurityConfig: minimal @Configuration providing BCryptPasswordEncoder bean. Required because Spring Boot 4 no longer auto-configures a PasswordEncoder. - Subscription enum: NONE, BASIC, PRO with string values matching the DB CHECK constraint. - User entity: @PrePersist generates UUID and timestamps in application code, @PreUpdate refreshes updated_at. Email setter normalizes to lowercase for case-insensitive uniqueness. Explicit getters/setters (no Lombok per guidelines). - UserRepository: Spring Data JPA extending JpaRepository<User, UUID>. findByEmail(Optional) and existsByEmail for duplicate checks. - UserService: @RequiredArgsConstructor with constructor-injected UserRepository and PasswordEncoder. createUser normalizes email, checks duplicates via existsByEmail, throws EmailAlreadyExistsException, hashes password with BCrypt, saves. findByEmail returns Optional<User>. - EmailAlreadyExistsException: custom RuntimeException for duplicate registration attempts. ControllerAdvice handler deferred to auth ticket. Verification: ./gradlew test passes (Flyway + H2 context loads). docker compose up -d succeeds, Flyway applies V1 against PostgreSQL 16. \d users confirms all columns, constraints, defaults, and indexes.
102 lines
2.4 KiB
Java
102 lines
2.4 KiB
Java
package se.bilhalsning.entity;
|
|
|
|
import jakarta.persistence.Column;
|
|
import jakarta.persistence.Entity;
|
|
import jakarta.persistence.EnumType;
|
|
import jakarta.persistence.Enumerated;
|
|
import jakarta.persistence.Id;
|
|
import jakarta.persistence.PrePersist;
|
|
import jakarta.persistence.PreUpdate;
|
|
import jakarta.persistence.Table;
|
|
import java.time.Instant;
|
|
import java.util.UUID;
|
|
|
|
@Entity
|
|
@Table(name = "users")
|
|
public class User {
|
|
|
|
@Id
|
|
@Column(name = "id", columnDefinition = "uuid", nullable = false, updatable = false)
|
|
private UUID id;
|
|
|
|
@Column(name = "email", nullable = false, unique = true, length = 255)
|
|
private String email;
|
|
|
|
@Column(name = "password_hash", nullable = false, length = 255)
|
|
private String passwordHash;
|
|
|
|
@Enumerated(EnumType.STRING)
|
|
@Column(name = "subscription", nullable = false, length = 20)
|
|
private Subscription subscription = Subscription.NONE;
|
|
|
|
@Column(name = "created_at", nullable = false)
|
|
private Instant createdAt;
|
|
|
|
@Column(name = "updated_at", nullable = false)
|
|
private Instant updatedAt;
|
|
|
|
@PrePersist
|
|
void onCreate() {
|
|
if (this.id == null) {
|
|
this.id = UUID.randomUUID();
|
|
}
|
|
Instant now = Instant.now();
|
|
if (this.createdAt == null) {
|
|
this.createdAt = now;
|
|
}
|
|
this.updatedAt = now;
|
|
}
|
|
|
|
@PreUpdate
|
|
void onUpdate() {
|
|
this.updatedAt = Instant.now();
|
|
}
|
|
|
|
public UUID getId() {
|
|
return id;
|
|
}
|
|
|
|
public void setId(UUID id) {
|
|
this.id = id;
|
|
}
|
|
|
|
public String getEmail() {
|
|
return email;
|
|
}
|
|
|
|
public void setEmail(String email) {
|
|
this.email = email != null ? email.toLowerCase().trim() : null;
|
|
}
|
|
|
|
public String getPasswordHash() {
|
|
return passwordHash;
|
|
}
|
|
|
|
public void setPasswordHash(String passwordHash) {
|
|
this.passwordHash = passwordHash;
|
|
}
|
|
|
|
public Subscription getSubscription() {
|
|
return subscription;
|
|
}
|
|
|
|
public void setSubscription(Subscription subscription) {
|
|
this.subscription = subscription;
|
|
}
|
|
|
|
public Instant getCreatedAt() {
|
|
return createdAt;
|
|
}
|
|
|
|
public void setCreatedAt(Instant createdAt) {
|
|
this.createdAt = createdAt;
|
|
}
|
|
|
|
public Instant getUpdatedAt() {
|
|
return updatedAt;
|
|
}
|
|
|
|
public void setUpdatedAt(Instant updatedAt) {
|
|
this.updatedAt = updatedAt;
|
|
}
|
|
}
|