feat: add email change

This commit is contained in:
Sergey Krylov 2026-02-17 05:24:53 +03:00
parent be986d1467
commit 9b040ba885
31 changed files with 789 additions and 254 deletions

View File

@ -0,0 +1,26 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html lang="en">
<head>
<title>Charm Profiles</title>
<%@ include file="style.html" %>
</head>
<body>
<%@ include file="header.jsp" %>
<div>
<h3 style="color: red">${requestScope.wordBundle.getWord("email-warning")}</h3>
<form method="post" action="/email">
<input type="hidden" name="_method" value="put"/>
<input type="hidden" name="id" value="${requestScope.profile.id}">
<table>
<tr>
<td><h3>${requestScope.wordBundle.getWord("email")}</h3></td>
<td><input type="email" name="email" value="${requestScope.profile.email}"></td>
</tr>
</table>
<button type="submit">${requestScope.wordBundle.getWord("save")}</button>
</form>
</div>
<%@ include file="footer.jsp" %>
</body>
</html>

View File

@ -0,0 +1,15 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html lang="en">
<head>
<title>Charm Bad Request</title>
<%@ include file="style.html" %>
</head>
<body>
<%@ include file="header.jsp" %>
<div>
<h3>400 - ${requestScope.wordBundle.getWord("page-bad-request")}</h3>
</div>
<%@ include file="footer.jsp" %>
</body>
</html>

View File

@ -0,0 +1,15 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html lang="en">
<head>
<title>Charm Not Found</title>
<%@ include file="style.html" %>
</head>
<body>
<%@ include file="header.jsp" %>
<div>
<h3>404 - ${requestScope.wordBundle.getWord("page-not-found")}</h3>
</div>
<%@ include file="footer.jsp" %>
</body>
</html>

View File

@ -1,5 +1,5 @@
<!DOCTYPE html> <%@ page contentType="text/html;charset=UTF-8" %>
<div> <div>
<hr> <hr>
<h3>All rights reserved 2024</h3> <small>${requestScope.wordBundle.getWord("all-rights-reserved")} 2024</small>
</div> </div>

View File

@ -1,14 +1,9 @@
<%@ page contentType="text/html;charset=UTF-8" %> <%@ page contentType="text/html;charset=UTF-8" %>
<div> <div>
<h3>Charm <3</h3> <h3>Charm <3</h3>
<form method="post" action="/lang">
<button type="submit" name="lang" value="ru">ru</button>
<button type="submit" name="lang" value="en">en</button>
</form>
<hr> <hr>
<form action="/lang" method="post">
<button name="lang" value="ru">Русский</button>
<button name="lang" value="en">English</button>
</form>
<h3>My current language: ${cookie["lang"].value}</h3>
<hr>
</div> </div>

View File

