Add fields to user entity.

This commit is contained in:
Florian THIERRY
2024-03-14 18:17:03 +01:00
parent 9fcfc04117
commit 50b305c3cd
14 changed files with 174 additions and 4 deletions

View File

@@ -5,6 +5,9 @@ import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import static org.codiki.domain.user.model.UserRole.STANDARD;
import static org.codiki.domain.user.model.builder.UserBuilder.anUser;
import static org.springframework.util.ObjectUtils.isEmpty;
import org.codiki.application.security.AuthenticationFacade; import org.codiki.application.security.AuthenticationFacade;
import org.codiki.application.security.JwtService; import org.codiki.application.security.JwtService;
import org.codiki.application.security.annotation.AllowedToAdmins; import org.codiki.application.security.annotation.AllowedToAdmins;
@@ -12,6 +15,8 @@ import org.codiki.application.security.model.CustomUserDetails;
import org.codiki.domain.exception.LoginFailureException; import org.codiki.domain.exception.LoginFailureException;
import org.codiki.domain.exception.RefreshTokenDoesNotExistException; import org.codiki.domain.exception.RefreshTokenDoesNotExistException;
import org.codiki.domain.exception.UserDoesNotExistException; import org.codiki.domain.exception.UserDoesNotExistException;
import org.codiki.domain.user.exception.UserAlreadyExistsException;
import org.codiki.domain.user.exception.UserCreationException;
import org.codiki.domain.user.model.RefreshToken; import org.codiki.domain.user.model.RefreshToken;
import org.codiki.domain.user.model.User; import org.codiki.domain.user.model.User;
import org.codiki.domain.user.model.UserAuthenticationData; import org.codiki.domain.user.model.UserAuthenticationData;
@@ -107,4 +112,26 @@ public class UserUseCases {
userPort.save(refreshToken); userPort.save(refreshToken);
return refreshToken; return refreshToken;
} }
public User createUser(String pseudo, String email, String password) {
if (isEmpty(pseudo) || isEmpty(email) || isEmpty(password)) {
throw new UserCreationException();
}
if (userPort.existsByEmail(email)) {
throw new UserAlreadyExistsException();
}
User newUser = anUser()
.withId(UUID.randomUUID())
.withPseudo(pseudo)
.withEmail(email)
.withPassword(passwordEncoder.encode(password))
.withRole(STANDARD)
.build();
userPort.save(newUser);
return newUser;
}
} }

View File

@@ -0,0 +1,9 @@
package org.codiki.domain.user.exception;
import org.codiki.domain.exception.FunctionnalException;
public class UserAlreadyExistsException extends FunctionnalException {
public UserAlreadyExistsException() {
super("An user already exists with this email address.");
}
}

View File

@@ -0,0 +1,9 @@
package org.codiki.domain.user.exception;
import org.codiki.domain.exception.FunctionnalException;
public class UserCreationException extends FunctionnalException {
public UserCreationException() {
super("Pseudo, email address and password can not be empty.");
}
}

View File

@@ -5,6 +5,9 @@ import java.util.UUID;
public record User( public record User(
UUID id, UUID id,
String pseudo,
String email,
String password, String password,
UUID photoId,
List<UserRole> roles List<UserRole> roles
) {} ) {}

View File

@@ -0,0 +1,64 @@
package org.codiki.domain.user.model.builder;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.codiki.domain.user.model.User;
import org.codiki.domain.user.model.UserRole;
public class UserBuilder {
private UUID id;
private String pseudo;
private String email;
private String password;
private UUID photoId;
private Set<UserRole> roles = new HashSet<>();
private UserBuilder() {}
public static UserBuilder anUser() {
return new UserBuilder();
}
public UserBuilder withId(UUID id) {
this.id = id;
return this;
}
public UserBuilder withPseudo(String pseudo) {
this.pseudo = pseudo;
return this;
}
public UserBuilder withEmail(String email) {
this.email = email;
return this;
}
public UserBuilder withPassword(String password) {
this.password = password;
return this;
}
public UserBuilder withPhotoId(UUID photoId) {
this.photoId = photoId;
return this;
}
public UserBuilder withRole(UserRole role) {
this.roles.add(role);
return this;
}
public UserBuilder withRoles(List<UserRole> roles) {
this.roles = new HashSet<>(roles);
return this;
}
public User build() {
return new User(id, pseudo,email, password, photoId, new LinkedList<>(roles));
}
}

