UserMapper.java
package com.albertsilva.dev.asjcatalog.mapper.user;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.stream.Collectors;
import org.springframework.data.domain.Page;
import org.springframework.stereotype.Component;
import com.albertsilva.dev.asjcatalog.domain.user.Role;
import com.albertsilva.dev.asjcatalog.domain.user.User;
import com.albertsilva.dev.asjcatalog.dto.role.response.RoleResponse;
import com.albertsilva.dev.asjcatalog.dto.user.request.UserCreateRequest;
import com.albertsilva.dev.asjcatalog.dto.user.request.UserRegisterRequest;
import com.albertsilva.dev.asjcatalog.dto.user.request.UserUpdateRequest;
import com.albertsilva.dev.asjcatalog.dto.user.response.UserDetailsResponse;
import com.albertsilva.dev.asjcatalog.dto.user.response.UserResponse;
/**
* Conversor manual (sem MapStruct) entre os DTOs de usuário e a entidade
* {@link User}. Componente sem estado, usado por {@code UserService} e
* {@code AccountService}.
*
* <p>
* <b>Entrada:</b> os dois {@code toEntity} copiam nome, e-mail e senha
* <b>como recebidos</b> (sem {@code trim}, sem conversão de caixa e
* <b>sem codificar a senha</b>: o service a codifica logo depois) e recebem as
* roles já resolvidas como entidades, sem ler {@code roleIds}. O
* {@code updateEntity} copia apenas nome, sobrenome e e-mail; senha e roles da
* atualização são tratadas por {@code UserService}. O fluxo
* {@code AccountService.updateAuthenticatedUser} <b>não usa</b> este mapper.
* </p>
*
* <p>
* <b>Saída:</b> as respostas nunca incluem a senha (nem o hash) nem
* {@code User.tokens}; percorrem {@code User.getRoles()} (carregada sob
* demanda, possível consulta adicional por usuário) e convertem cada role em
* {@link RoleResponse}.
* </p>
*/
@Component
public class UserMapper {
/**
* Cria um novo {@link User} (ainda sem id) a partir do request de criação por
* administrador, com as roles informadas.
*
* <p>
* Copia {@code firstName}, {@code lastName}, {@code email} e {@code password}
* (<b>texto puro</b>, sem codificar) e adiciona as roles recebidas em
* {@code roles}, se não for {@code null}. {@code request.roleIds()} <b>não é
* lido</b>: a resolução dos ids em entidades é feita antes, por
* {@code UserService}. O indicador {@code active} não é definido (valor padrão
* {@code false}; o service chama {@code activate()}).
* </p>
*
* @param request dados de criação
* @param roles roles já resolvidas (pode ser {@code null})
* @return nova entidade (não persistida) ou {@code null} se {@code request} for
* {@code null}
*/
public User toEntity(UserCreateRequest request, Set<Role> roles) {
if (request == null) {
return null;
}
User user = new User();
user.setFirstName(request.firstName());
user.setLastName(request.lastName());
user.setEmail(request.email());
user.setPassword(request.password());
if (roles != null) {
user.getRoles().addAll(roles);
}
return user;
}
/**
* Cria um novo {@link User} (ainda sem id) a partir do request de registro
* público, com as roles informadas.
*
* <p>
* Mesmo comportamento de {@link #toEntity(UserCreateRequest, Set)}: copia nome,
* e-mail e senha como recebidos (senha em texto puro), adiciona as roles
* recebidas e não define {@code active} (o {@code AccountService.register}
* chama {@code deactivate()} e passa apenas {@code ROLE_OPERATOR}).
* </p>
*
* @param request dados do registro
* @param roles roles já resolvidas (pode ser {@code null})
* @return nova entidade (não persistida) ou {@code null} se {@code request} for
* {@code null}
*/
public User toEntity(UserRegisterRequest request, Set<Role> roles) {
if (request == null) {
return null;
}
User user = new User();
user.setFirstName(request.firstName());
user.setLastName(request.lastName());
user.setEmail(request.email());
user.setPassword(request.password());
if (roles != null) {
user.getRoles().addAll(roles);
}
return user;
}
/**
* Aplica ao usuário os campos básicos do request de atualização:
* {@code firstName}, {@code lastName} e {@code email}.
*
* <p>
* Ao contrário de {@code CategoryMapper} e {@code ProductMapper}, <b>não
* verifica {@code null}</b>: os três valores sempre são gravados, mesmo que
* {@code null}, e o e-mail não é normalizado (o DTO exige valores não vazios).
* Uma entrada {@code request} ou {@code entity} {@code null} lança
* {@link NullPointerException}. <b>Não altera</b> senha, roles, {@code active}
* nem tokens: {@code UserService} trata senha e roles à parte.
* </p>
*
* @param request dados de atualização
* @param entity usuário a ser modificado
*/
public void updateEntity(UserUpdateRequest request, User entity) {
entity.setFirstName(request.firstName());
entity.setLastName(request.lastName());
entity.setEmail(request.email());
}
/**
* Converte o usuário em resposta: {@code id}, nomes, e-mail e roles como
* {@link RoleResponse}. Não inclui senha, {@code active} nem tokens.
*
* <p>
* Percorre {@code entity.getRoles()} (carregada sob demanda quando a entidade
* vem do banco). As roles são coletadas em um {@link LinkedHashSet}, que apenas
* preserva a ordem de iteração da coleção de origem (um {@code HashSet}, sem
* ordem definida).
* </p>
*
* @param entity usuário
* @return resposta ou {@code null} se {@code entity} for {@code null}
*/
public UserResponse toResponse(User entity) {
if (entity == null) {
return null;
}
Set<RoleResponse> roles = entity.getRoles().stream().map(this::toRoleResponse)
.collect(Collectors.toCollection(LinkedHashSet::new));
return new UserResponse(entity.getId(), entity.getFirstName(), entity.getLastName(), entity.getEmail(), roles);
}
/**
* Converte o usuário em resposta detalhada: os campos de
* {@link #toResponse(User)} mais o indicador {@code active}. Percorre
* {@code entity.getRoles()} da mesma forma. Não inclui senha nem tokens.
*
* @param entity usuário
* @return resposta ou {@code null} se {@code entity} for {@code null}
*/
public UserDetailsResponse toDetailsResponse(User entity) {
if (entity == null) {
return null;
}
Set<RoleResponse> roles = entity.getRoles().stream().map(this::toRoleResponse)
.collect(Collectors.toCollection(LinkedHashSet::new));
return new UserDetailsResponse(entity.getId(), entity.getFirstName(), entity.getLastName(), entity.getEmail(),
roles, entity.isActive());
}
/**
* Converte uma página de usuários em página de {@link UserResponse} aplicando
* {@link #toResponse(User)} a cada elemento e preservando os metadados de
* paginação.
*
* @param entities página de entidades
* @return página de respostas
*/
public Page<UserResponse> toResponsePage(Page<User> entities) {
return entities.map(this::toResponse);
}
/**
* Converte uma {@link Role} em {@link RoleResponse} (id e authority).
*/
private RoleResponse toRoleResponse(Role role) {
return new RoleResponse(role.getId(), role.getAuthority());
}
}