@ -1,66 +1,59 @@
<%@ page contentType="text/html;charset=UTF-8" %> <%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html lang="en"> <html lang="en">
<head> <head>
<title>Charm Profile</title> <title>Charm Profile</title>
</head> <%@ include file="style.html" %>
<body> </head>
<%@ include file="header.jsp" %> <body>
<div> <%@ include file="header.jsp" %>
<form method="post" action="/profile"> <div>
<c:if test="${requestScope.profile.id != null}"> <form method="post" action="/profile">
<input type="hidden" name="_method" value="put"/> <input type="hidden" name="_method" value="put"/>
</c:if> <input type="hidden" name="id" value="${requestScope.profile.id}">
<input type="hidden" name="id" value="${requestScope.profile.id}"> <table>
<table> <tr>
<tr> <td><h3>${requestScope.wordBundle.getWord("email")}</h3></td>
<td><h3>${requestScope.wordBundle.getWord("email")}</h3></td> <td><a href="/email?id=${requestScope.profile.id}">${requestScope.profile.email}</a></td>
<td><input type="email" name="email" value="${requestScope.profile.email}"></td> </tr>
</tr> <tr>
<tr> <td><h3>${requestScope.wordBundle.getWord("name")}</h3></td>
<td><h3>${requestScope.wordBundle.getWord("name")}</h3></td> <td><input type="text" name="name" value="${requestScope.profile.name}"></td>
<td><input type="text" name="name" value="${requestScope.profile.name}"></td> </tr>
</tr> <tr>
<tr> <td><h3>${requestScope.wordBundle.getWord("surname")}</h3></td>
<td><h3>${requestScope.wordBundle.getWord("surname")}</h3></td> <td><input type="text" name="surname" value="${requestScope.profile.surname}"></td>
<td><input type="text" name="surname" value="${requestScope.profile.surname}"></td> </tr>
</tr> <tr>
<tr> <td><h3>${requestScope.wordBundle.getWord("birth-date")}</h3></td>
<td><h3>${requestScope.wordBundle.getWord("birth-date")}</h3></td> <td><input type="date" name="birthDate" value="${requestScope.profile.birthDate}"></td>
<td><input type="date" name="birthDate" value="${requestScope.profile.birthDate}"></td> </tr>
</tr> <tr>
<tr> <td><h3>${requestScope.wordBundle.getWord("about")}</h3></td>
<td><h3>${requestScope.wordBundle.getWord("age")}</h3></td> <td><input type="text" name="about" value="${requestScope.profile.about}"></td>
<td><h3>${requestScope.profile.age}</h3></td> </tr>
</tr> <tr>
<tr> <td><h3>${requestScope.wordBundle.getWord("gender")}</h3></td>
<td><h3>${requestScope.wordBundle.getWord("about")}</h3></td> <td>
<td><input type="text" name="about" value="${requestScope.profile.about}"></td> <select name="gender">
</tr> <option value="${requestScope.profile.gender}" selected hidden>
<tr> ${requestScope.wordBundle.getWord(requestScope.profile.gender)}
<td><h3>${requestScope.wordBundle.getWord("gender")}</h3></td> </option>
<td> <c:forEach var="gender" items="${applicationScope.genders}">
<select name="gender"> <option value="${gender}">${requestScope.wordBundle.getWord(gender)}</option>
<option value="${requestScope.profile.gender}" selected hidden> </c:forEach>
${requestScope.profile.gender} </select>
</option> </td>
<c:forEach var="gender" items="${applicationScope.genders}"> </tr>
<option value="${gender}">${requestScope.wordBundle.getWord(gender)}</option> </table>
</c:forEach> <button type="submit">${requestScope.wordBundle.getWord("save")}</button>
</select> </form>
</td> <form method="post" action="/registration">
</tr> <input type="hidden" name="_method" value="delete"/>
</table> <input type="hidden" name="id" value="${requestScope.profile.id}">
<button type="submit">${requestScope.wordBundle.getWord("save")}</button> <button type="submit">${requestScope.wordBundle.getWord("delete")}</button>
</form> </form>
<c:if test="${requestScope.profile.id != null}"> </div>
<form method="post" action="/profile"> <%@ include file="footer.jsp" %>
<input type="hidden" name="_method" value="delete"/> </body>
<input type="hidden" name="id" value="${requestScope.profile.id}">
<button type="submit">${requestScope.wordBundle.getWord("delete")}</button>
</form>
</c:if>
</div>
<%@ include file="footer.jsp" %>
</body>
</html> </html>

View File

@ -3,12 +3,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<title>Charm Profiles</title> <title>Charm Profiles</title>
<style> <%@ include file="style.html" %>
table, td {
border: 1px solid;
border-collapse: collapse;
}
</style>
</head> </head>
<body> <body>
<%@ include file="header.jsp" %> <%@ include file="header.jsp" %>
@ -20,14 +15,30 @@
<td><h3>${requestScope.wordBundle.getWord("name")}</h3></td> <td><h3>${requestScope.wordBundle.getWord("name")}</h3></td>
<td><h3>${requestScope.wordBundle.getWord("surname")}</h3></td> <td><h3>${requestScope.wordBundle.getWord("surname")}</h3></td>
<td><h3>${requestScope.wordBundle.getWord("age")}</h3></td> <td><h3>${requestScope.wordBundle.getWord("age")}</h3></td>
<td><h3>${requestScope.wordBundle.getWord("status")}</h3></td>
</tr> </tr>
<c:forEach var="profile" items="${requestScope.profiles}"> <c:forEach var="profile" items="${requestScope.profiles}">
<tr> <tr>
<td><h3>${profile.id}</h3></td> <td><h4>${profile.id}</h4></td>
<td><h3>${profile.email}</h3></td> <td><h4>${profile.email}</h4></td>
<td><h3>${profile.name}</h3></td> <td><h4>${profile.name}</h4></td>
<td><h3>${profile.surname}</h3></td> <td><h4>${profile.surname}</h4></td>
<td><h3>${profile.age}</h3></td> <td><h4>${profile.age}</h4></td>
<td>
<form action="/profile" method="post">
<input type="hidden" name="_method" value="put"/>
<input type="hidden" name="id" value="${profile.id}">
<select name="status">
<option value="${profile.status}" selected hidden>
${requestScope.wordBundle.getWord(profile.status)}
</option>
<c:forEach var="status" items="${applicationScope.statuses}">
<option value="${status}">${requestScope.wordBundle.getWord(status)}</option>
</c:forEach>
</select>
<button type="submit">${requestScope.wordBundle.getWord("save")}</button>
</form>
</td>
</tr> </tr>
</c:forEach> </c:forEach>
</table> </table>

View File

