feat: add registration validator

This commit is contained in:
Sergey Krylov 2026-02-19 05:10:46 +03:00
parent c28f6e464f
commit fd2f08e8bc
12 changed files with 114 additions and 52 deletions

View File

@ -1,4 +1,6 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html lang="en">
<head>
<title>Charm Registration</title>
@ -20,6 +22,13 @@
</table>
<button type="submit">${requestScope.wordBundle.getWord("save")}</button>
</form>
<div style="color: red">
<c:forEach var="error" items="${errors}">
<span>${wordBundle.getWord(error)}</span>
<br>
</c:forEach>
</div>
</div>
<%@ include file="footer.jsp" %>
</body>

View File

@ -7,6 +7,10 @@ confirm-password=Confirm password
delete=Delete
email=Email
email-warning=Changing your mail may have negative effects
error.age.invalid=Age must be between 18 and 100 years old
error.email.exist=This email already exists
error.email.invalid=Invalid email
error.password.invalid=Invalid password
female=Female
gender=Gender
inactive=Inactive
@ -16,6 +20,9 @@ other=Other
page-bad-request=Bad request
page-not-found=Page not found
password=Password
photo=Photo
save=Save
status=Status
surname=Surname
surname=Surname
unexpected-error=Unexpected error
update=Update

View File

@ -7,6 +7,10 @@ confirm-password=Confirm password
delete=Delete
email=Email
email-warning=Changing your mail may have negative effects
error.age.invalid=Age must be between 18 and 100 years old
error.email.exist=This email already exists
error.email.invalid=Invalid email
error.password.invalid=Invalid password
female=Female
gender=Gender
inactive=Inactive
@ -16,6 +20,9 @@ other=Other
page-bad-request=Bad request
page-not-found=Page not found
password=Password
photo=Photo
save=Save
status=Status
surname=Surname
surname=Surname
unexpected-error=Unexpected error
update=Update

View File

@ -7,6 +7,10 @@ confirm-password=\u041F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0
delete=\u0423\u0434\u0430\u043B\u0438\u0442\u044C
email=\u041F\u043E\u0447\u0442\u0430
email-warning=\u0421\u043C\u0435\u043D\u0430 \u043F\u043E\u0447\u0442\u044B \u043C\u043E\u0436\u0435\u0442 \u043F\u0440\u0438\u0432\u0435\u0441\u0442\u0438 \u043A \u043D\u0435\u0436\u0435\u043B\u0430\u0442\u0435\u043B\u044C\u043D\u044B\u043C \u043F\u043E\u0441\u043B\u0435\u0434\u0441\u0442\u0432\u0438\u044F\u043C
error.age.invalid=\u0412\u043E\u0437\u0440\u0430\u0441\u0442 \u0434\u043E\u043B\u0436\u0435\u043D \u0431\u044B\u0442\u044C \u043E\u0442 18 \u0434\u043E 100 \u043B\u0435\u0442
error.email.exist=\u0422\u0430\u043A\u0430\u044F \u043F\u043E\u0447\u0442\u0430 \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442
error.email.invalid=\u041D\u0435\u0432\u0435\u0440\u043D\u0430\u044F \u043F\u043E\u0447\u0442\u0430
error.password.invalid=\u041D\u0435\u043F\u043E\u0434\u0445\u043E\u0434\u044F\u0449\u0438\u0439 \u043F\u0430\u0440\u043E\u043B\u044C
female=\u0416\u0435\u043D\u0449\u0438\u043D\u0430
gender=\u041F\u043E\u043B
inactive=\u041D\u0435\u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0439
@ -16,6 +20,9 @@ other=\u0414\u0440\u0443\u0433\u043E\u0435
page-bad-request=\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 \u0437\u0430\u043F\u0440\u043E\u0441
page-not-found=\u0421\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430
password=\u041F\u0430\u0440\u043E\u043B\u044C
photo=\u0424\u043E\u0442\u043E
save=\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C
status=\u0421\u0442\u0430\u0442\u0443\u0441
surname=\u0424\u0430\u043C\u0438\u043B\u0438\u044F
surname=\u0424\u0430\u043C\u0438\u043B\u0438\u044F
unexpected-error=\u041D\u0435\u043F\u0440\u0435\u0434\u0432\u0438\u0434\u0435\u043D\u043D\u0430\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u0441\u0435\u0440\u0432\u0435\u0440\u0430
update=\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C

View File

@ -1,33 +0,0 @@
package ru.charm.back.controller;
import jakarta.servlet.ServletConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.annotation.WebServlet;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@WebServlet("/like")
public class LikeController extends HttpServlet {
@Override
public void init(ServletConfig config) throws ServletException {
System.out.println("LikeController init" + config.getServletName() );
super.init(config);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/plain");
System.out.println("User-Agent: " + req.getHeader("User-Agent"));
resp.getWriter().write("Like count: 101");
resp.setHeader("X-Custom-Header", "Zalupa");
}
@Override
public void destroy() {
System.out.println("LikeController destroy");
super.destroy();
}
}