View File

@@ -21,4 +21,6 @@ public interface UserPort {
Optional<RefreshToken> findRefreshTokenById(UUID refreshTokenId); Optional<RefreshToken> findRefreshTokenById(UUID refreshTokenId);
void save(RefreshToken refreshToken); void save(RefreshToken refreshToken);
boolean existsByEmail(String email);
} }

View File

@@ -16,6 +16,8 @@ import org.codiki.domain.picture.exception.PictureUploadException;
import org.codiki.domain.publication.exception.PublicationEditionException; import org.codiki.domain.publication.exception.PublicationEditionException;
import org.codiki.domain.publication.exception.PublicationNotFoundException; import org.codiki.domain.publication.exception.PublicationNotFoundException;
import org.codiki.domain.publication.exception.PublicationUpdateForbiddenException; import org.codiki.domain.publication.exception.PublicationUpdateForbiddenException;
import org.codiki.domain.user.exception.UserAlreadyExistsException;
import org.codiki.domain.user.exception.UserCreationException;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail; import org.springframework.http.ProblemDetail;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
@@ -30,7 +32,9 @@ public class GlobalControllerExceptionHandler extends ResponseEntityExceptionHan
CategoryNotFoundException.class, CategoryNotFoundException.class,
LoginFailureException.class, LoginFailureException.class,
PublicationEditionException.class, PublicationEditionException.class,
PictureUploadException.class PictureUploadException.class,
UserAlreadyExistsException.class,
UserCreationException.class
}) })
public ProblemDetail handleBadRequestExceptions(Exception exception) { public ProblemDetail handleBadRequestExceptions(Exception exception) {
return buildProblemDetail(BAD_REQUEST, exception); return buildProblemDetail(BAD_REQUEST, exception);

View File

@@ -2,6 +2,7 @@ package org.codiki.exposition.user;
import java.util.List; import java.util.List;
import static org.springframework.http.HttpStatus.CREATED;
import org.codiki.application.security.annotation.AllowedToAdmins; import org.codiki.application.security.annotation.AllowedToAdmins;
import org.codiki.application.security.annotation.AllowedToAnonymous; import org.codiki.application.security.annotation.AllowedToAnonymous;
import org.codiki.application.user.UserUseCases; import org.codiki.application.user.UserUseCases;
@@ -10,10 +11,13 @@ import org.codiki.domain.user.model.UserAuthenticationData;
import org.codiki.exposition.user.model.LoginRequest; import org.codiki.exposition.user.model.LoginRequest;
import org.codiki.exposition.user.model.LoginResponse; import org.codiki.exposition.user.model.LoginResponse;
import org.codiki.exposition.user.model.RefreshTokenRequest; import org.codiki.exposition.user.model.RefreshTokenRequest;
import org.codiki.exposition.user.model.SignInRequestDto;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
@RestController @RestController
@@ -43,4 +47,10 @@ public class UserController {
UserAuthenticationData userAuthenticationData = userUseCases.authenticate(request.refreshTokenValue()); UserAuthenticationData userAuthenticationData = userUseCases.authenticate(request.refreshTokenValue());
return new LoginResponse(userAuthenticationData); return new LoginResponse(userAuthenticationData);
} }
@PostMapping
@ResponseStatus(CREATED)
public void signIn(@RequestBody SignInRequestDto request) {
userUseCases.createUser(request.pseudo(), request.email(), request.password());
}
} }

View File

@@ -0,0 +1,8 @@
package org.codiki.exposition.user.model;
public record SignInRequestDto(
String pseudo,
String email,
String password
) {
}

View File