@ -0,0 +1,26 @@
<%@ page contentType="text/html;charset=UTF-8" %>
<html lang="en">
<head>
<title>Charm Registration</title>
<%@ include file="style.html" %>
</head>
<body>
<%@ include file="header.jsp" %>
<div>
<form method="post" action="/registration">
<table>
<tr>
<td><h3>${requestScope.wordBundle.getWord("email")}</h3></td>
<td><input type="email" name="email" placeholder="user@charm.ru"></td>
</tr>
<tr>
<td><h3>${requestScope.wordBundle.getWord("password")}</h3></td>
<td><input type="password" name="password"></td>
</tr>
</table>
<button type="submit">${requestScope.wordBundle.getWord("save")}</button>
</form>
</div>
<%@ include file="footer.jsp" %>
</body>
</html>

View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en">
<style>
.saveImage {
width: 75px;
}
input {
width: 500px;
font-size: 15px;
font-family: "Roboto", sans-serif;
}
select {
width: 500px;
font-size: 15px;
font-family: "Roboto", sans-serif;
}
img {
max-width: 500px;
max-height: 500px;
}
textarea {
resize: none;
width: 500px;
font-size: 15px;
font-family: "Roboto", sans-serif;
}
div {
text-align: center;
font-family: "Roboto", sans-serif;
}
table {
margin-left: auto;
margin-right: auto;
border-collapse: collapse;
}
td {
text-align: left;
padding: 10px;
}
tr {
text-align: center;
padding: 10px;
border: 1px solid;
border-top: none;
border-left: none;
border-right: none;
}
</style>
</html>

View File

@ -20,4 +20,13 @@
xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee
https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd" https://jakarta.ee/xml/ns/jakartaee/web-app_6_0.xsd"
version="6.0"> version="6.0">
<error-page>
<error-code>404</error-code>
<location>/WEB-INF/jsp/error404.jsp</location>
</error-page>
<error-page>
<error-code>400</error-code>
<location>/WEB-INF/jsp/error400.jsp</location>
</error-page>
</web-app> </web-app>

View File

@ -1,13 +1,21 @@
about=About about=About
active=Active
age=Age age=Age
all-rights-reserved=All rights reserved all-rights-reserved=All rights reserved
birth-date=Birth date birth-date=Birth date
confirm-password=Confirm password
delete=Delete delete=Delete
email=Email email=Email
email-warning=Changing your mail may have negative effects
female=Female female=Female
gender=Gender gender=Gender
inactive=Inactive
male=Male male=Male
name=Name name=Name
other=Other other=Other
page-bad-request=Bad request
page-not-found=Page not found
password=Password
save=Save save=Save
status=Status
surname=Surname surname=Surname

View File

@ -1,13 +1,21 @@
about=About about=About
active=Active
age=Age age=Age
all-rights-reserved=All rights reserved all-rights-reserved=All rights reserved
birth-date=Birth date birth-date=Birth date
confirm-password=Confirm password
delete=Delete delete=Delete
email=Email email=Email
email-warning=Changing your mail may have negative effects
female=Female female=Female
gender=Gender gender=Gender
inactive=Inactive
male=Male male=Male
name=Name name=Name
other=Other other=Other
page-bad-request=Bad request
page-not-found=Page not found
password=Password
save=Save save=Save
status=Status
surname=Surname surname=Surname

View File

@ -1,13 +1,21 @@
about=\u041E \u0441\u0435\u0431\u0435 about=\u041E \u0441\u0435\u0431\u0435
active=\u0410\u043A\u0442\u0438\u0432\u043D\u044B\u0439
age=\u0412\u043E\u0437\u0440\u0430\u0441\u0442 age=\u0412\u043E\u0437\u0440\u0430\u0441\u0442
all-rights-reserved=\u0412\u0441\u0435 \u043F\u0440\u0430\u0432\u0430 \u0437\u0430\u0449\u0438\u0449\u0435\u043D\u044B all-rights-reserved=\u0412\u0441\u0435 \u043F\u0440\u0430\u0432\u0430 \u0437\u0430\u0449\u0438\u0449\u0435\u043D\u044B
birth-date=\u0414\u0430\u0442\u0430 \u0440\u043E\u0436\u0434\u0435\u043D\u0438\u044F birth-date=\u0414\u0430\u0442\u0430 \u0440\u043E\u0436\u0434\u0435\u043D\u0438\u044F
confirm-password=\u041F\u043E\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u0435 \u043F\u0430\u0440\u043E\u043B\u044C
delete=\u0423\u0434\u0430\u043B\u0438\u0442\u044C delete=\u0423\u0434\u0430\u043B\u0438\u0442\u044C
email=\u041F\u043E\u0447\u0442\u0430 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
female=\u0416\u0435\u043D\u0449\u0438\u043D\u0430 female=\u0416\u0435\u043D\u0449\u0438\u043D\u0430
gender=\u041F\u043E\u043B gender=\u041F\u043E\u043B
inactive=\u041D\u0435\u0430\u043A\u0442\u0438\u0432\u043D\u044B\u0439
male=\u041C\u0443\u0436\u0447\u0438\u043D\u0430 male=\u041C\u0443\u0436\u0447\u0438\u043D\u0430
name=\u0418\u043C\u044F name=\u0418\u043C\u044F
other=\u0414\u0440\u0443\u0433\u043E\u0435 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
save=\u0421\u043E\u0445\u0440\u0430\u043D\u0438\u0442\u044C 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