View File

@ -9,6 +9,8 @@ import lombok.extern.slf4j.Slf4j;
import ru.charm.back.dto.RegistrationDto;
import ru.charm.back.mapper.RequestToRegistrationDtoMapper;
import ru.charm.back.service.ProfileService;
import ru.charm.back.validator.RegistrationValidator;
import ru.charm.back.validator.ValidationResult;
import java.io.IOException;
@ -18,6 +20,7 @@ public class RegistrationController extends HttpServlet {
private final ProfileService service = ProfileService.getInstance();
private final RequestToRegistrationDtoMapper requestToRegistrationDtoMapper = RequestToRegistrationDtoMapper.getInstance();
private final RegistrationValidator validator = RegistrationValidator.getInstance();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
@ -25,8 +28,16 @@ public class RegistrationController extends HttpServlet {
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException {
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
RegistrationDto dto = requestToRegistrationDtoMapper.map(req);
ValidationResult validationResult = validator.validate(dto);
if (!validationResult.isValid()) {
req.setAttribute("errors", validationResult.getErrors());
doGet(req, resp);
return;
}
Long id = service.save(dto);
log.info("Profile with the email address {} has been registered with id {}", dto.getEmail(), id);
resp.sendRedirect(String.format("/profile?id=%s", id));

View File

@ -5,6 +5,8 @@ import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletRequestWrapper;
import jakarta.servlet.http.HttpServletResponse;
import lombok.Getter;
import lombok.Setter;
import ru.charm.back.model.Gender;
import java.io.IOException;
@ -39,16 +41,13 @@ public class HiddenHttpMethodFilter implements Filter {
public static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
@Setter
@Getter
public final String method;
public HttpMethodRequestWrapper(HttpServletRequest request, String method) {
super(request);
this.method = method;
}
@Override
public String getMethod() {
return method;
}
}
}

View File

@ -48,21 +48,11 @@ public class ProfileService {
public void update(ProfileUpdateDto dto) {
dao.findById(dto.getId())
.ifPresent(profile -> {
checkEmail(profile.getEmail(), dto.getEmail());
dao.update(profileUpdateDtoToProfileMapper.map(dto, profile));
}
);
}
private void checkEmail(String oldEmail, String newEmail) {
if (newEmail == null) return;
Set<String> emails = dao.getAllEmails();
if (!Objects.equals(oldEmail, newEmail) &&
emails.contains(newEmail)) {
throw new DuplicateEmailException();
}
}
public boolean delete(Long id) {
return dao.delete(id);
}

View File

@ -2,8 +2,13 @@ package ru.charm.back.utils;
import lombok.experimental.UtilityClass;
import java.util.regex.Pattern;
@UtilityClass
public class StringUtils {
public static final Pattern VALID_EMAIL_ADDRESS_REGEX =
Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE);
public static boolean isBlank(String str) {
return str == null || str.isBlank();
}

View File

@ -0,0 +1,35 @@
package ru.charm.back.validator;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import ru.charm.back.dao.ProfileDao;
import ru.charm.back.dto.RegistrationDto;
import ru.charm.back.mapper.ProfileUpdateDtoToProfileMapper;
import static ru.charm.back.utils.StringUtils.VALID_EMAIL_ADDRESS_REGEX;
import static ru.charm.back.utils.StringUtils.isBlank;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class RegistrationValidator implements Validator<RegistrationDto> {
private static final RegistrationValidator INSTANCE = new RegistrationValidator();
public static RegistrationValidator getInstance() { return INSTANCE; }
private final ProfileDao dao = ProfileDao.getInstance();
@Override
public ValidationResult validate(RegistrationDto dto) {
ValidationResult validationResult = new ValidationResult();
if (isBlank(dto.getEmail()) || !VALID_EMAIL_ADDRESS_REGEX.matcher(dto.getEmail()).matches()) {
validationResult.addError("error.email.invalid");
} else if (dao.getAllEmails().contains(dto.getEmail())) {
validationResult.addError("error.email.exists");
}
if (isBlank(dto.getPassword())) {
validationResult.addError("error.password.invalid");
}
return validationResult;
}
}

View File

@ -0,0 +1,20 @@
package ru.charm.back.validator;
import java.util.ArrayList;
import java.util.List;
public class ValidationResult {
private final List<String> errors = new ArrayList<>();
public boolean isValid() {
return errors.isEmpty();
}
public void addError(String code) {
errors.add(code);
}
public List<String> getErrors() {
return new ArrayList<>(errors);
}
}

View File

@ -0,0 +1,5 @@
package ru.charm.back.validator;
public interface Validator<T> {
ValidationResult validate(T object);
}