@@ -51,6 +51,11 @@ public class UserJpaAdapter implements UserPort {
return userJpaRepository.existsById(userId); return userJpaRepository.existsById(userId);
} }
@Override
public boolean existsByEmail(String email) {
return userJpaRepository.existsByEmail(email);
}
@Override @Override
public Optional<RefreshToken> findRefreshTokenByUserId(UUID userId) { public Optional<RefreshToken> findRefreshTokenByUserId(UUID userId) {
return refreshTokenJpaRepository.findByUserId(userId) return refreshTokenJpaRepository.findByUserId(userId)

View File

@@ -27,10 +27,14 @@ import lombok.Setter;
public class UserEntity { public class UserEntity {
@Id @Id
private UUID id; private UUID id;
@Column(nullable = false)
private String pseudo;
@Column(nullable = false)
private String email;
@Column(nullable = false) @Column(nullable = false)
private String password; private String password;
@Column
private UUID photoId;
@ElementCollection(targetClass = UserRole.class) @ElementCollection(targetClass = UserRole.class)
@CollectionTable( @CollectionTable(
name = "user_role", name = "user_role",
@@ -41,14 +45,20 @@ public class UserEntity {
public UserEntity(User user) { public UserEntity(User user) {
id = user.id(); id = user.id();
pseudo = user.pseudo();
email = user.email();
password = user.password(); password = user.password();
photoId = user.photoId();
roles = user.roles(); roles = user.roles();
} }
public User toUser() { public User toUser() {
return new User( return new User(
id, id,
pseudo,
email,
password, password,
photoId,
roles roles
); );
} }

View File

@@ -17,4 +17,6 @@ public interface UserJpaRepository extends JpaRepository<UserEntity, UUID> {
@Query("SELECT u FROM UserEntity u JOIN FETCH u.roles") @Query("SELECT u FROM UserEntity u JOIN FETCH u.roles")
List<UserEntity> findAll(); List<UserEntity> findAll();
boolean existsByEmail(String email);
} }

View File

@@ -0,0 +1,12 @@
insert into "user" values
('5ad462b8-8f9e-4a26-bb86-c74fef5d11b6', 'Standard user', 'standard.user@codiki.org', '$2a$10$FVhrYRXw.Zw2V5jGUkvX/.1U.IdWlwd8J.Y/5pb5etAzyoBhJ3FHG', null),
('15a13dc7-029d-4eab-a63d-c1e96f90241d', 'Admin user', 'admin.user@codiki.org', '$2a$10$FVhrYRXw.Zw2V5jGUkvX/.1U.IdWlwd8J.Y/5pb5etAzyoBhJ3FHG', null);
insert into user_role values
('5ad462b8-8f9e-4a26-bb86-c74fef5d11b6', 0),
('15a13dc7-029d-4eab-a63d-c1e96f90241d', 0),
('15a13dc7-029d-4eab-a63d-c1e96f90241d', 1);
insert into category values
('172fa901-3f4b-4540-92f3-1c15820e8ec9', 'Main category', null),
('3f4b4540-a901-92f3-1c15-8ec9172f820e', 'Sub category', '172fa901-3f4b-4540-92f3-1c15820e8ec9');

View File

@@ -1,7 +1,9 @@
CREATE TABLE IF NOT EXISTS "user" ( CREATE TABLE IF NOT EXISTS "user" (
id UUID NOT NULL, id UUID NOT NULL,
name VARCHAR NOT NULL, pseudo VARCHAR NOT NULL,
email VARCHAR NOT NULL,
password VARCHAR NOT NULL, password VARCHAR NOT NULL,
photo_id UUID,
CONSTRAINT user_pk PRIMARY KEY (id) CONSTRAINT user_pk PRIMARY KEY (id)
); );
@@ -39,6 +41,9 @@ CREATE TABLE IF NOT EXISTS picture (
); );
CREATE INDEX picture_publisher_id_idx ON picture (publisher_id); CREATE INDEX picture_publisher_id_idx ON picture (publisher_id);
ALTER TABLE "user" ADD CONSTRAINT user_photo_id_fk FOREIGN KEY (photo_id) REFERENCES picture (id);
CREATE INDEX user_photo_id_idx ON "user" (photo_id);
CREATE TABLE IF NOT EXISTS publication ( CREATE TABLE IF NOT EXISTS publication (
id UUID NOT NULL, id UUID NOT NULL,
key VARCHAR(10) NOT NULL, key VARCHAR(10) NOT NULL,