View File

@ -0,0 +1,52 @@
package ru.charm.back.controller;
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 ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.dto.ProfileUpdateDto;
import ru.charm.back.mapper.RequestToProfileUpdateDtoMapper;
import ru.charm.back.model.Gender;
import ru.charm.back.model.Profile;
import ru.charm.back.model.exception.DuplicateEmailException;
import ru.charm.back.service.ProfileService;
import java.io.IOException;
import java.time.LocalDate;
import java.util.Optional;
import static jakarta.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
@WebServlet("/email")
public class EmailController extends HttpServlet {
private final ProfileService service = ProfileService.getInstance();
private final RequestToProfileUpdateDtoMapper requestToProfileUpdateDtoMapper = RequestToProfileUpdateDtoMapper.getInstance();
@Override
protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String sId = req.getParameter("id");
String forwardUri = "/notFound";
if (sId != null) {
Optional<ProfileGetDto> optProfileGetDto = service.findById(Long.parseLong(sId));
if (optProfileGetDto.isPresent()) {
req.setAttribute("profile", optProfileGetDto.get());
forwardUri = "/WEB-INF/jsp/email.jsp";
}
}
req.getRequestDispatcher(forwardUri).forward(req, resp);
}
@Override
protected void doPut (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
ProfileUpdateDto dto = requestToProfileUpdateDtoMapper.map(req, new ProfileUpdateDto());
try {
service.update(dto);
resp.sendRedirect(String.format("/profile?id=%s", dto.getId()));
} catch (DuplicateEmailException e) {
resp.sendError(SC_BAD_REQUEST);
}
}
}

View File

@ -6,73 +6,47 @@ import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse; import jakarta.servlet.http.HttpServletResponse;
import ru.charm.back.dto.ProfileGetDto; import ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.model.Gender; import ru.charm.back.dto.ProfileUpdateDto;
import ru.charm.back.model.Profile; import ru.charm.back.mapper.RequestToProfileUpdateDtoMapper;
import ru.charm.back.service.ProfileService; import ru.charm.back.service.ProfileService;
import java.io.IOException; import java.io.IOException;
import java.time.LocalDate;
import java.util.Optional; import java.util.Optional;
import static jakarta.servlet.http.HttpServletResponse.SC_NOT_FOUND;
@WebServlet("/profile") @WebServlet("/profile")
public class ProfileController extends HttpServlet { public class ProfileController extends HttpServlet {
private final ProfileService service = ProfileService.getInstance(); private final ProfileService service = ProfileService.getInstance();
private final RequestToProfileUpdateDtoMapper requestToProfileUpdateDtoMapper = RequestToProfileUpdateDtoMapper.getInstance();
@Override @Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String sId = req.getParameter("id"); String sId = req.getParameter("id");
String forwardUri = "/notFound"; String forwardUri = null;
if (sId != null) { if (sId != null) {
Optional<ProfileGetDto> optProfileGetDto = service.findById(Long.parseLong(sId)); Optional<ProfileGetDto> optProfileDto = service.findById(Long.parseLong(sId));
if (optProfileGetDto.isPresent()) { if (optProfileDto.isPresent()) {
req.setAttribute("profile", optProfileGetDto.get()); req.setAttribute("profile", optProfileDto.get());
forwardUri = "/WEB-INF/jsp/profile.jsp"; forwardUri = "/WEB-INF/jsp/profile.jsp";
} }
} else { } else {
req.setAttribute("profiles", service.findAll()); req.setAttribute("profiles", service.findAll());
forwardUri = "/WEB-INF/jsp/profiles.jsp"; forwardUri = "/WEB-INF/jsp/profiles.jsp";
} }
req.getRequestDispatcher(forwardUri).forward(req, resp); if (forwardUri == null) {
} resp.sendError(SC_NOT_FOUND);
} else {
@Override req.getRequestDispatcher(forwardUri).forward(req, resp);
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Profile profile = getProfile(req);
service.save(profile);
resp.sendRedirect(String.format("/profile?id=%s", profile.getId()));
}
@Override
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Profile profile = getProfile(req);
service.update(profile);
resp.sendRedirect(String.format("/profile?id=%s", profile.getId()));
}
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String sId = req.getParameter("id");
if (!sId.isBlank()) {
service.delete(Long.parseLong(sId));
} }
resp.sendRedirect("/registration");
} }
private Profile getProfile(HttpServletRequest req) { @Override
Profile profile = new Profile(); protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws IOException {
String sId = req.getParameter("id"); ProfileUpdateDto dto = requestToProfileUpdateDtoMapper.map(req);
service.update(dto);
if (!sId.isBlank()) { String referer = req.getHeader("referer");
profile.setId(Long.parseLong(sId)); resp.sendRedirect(referer);
}
profile.setEmail(req.getParameter("email"));
profile.setName(req.getParameter("name"));
profile.setSurname(req.getParameter("surname"));
profile.setAbout(req.getParameter("about"));
profile.setGender(Gender.valueOf(req.getParameter("gender")));
profile.setBirthday(LocalDate.parse(req.getParameter("birthDate")));
return profile;
} }
} }

View File

@ -2,41 +2,49 @@ package ru.charm.back.dao;
import ru.charm.back.model.Gender; import ru.charm.back.model.Gender;
import ru.charm.back.model.Profile; import ru.charm.back.model.Profile;
import ru.charm.back.model.Status;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
import java.util.stream.Collectors;
public class ProfileDao { public class ProfileDao {
private final ConcurrentHashMap<Long, Profile> storage;
private final AtomicLong idStorage;
private static final ProfileDao INSTANCE = new ProfileDao(); private static final ProfileDao INSTANCE = new ProfileDao();
private final AtomicLong idStorage;
private final ConcurrentHashMap<Long, Profile> storage;
private ProfileDao() { private ProfileDao() {
this.storage = new ConcurrentHashMap<>(); this.storage = new ConcurrentHashMap<>();
Profile profile = new Profile(); Profile profile = new Profile();
profile.setId(1L); profile.setId(1L);
profile.setEmail("ivanov@mail.ru"); profile.setEmail("ivanov@mail.ru");
profile.setPassword("123");
profile.setName("Ivan"); profile.setName("Ivan");
profile.setSurname("Ivanov"); profile.setSurname("Ivanov");
profile.setBirthDate(LocalDate.parse("2001-12-03"));
profile.setAbout("I am QA"); profile.setAbout("I am QA");
profile.setGender(Gender.MALE); profile.setGender(Gender.MALE);
profile.setBirthday(LocalDate.parse("2000-01-01")); profile.setStatus(Status.ACTIVE);
this.storage.put(1L, profile); this.storage.put(1L, profile);
Profile profile1 = new Profile(); Profile profile1 = new Profile();
profile1.setId(2L); profile1.setId(2L);
profile1.setEmail("sidorova@mail.ru"); profile1.setEmail("sidorova@mail.ru");
profile1.setPassword("456");
profile1.setName("Elena"); profile1.setName("Elena");
profile1.setSurname("Sidorova"); profile1.setSurname("Sidorova");
profile1.setBirthDate(LocalDate.parse("1999-09-01"));
profile1.setAbout("I am Java Dev"); profile1.setAbout("I am Java Dev");
profile1.setBirthday(LocalDate.parse("2000-01-01"));
profile1.setGender(Gender.FEMALE); profile1.setGender(Gender.FEMALE);
profile1.setStatus(Status.INACTIVE);
this.storage.put(2L, profile1); this.storage.put(2L, profile1);
this.idStorage = new AtomicLong(2L); this.idStorage = new AtomicLong(3L);
} }
public static ProfileDao getInstance() { public static ProfileDao getInstance() {
@ -44,30 +52,32 @@ public class ProfileDao {
} }
public Profile save(Profile profile) { public Profile save(Profile profile) {
long id = idStorage.incrementAndGet(); profile.setId(idStorage.getAndIncrement());
profile.setId(id); storage.put(profile.getId(), profile);
storage.put(id, profile);
return profile; return profile;
} }
public Optional<Profile> findById(Long id) { public Optional<Profile> findById(Long id) {
if (id == null) return Optional.empty();
return Optional.ofNullable(storage.get(id)); return Optional.ofNullable(storage.get(id));
} }
public void update(Profile profile) {
Long id = profile.getId();
if (id == null) {
return;
}
storage.put(id, profile);
}
public List<Profile> findAll() { public List<Profile> findAll() {
return new ArrayList<>(storage.values()); return new ArrayList<>(storage.values());
} }
public Boolean delete(Long id) { public void update(Profile profile) {
Long id = profile.getId();
if (id == null) return;
storage.put(id, profile);
}
public boolean delete(Long id) {
if (id == null) return false;
return storage.remove(id) != null; return storage.remove(id) != null;
} }
public Set<String> getAllEmails() {
return storage.values().stream().map(Profile::getEmail).collect(Collectors.toSet());
}
} }

View File

@ -1,34 +1,20 @@
package ru.charm.back.dto; package ru.charm.back.dto;
import ru.charm.back.model.Gender; import ru.charm.back.model.Gender;
import ru.charm.back.model.Status;
import java.time.LocalDate; import java.time.LocalDate;
public class ProfileGetDto { public class ProfileGetDto {
private Long id; private Long id;
private String email; private String email;
private String name; private String name;
private String surname; private String surname;
private LocalDate birthDate;
private Integer age;
private String about; private String about;
private Gender gender; private Gender gender;
private LocalDate birthDate; private Status status;
private int age;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public LocalDate getBirthDate() {
return birthDate;
}
public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}
public Long getId() { public Long getId() {
return id; return id;
@ -62,6 +48,22 @@ public class ProfileGetDto {
this.surname = surname; this.surname = surname;
} }
public LocalDate getBirthDate() {
return birthDate;
}
public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getAbout() { public String getAbout() {
return about; return about;
} }
@ -78,14 +80,11 @@ public class ProfileGetDto {
this.gender = gender; this.gender = gender;
} }
@Override public Status getStatus() {
public String toString() { return status;
return "Profile{" + }
"id=" + id +
", email='" + email + '\'' + public void setStatus(Status status) {
", name='" + name + '\'' + this.status = status;
", surname='" + surname + '\'' +
", about='" + about + '\'' +
'}';
} }
} }

View File

@ -0,0 +1,91 @@
package ru.charm.back.dto;
import ru.charm.back.model.Gender;
import ru.charm.back.model.Status;
import java.time.LocalDate;
public class ProfileUpdateDto {
private Long id;
private String email;
private String password;
private String name;
private String surname;
private LocalDate birthDate;
private String about;
private Gender gender;
private Status status;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSurname() {
return surname;
}
public void setSurname(String surname) {
this.surname = surname;
}
public LocalDate getBirthDate() {
return birthDate;
}
public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}
public String getAbout() {
return about;
}
public void setAbout(String about) {
this.about = about;
}
public Gender getGender() {
return gender;
}
public void setGender(Gender gender) {
this.gender = gender;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
}

View File

@ -0,0 +1,22 @@
package ru.charm.back.dto;
public class RegistrationDto {
private String email;
private String password;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}

View File

@ -3,4 +3,5 @@ package ru.charm.back.mapper;
public interface Mapper<From, To> { public interface Mapper<From, To> {
To map(From from); To map(From from);
To map(From from, To to);
} }

View File

@ -1,32 +0,0 @@
package ru.charm.back.mapper;
import ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.model.Profile;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class ProfileGetDtoMapper implements Mapper<Profile, ProfileGetDto> {
private static final ProfileGetDtoMapper INSTANCE = new ProfileGetDtoMapper();
public static ProfileGetDtoMapper getInstance() {
return INSTANCE;
}
@Override
public ProfileGetDto map(Profile profile) {
ProfileGetDto dto = new ProfileGetDto();
dto.setId(profile.getId());
dto.setEmail(profile.getEmail());
dto.setName(profile.getName());
dto.setSurname(profile.getSurname());
dto.setBirthDate(profile.getBirthday());
dto.setAge(Math.toIntExact(ChronoUnit.YEARS.between(profile.getBirthday(), LocalDate.now())));
dto.setGender(profile.getGender());
dto.setAbout(profile.getAbout());
return dto;
}
}

View File

@ -0,0 +1,41 @@
package ru.charm.back.mapper;
import ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.model.Profile;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
public class ProfileToProfileGetDtoMapper implements Mapper<Profile, ProfileGetDto> {
private static final ProfileToProfileGetDtoMapper INSTANCE = new ProfileToProfileGetDtoMapper();
private ProfileToProfileGetDtoMapper() {
}
public static ProfileToProfileGetDtoMapper getInstance() {
return INSTANCE;
}
@Override
public ProfileGetDto map(Profile profile) {
return map(profile, new ProfileGetDto());
}
@Override
public ProfileGetDto map(Profile profile, ProfileGetDto dto) {
dto.setId(profile.getId());
dto.setEmail(profile.getEmail());
dto.setName(profile.getName());
dto.setSurname(profile.getSurname());
dto.setBirthDate(profile.getBirthDate());
if (profile.getBirthDate() != null) {
dto.setAge(Math.toIntExact(ChronoUnit.YEARS.between(profile.getBirthDate(), LocalDate.now())));
}
dto.setAbout(profile.getAbout());
dto.setGender(profile.getGender());
dto.setStatus(profile.getStatus());
return dto;
}
}

View File

@ -0,0 +1,49 @@
package ru.charm.back.mapper;
import ru.charm.back.dto.ProfileUpdateDto;
import ru.charm.back.model.Profile;
public class ProfileUpdateDtoToProfileMapper implements Mapper<ProfileUpdateDto, Profile> {
private static final ProfileUpdateDtoToProfileMapper INSTANCE = new ProfileUpdateDtoToProfileMapper();
private ProfileUpdateDtoToProfileMapper() {
}
public static ProfileUpdateDtoToProfileMapper getInstance() {
return INSTANCE;
}
@Override
public Profile map(ProfileUpdateDto dto) {
return map(dto, new Profile());
}
@Override
public Profile map(ProfileUpdateDto dto, Profile profile) {
profile.setId(dto.getId());
if (dto.getEmail() != null) {
profile.setEmail(dto.getEmail());
}
if (dto.getName() != null) {
profile.setName(dto.getName());
}
if (dto.getSurname() != null) {
profile.setSurname(dto.getSurname());
}
if (dto.getBirthDate() != null) {
profile.setBirthDate(dto.getBirthDate());
}
if (dto.getAbout() != null) {
profile.setAbout(dto.getAbout());
}
if (dto.getGender() != null) {
profile.setGender(dto.getGender());
}
if (dto.getStatus() != null) {
profile.setStatus(dto.getStatus());
}
return profile;
}
}

View File

@ -0,0 +1,30 @@
package ru.charm.back.mapper;
import ru.charm.back.dto.RegistrationDto;
import ru.charm.back.model.Profile;
import ru.charm.back.model.Status;
public class RegistrationDtoToProfileMapper implements Mapper<RegistrationDto, Profile> {
private static final RegistrationDtoToProfileMapper INSTANCE = new RegistrationDtoToProfileMapper();
private RegistrationDtoToProfileMapper() {
}
public static RegistrationDtoToProfileMapper getInstance() {
return INSTANCE;
}
@Override
public Profile map(RegistrationDto dto) {
return map(dto, new Profile());
}
@Override
public Profile map(RegistrationDto dto, Profile profile) {
profile.setEmail(dto.getEmail());
profile.setPassword(dto.getPassword());
profile.setStatus(Status.INACTIVE);
return profile;
}
}

View File

@ -0,0 +1,55 @@
package ru.charm.back.mapper;
import jakarta.servlet.http.HttpServletRequest;
import ru.charm.back.dto.ProfileUpdateDto;
import ru.charm.back.model.Gender;
import ru.charm.back.model.Status;
import java.time.LocalDate;
import static ru.charm.back.utils.StringUtils.isBlank;
public class RequestToProfileUpdateDtoMapper implements Mapper<HttpServletRequest, ProfileUpdateDto> {
private static final RequestToProfileUpdateDtoMapper INSTANCE = new RequestToProfileUpdateDtoMapper();
private RequestToProfileUpdateDtoMapper() {
}
public static RequestToProfileUpdateDtoMapper getInstance() {
return INSTANCE;
}
@Override
public ProfileUpdateDto map(HttpServletRequest req) {
return map(req, new ProfileUpdateDto());
}
@Override
public ProfileUpdateDto map(HttpServletRequest req, ProfileUpdateDto dto) {
String id = req.getParameter("id");
if (!isBlank(id)) {
dto.setId(Long.parseLong(id));
}
String email = req.getParameter("email");
if (!isBlank(email)) {
dto.setEmail(email);
}
dto.setName(req.getParameter("name"));
dto.setSurname(req.getParameter("surname"));
String birthDate = req.getParameter("birthDate");
if (!isBlank(birthDate)) {
dto.setBirthDate(LocalDate.parse(birthDate));
}
dto.setAbout(req.getParameter("about"));
String gender = req.getParameter("gender");
if (!isBlank(gender)) {
dto.setGender(Gender.valueOf(gender));
}
String status = req.getParameter("status");
if (!isBlank(status)) {
dto.setStatus(Status.valueOf(status));
}
return dto;
}
}

View File

@ -0,0 +1,28 @@
package ru.charm.back.mapper;
import jakarta.servlet.http.HttpServletRequest;
import ru.charm.back.dto.RegistrationDto;
public class RequestToRegistrationDtoMapper implements Mapper<HttpServletRequest, RegistrationDto> {
private static final RequestToRegistrationDtoMapper INSTANCE = new RequestToRegistrationDtoMapper();
private RequestToRegistrationDtoMapper() {
}
public static RequestToRegistrationDtoMapper getInstance() {
return INSTANCE;
}
@Override
public RegistrationDto map(HttpServletRequest req) {
return map(req, new RegistrationDto());
}
@Override
public RegistrationDto map(HttpServletRequest req, RegistrationDto dto) {
dto.setEmail(req.getParameter("email"));
dto.setPassword(req.getParameter("password"));
return dto;
}
}

View File

@ -5,19 +5,13 @@ import java.time.LocalDate;
public class Profile { public class Profile {
private Long id; private Long id;
private String email; private String email;
private String password;
private String name; private String name;
private String surname; private String surname;
private LocalDate birthDate;
private String about; private String about;
private Gender gender; private Gender gender;
private LocalDate birthday; private Status status;
public LocalDate getBirthday() {
return birthday;
}
public void setBirthday(LocalDate birthday) {
this.birthday = birthday;
}
public Long getId() { public Long getId() {
return id; return id;
@ -35,6 +29,14 @@ public class Profile {
this.email = email; this.email = email;
} }
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getName() { public String getName() {
return name; return name;
} }
@ -51,6 +53,14 @@ public class Profile {
this.surname = surname; this.surname = surname;
} }
public LocalDate getBirthDate() {
return birthDate;
}
public void setBirthDate(LocalDate birthDate) {
this.birthDate = birthDate;
}
public String getAbout() { public String getAbout() {
return about; return about;
} }
@ -67,14 +77,11 @@ public class Profile {
this.gender = gender; this.gender = gender;
} }
@Override public Status getStatus() {
public String toString() { return status;
return "Profile{" +
"id=" + id +
", email='" + email + '\'' +
", name='" + name + '\'' +
", surname='" + surname + '\'' +
", about='" + about + '\'' +
'}';
} }
}
public void setStatus(Status status) {
this.status = status;
}
}

View File

@ -0,0 +1,5 @@
package ru.charm.back.model;
public enum Status {
ACTIVE, INACTIVE
}

View File

@ -0,0 +1,4 @@
package ru.charm.back.model.exception;
public class DuplicateEmailException extends RuntimeException {
}

View File

@ -2,47 +2,68 @@ package ru.charm.back.service;
import ru.charm.back.dao.ProfileDao; import ru.charm.back.dao.ProfileDao;
import ru.charm.back.dto.ProfileGetDto; import ru.charm.back.dto.ProfileGetDto;
import ru.charm.back.mapper.ProfileGetDtoMapper; import ru.charm.back.dto.ProfileUpdateDto;
import ru.charm.back.model.Profile; import ru.charm.back.dto.RegistrationDto;
import ru.charm.back.mapper.ProfileToProfileGetDtoMapper;
import ru.charm.back.mapper.ProfileUpdateDtoToProfileMapper;
import ru.charm.back.mapper.RegistrationDtoToProfileMapper;
import ru.charm.back.model.exception.DuplicateEmailException;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
public class ProfileService { public class ProfileService {
private static final ProfileService INSTANCE = new ProfileService(); private static final ProfileService INSTANCE = new ProfileService();
private final ProfileDao dao = ProfileDao.getInstance(); private final ProfileDao dao = ProfileDao.getInstance();
private final ProfileGetDtoMapper profileGetDtoMapper = ProfileGetDtoMapper.getInstance();
private final ProfileToProfileGetDtoMapper profileToProfileGetDtoMapper = ProfileToProfileGetDtoMapper.getInstance();
public static ProfileService getInstance() { private final ProfileUpdateDtoToProfileMapper profileUpdateDtoToProfileMapper = ProfileUpdateDtoToProfileMapper.getInstance();
return INSTANCE;
private final RegistrationDtoToProfileMapper registrationDtoToProfileMapper = RegistrationDtoToProfileMapper.getInstance();
private ProfileService() {
} }
public Profile save(Profile profile) { public static ProfileService getInstance() {
return dao.save(profile); return INSTANCE;
}
public Long save(RegistrationDto dto) {
return dao.save(registrationDtoToProfileMapper.map(dto)).getId();
} }
public Optional<ProfileGetDto> findById(Long id) { public Optional<ProfileGetDto> findById(Long id) {
if (id == null) { return dao.findById(id).map(profileToProfileGetDtoMapper::map);
return Optional.empty();
}
return dao.findById(id).map(profileGetDtoMapper::map);
}
public Boolean delete(Long id) {
if (id == null) {
return false;
}
return dao.delete(id);
}
public void update(Profile profile) {
dao.update(profile);
} }
public List<ProfileGetDto> findAll() { public List<ProfileGetDto> findAll() {
return dao.findAll().stream().map(profileGetDtoMapper::map).toList(); return dao.findAll().stream().map(profileToProfileGetDtoMapper::map).toList();
} }
}
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

@ -0,0 +1,7 @@
package ru.charm.back.utils;
public class StringUtils {
public static boolean isBlank(String str) {
return str == null || str.isBlank();
}
}