diff --git a/backend/pom.xml b/backend/pom.xml
index e9058b4..2257d72 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -92,6 +92,15 @@
imgscalr-lib
4.2
+
+
+ org.springframework.boot
+ spring-boot-starter-mail
+ 2.7.0
+
+
+
+
diff --git a/backend/setup/Swappee.postman_collection.json b/backend/setup/Swappee.postman_collection.json
index 8f85ca3..d1c1c8e 100644
--- a/backend/setup/Swappee.postman_collection.json
+++ b/backend/setup/Swappee.postman_collection.json
@@ -1,8 +1,9 @@
{
"info": {
- "_postman_id": "4614ace9-62a7-4359-8375-1efb42aa8aed",
+ "_postman_id": "af01e8d4-2ded-4c87-9231-36fbfbddd809",
"name": "Swappee",
- "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
+ "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
+ "_exporter_id": "17174728"
},
"item": [
{
@@ -140,6 +141,47 @@
},
"response": []
},
+ {
+ "name": "createAuthenticationToken Tauple",
+ "event": [
+ {
+ "listen": "test",
+ "script": {
+ "exec": [
+ "var data = pm.response.json();\r",
+ "console.log(data.token);\r",
+ "pm.globals.set(\"token\", data.token);"
+ ],
+ "type": "text/javascript"
+ }
+ }
+ ],
+ "request": {
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"username\": \"tauple\",\r\n \"password\": \"apple\"\r\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{SwappeeUrl}}/api/login/authenticate",
+ "host": [
+ "{{SwappeeUrl}}"
+ ],
+ "path": [
+ "api",
+ "login",
+ "authenticate"
+ ]
+ }
+ },
+ "response": []
+ },
{
"name": "refreshAndGetAuthenticationToken",
"request": {
@@ -323,7 +365,7 @@
"method": "GET",
"header": [],
"url": {
- "raw": "{{SwappeeUrl}}/api/public/user/MarcusTXK",
+ "raw": "{{SwappeeUrl}}/api/public/user/lyuzher",
"host": [
"{{SwappeeUrl}}"
],
@@ -331,7 +373,7 @@
"api",
"public",
"user",
- "MarcusTXK"
+ "lyuzher"
]
}
},
@@ -374,13 +416,13 @@
"formdata": [
{
"key": "userDTO",
- "value": "{\n\t\"id\": \"\",\n\t\"firstName\": \"New Guy\",\n\t\"lastName\": \"Tang\",\n\t\"username\": \"NewGuy\",\n\t\"password\": \"test\",\n\t\"email\": \"Guy@gmail\",\n\t\"avatar\": null,\n\t\"phone\": 98787397,\n\t\"emailOnly\": false,\n\t\"score\": 0,\n\t\"totalTraded\": 0\n}",
+ "value": "{\n\t\"id\": \"\",\n\t\"firstName\": \"Taufiq\",\n\t\"lastName\": \"Abdul Rahman\",\n\t\"username\": \"taubar\",\n\t\"password\": \"password\",\n\t\"email\": \"taufiq.b.ar@gmail.com\",\n\t\"avatar\": null,\n\t\"phone\": 91919191,\n\t\"emailOnly\": false,\n\t\"score\": 0,\n\t\"totalTraded\": 0\n\n}",
"type": "text"
},
{
"key": "avatar",
"type": "file",
- "src": "/C:/Users/Marcus Tang/Pictures/ProfilePics/Charmander.jpg"
+ "src": "/C:/Users/User/Pictures/index.jpg"
}
]
},
@@ -498,13 +540,13 @@
"formdata": [
{
"key": "itemDTO",
- "value": "{\n\t\"id\": \"null\",\n\t\"userId\": \"1\",\n\t\"status\": \"OPEN\",\n\t\"name\": \"Samsung Galaxy Note 10+\",\n\t\"description\": \"Its big\",\n\t\"brand\": \"Samsung\",\n\t\"new\": \"false\",\n\t\"category\": \"Phones\",\n\t\"strict\": \"true\",\n\t\"likes\": \"2\",\n\t\"preferredCats\": [\"Phone\", \"Game\", \"Car\"],\n\t\"preferredItems\": [{\n\t\t\"name\": \"1\",\n\t\t\"category\": \"1\",\n\t\t\"brand\": \"1\",\n\t\t\"new\": \"true\"\n\t}, {\n\t\t\"name\": \"2\",\n\t\t\"category\": \"2\",\n\t\t\"brand\": \"2\",\n\t\t\"new\": \"true\"\n\t}],\n\t\"itemHistory\": []\n}",
+ "value": "{\n\t\"id\": \"14\",\n\t\"userId\": \"4\",\n\t\"status\": \"OPEN\",\n\t\"name\": \"Samsung Galaxy Note 10+\",\n\t\"description\": \"Its big\",\n\t\"brand\": \"Samsung\",\n\t\"new\": \"false\",\n\t\"category\": \"Phones\",\n\t\"strict\": \"true\",\n\t\"likes\": \"2\",\n\t\"preferredCats\": [\"Phone\", \"Game\", \"Car\"],\n\t\"preferredItems\": [{\n\t\t\"name\": \"1\",\n\t\t\"category\": \"1\",\n\t\t\"brand\": \"1\",\n\t\t\"new\": \"true\"\n\t}, {\n\t\t\"name\": \"2\",\n\t\t\"category\": \"2\",\n\t\t\"brand\": \"2\",\n\t\t\"new\": \"true\"\n\t}],\n\t\"itemHistory\": []\n}",
"type": "text"
},
{
"key": "photos",
"type": "file",
- "src": "/C:/Users/Marcus Tang/Pictures/ProfilePics/Charmander original.png"
+ "src": "/C:/Users/User/Pictures/index.jpg"
},
{
"key": "descriptions",
@@ -514,7 +556,7 @@
{
"key": "photos",
"type": "file",
- "src": "/C:/Users/Marcus Tang/Pictures/ProfilePics/Charmander.jpg"
+ "src": "/C:/Users/User/Pictures/index.jpg"
}
]
},
@@ -626,7 +668,7 @@
"method": "GET",
"header": [],
"url": {
- "raw": "{{SwappeeUrl}}/api/private/user/5",
+ "raw": "{{SwappeeUrl}}/api/private/user/4",
"host": [
"{{SwappeeUrl}}"
],
@@ -634,7 +676,7 @@
"api",
"private",
"user",
- "5"
+ "4"
]
}
},
@@ -713,6 +755,272 @@
"response": []
}
]
+ },
+ {
+ "name": "RequestPrivateApiController",
+ "item": [
+ {
+ "name": "getRequestsToUser",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "{{SwappeeUrl}}/api/private/request/owner",
+ "host": [
+ "{{SwappeeUrl}}"
+ ],
+ "path": [
+ "api",
+ "private",
+ "request",
+ "owner"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "getRequestsFromUser",
+ "request": {
+ "method": "GET",
+ "header": [],
+ "url": {
+ "raw": "{{SwappeeUrl}}/api/private/request/trader",
+ "host": [
+ "{{SwappeeUrl}}"
+ ],
+ "path": [
+ "api",
+ "private",
+ "request",
+ "trader"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "createRequest",
+ "request": {
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"id\": \"10\",\r\n \"ownerId\": \"3\",\r\n \"traderId\": \"4\",\r\n \"ownerItemId\": \"6\",\r\n \"traderItemId\": \"5\",\r\n \"status\": \"PENDING\",\r\n \"remarks\": \"cool shoe\",\r\n \"ownerReviewed\": \"false\",\r\n \"traderReviewed\": \"false\",\r\n \"ownerHidden\": \"false\",\r\n \"traderHidden\": \"false\"\r\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{SwappeeUrl}}/api/private/request",
+ "host": [
+ "{{SwappeeUrl}}"
+ ],
+ "path": [
+ "api",
+ "private",
+ "request"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "updateRequest",
+ "request": {
+ "method": "PUT",
+ "header": [],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"id\": \"5\",\r\n \"ownerId\": \"3\",\r\n \"traderId\": \"4\",\r\n \"ownerItemId\": \"6\",\r\n \"traderItemId\": \"5\",\r\n \"status\": \"PENDING\",\r\n \"remarks\": \"awesome shoe\",\r\n \"ownerReviewed\": \"false\",\r\n \"traderReviewed\": \"false\",\r\n \"ownerHidden\": \"false\",\r\n \"traderHidden\": \"false\"\r\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{SwappeeUrl}}/api/private/request/5",
+ "host": [
+ "{{SwappeeUrl}}"
+ ],
+ "path": [
+ "api",
+ "private",
+ "request",
+ "5"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "updateRequestStatus",
+ "request": {
+ "method": "PUT",
+ "header": [],
+ "body": {
+ "mode": "raw",
+ "raw": "",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{SwappeeUrl}}/api/private/request/5/ACCEPTED",
+ "host": [
+ "{{SwappeeUrl}}"
+ ],
+ "path": [
+ "api",
+ "private",
+ "request",
+ "5",
+ "ACCEPTED"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "deleteRequest",
+ "request": {
+ "method": "DELETE",
+ "header": [],
+ "body": {
+ "mode": "raw",
+ "raw": "{\r\n \"id\": \"1\",\r\n \"ownerId\": \"3\",\r\n \"traderId\": \"4\",\r\n \"ownerItemId\": \"6\",\r\n \"traderItemId\": \"5\",\r\n \"status\": \"PENDING\",\r\n \"remarks\": \"nice shoe\",\r\n \"ownerReviewed\": \"false\",\r\n \"traderReviewed\": \"false\",\r\n \"ownerHidden\": \"false\",\r\n \"traderHidden\": \"false\"\r\n}",
+ "options": {
+ "raw": {
+ "language": "json"
+ }
+ }
+ },
+ "url": {
+ "raw": "{{SwappeeUrl}}/api/private/request/5",
+ "host": [
+ "{{SwappeeUrl}}"
+ ],
+ "path": [
+ "api",
+ "private",
+ "request",
+ "5"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Mail",
+ "item": [
+ {
+ "name": "createEmailRequest",
+ "request": {
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "formdata",
+ "formdata": [
+ {
+ "key": "to",
+ "value": "taufiq.b.ar@gmail.com",
+ "type": "text"
+ }
+ ]
+ },
+ "url": {
+ "raw": "{{SwappeeUrl}}/api/public/mail/reset-password",
+ "host": [
+ "{{SwappeeUrl}}"
+ ],
+ "path": [
+ "api",
+ "public",
+ "mail",
+ "reset-password"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
+ },
+ {
+ "name": "Password Reset",
+ "item": [
+ {
+ "name": "sendPasswordResetRequest",
+ "request": {
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "formdata",
+ "formdata": [
+ {
+ "key": "email",
+ "value": "tau.bar.coding@gmail.com",
+ "type": "text"
+ }
+ ]
+ },
+ "url": {
+ "raw": "{{SwappeeUrl}}/api/public/password/reset",
+ "host": [
+ "{{SwappeeUrl}}"
+ ],
+ "path": [
+ "api",
+ "public",
+ "password",
+ "reset"
+ ]
+ }
+ },
+ "response": []
+ },
+ {
+ "name": "resetPassword",
+ "request": {
+ "method": "POST",
+ "header": [],
+ "body": {
+ "mode": "formdata",
+ "formdata": [
+ {
+ "key": "password",
+ "value": "apple",
+ "type": "text"
+ },
+ {
+ "key": "token",
+ "value": "30fb03e8-23c7-4fac-915c-796d390daca9",
+ "type": "text"
+ }
+ ]
+ },
+ "url": {
+ "raw": "{{SwappeeUrl}}/api/public/password/reset_password",
+ "host": [
+ "{{SwappeeUrl}}"
+ ],
+ "path": [
+ "api",
+ "public",
+ "password",
+ "reset_password"
+ ]
+ }
+ },
+ "response": []
+ }
+ ]
}
],
"auth": {
diff --git a/backend/src/main/java/com/swappee/controller/privateapi/user/UserPrivateApiController.java b/backend/src/main/java/com/swappee/controller/privateapi/user/UserPrivateApiController.java
index f81ef54..631d0da 100644
--- a/backend/src/main/java/com/swappee/controller/privateapi/user/UserPrivateApiController.java
+++ b/backend/src/main/java/com/swappee/controller/privateapi/user/UserPrivateApiController.java
@@ -3,7 +3,6 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Preconditions;
-import com.swappee.model.item.ItemDTO;
import com.swappee.model.user.UserDTO;
import com.swappee.model.wrapper.ContentResult;
import com.swappee.service.user.UserService;
diff --git a/backend/src/main/java/com/swappee/controller/publicapi/mail/MailPublicApiController.java b/backend/src/main/java/com/swappee/controller/publicapi/mail/MailPublicApiController.java
new file mode 100644
index 0000000..6773c38
--- /dev/null
+++ b/backend/src/main/java/com/swappee/controller/publicapi/mail/MailPublicApiController.java
@@ -0,0 +1,56 @@
+package com.swappee.controller.publicapi.mail;
+
+import com.google.common.base.Preconditions;
+import com.swappee.controller.publicapi.item.ItemPublicApiController;
+import com.swappee.model.wrapper.ContentResult;
+import com.swappee.utils.exception.UserFriendlyMessage;
+import com.swappee.service.mail.EmailService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.mail.SendFailedException;
+
+/**
+ * Public REST controller for sending emails. Kept here as an example.
+ *
+ */
+
+@RestController
+@RequestMapping("/api/public/mail")
+public class MailPublicApiController {
+
+ public static String DEFAULT_SUBJECT = "Hello from Swappee!";
+ public static String DEFAULT_TEXT = "Welcome to Swappee!";
+
+ private static final Logger logger = LoggerFactory.getLogger(ItemPublicApiController.class);
+ @Autowired
+ EmailService emailService;
+
+ @PostMapping("/send")
+ public ResponseEntity createEmailRequest(@RequestParam("to") String to) {
+ logger.info("Start createEmailRequest - to: {}", to);
+ ContentResult contentResult = new ContentResult();
+ contentResult.setIsSuccess(true);
+ contentResult.setMessage(UserFriendlyMessage.EMAIL_SEND_SUCCEED);
+ HttpStatus httpStatus = HttpStatus.OK;
+ try {
+ Preconditions.checkNotNull(to);
+ emailService.sendEmail(to, DEFAULT_SUBJECT, DEFAULT_TEXT);
+ } catch (SendFailedException sfe) {
+ logger.error("Error in sendEmail():", sfe);
+ contentResult.setIsSuccess(false);
+ contentResult.setMessage(UserFriendlyMessage.EMAIL_SEND_FAILED);
+ httpStatus = HttpStatus.FAILED_DEPENDENCY;
+ }
+ logger.info("End createEmailRequest");
+ return new ResponseEntity<>(contentResult, httpStatus);
+ }
+
+}
diff --git a/backend/src/main/java/com/swappee/controller/securityapi/PasswordController.java b/backend/src/main/java/com/swappee/controller/securityapi/PasswordController.java
new file mode 100644
index 0000000..7ed4fca
--- /dev/null
+++ b/backend/src/main/java/com/swappee/controller/securityapi/PasswordController.java
@@ -0,0 +1,99 @@
+package com.swappee.controller.securityapi;
+
+import com.google.common.base.Preconditions;
+import com.swappee.controller.publicapi.item.ItemPublicApiController;
+import com.swappee.model.user.UserDTO;
+import com.swappee.model.wrapper.ContentResult;
+import com.swappee.service.mail.EmailService;
+import com.swappee.service.user.UserService;
+import com.swappee.utils.exception.BaseServiceException;
+import com.swappee.utils.exception.UserFriendlyMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.mail.SimpleMailMessage;
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.web.bind.annotation.*;
+
+
+import javax.mail.SendFailedException;
+import java.util.UUID;
+
+@RestController
+@RequestMapping("/api/public/password")
+public class PasswordController {
+ @Autowired
+ private UserService userService;
+
+ @Autowired
+ private EmailService emailService;
+
+ @Autowired
+ private BCryptPasswordEncoder bCryptPasswordEncoder;
+
+ private static final Logger logger = LoggerFactory.getLogger(ItemPublicApiController.class);
+
+ @Value("${frontend.baseurl}")
+ private String baseUrl;
+
+ @PostMapping("/reset")
+ public ResponseEntity resetPasswordRequest(@RequestParam("email") String emailAddress) {
+ logger.info("Start resetPasswordRequest - from: {}", emailAddress);
+ ContentResult contentResult = new ContentResult();
+ contentResult.setIsSuccess(true);
+ contentResult.setMessage(UserFriendlyMessage.EMAIL_SEND_SUCCEED);
+ HttpStatus httpStatus = HttpStatus.OK;
+ try {
+ Preconditions.checkNotNull(emailAddress);
+ UserDTO user = userService.findByEmail(emailAddress);
+ user.setResetToken(UUID.randomUUID().toString());
+ userService.update(user);
+ UserDTO userAfterSetToken = userService.findByEmail(emailAddress);
+ SimpleMailMessage passwordResetEmail = new SimpleMailMessage();
+ passwordResetEmail.setFrom("hello@swappee.org");
+ passwordResetEmail.setTo(userAfterSetToken.getEmail());
+
+ passwordResetEmail.setSubject("Swappee: Password Reset");
+ String url = baseUrl + "/reset_password?token=" + userAfterSetToken.getResetToken();
+ passwordResetEmail.setText("To reset your password, click the link below:\n" + url);
+ emailService.sendEmail(passwordResetEmail);
+ } catch (BaseServiceException bse) {
+ logger.error("Error in findByEmail():", bse);
+ contentResult.setIsSuccess(false);
+ contentResult.setMessage(UserFriendlyMessage.USER_GET_ONE_FAILED);
+ httpStatus = HttpStatus.NOT_FOUND;
+ } catch (SendFailedException sfe) {
+ logger.error("Error in sendEmail():", sfe);
+ contentResult.setIsSuccess(false);
+ contentResult.setMessage(UserFriendlyMessage.EMAIL_SEND_FAILED);
+ httpStatus = HttpStatus.FAILED_DEPENDENCY;
+ }
+ return new ResponseEntity<>(contentResult, httpStatus);
+ }
+
+ @PostMapping("/reset_password")
+ public ResponseEntity resetPassword(@RequestParam("token") String token, @RequestParam("password") String newPassword) {
+ logger.info("Start resetPassword - resetToken: {}", token);
+ ContentResult contentResult = new ContentResult();
+ contentResult.setIsSuccess(true);
+ contentResult.setMessage(UserFriendlyMessage.PASSWORD_RESET_SUCCEED);
+ HttpStatus httpStatus = HttpStatus.OK;
+ try {
+ Preconditions.checkNotNull(token);
+ Preconditions.checkNotNull(newPassword);
+ UserDTO user = userService.findByResetToken(token);
+ user.setPassword(newPassword);
+ user.setResetToken(null);
+ userService.update(user);
+ } catch (BaseServiceException bse) {
+ logger.error("Error in findByResetToken():", bse);
+ contentResult.setIsSuccess(false);
+ contentResult.setMessage(UserFriendlyMessage.USER_GET_ONE_FAILED);
+ httpStatus = HttpStatus.NOT_FOUND;
+ }
+ return new ResponseEntity<>(contentResult, httpStatus);
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/swappee/dao/user/UserDao.java b/backend/src/main/java/com/swappee/dao/user/UserDao.java
index ce82e41..161d7e0 100644
--- a/backend/src/main/java/com/swappee/dao/user/UserDao.java
+++ b/backend/src/main/java/com/swappee/dao/user/UserDao.java
@@ -24,4 +24,10 @@ public interface UserDao {
User update(User toUpdate) throws BaseDaoException;
User delete(User toDelete) throws BaseDaoException;
+
+ User findUserByResetToken(String resetToken) throws BaseDaoException;
+
+ User findByEmail(String email) throws BaseDaoException;
+
+ User findByResetToken(String token) throws BaseDaoException;
}
diff --git a/backend/src/main/java/com/swappee/dao/user/UserDaoImpl.java b/backend/src/main/java/com/swappee/dao/user/UserDaoImpl.java
index b260ccb..7465b1d 100644
--- a/backend/src/main/java/com/swappee/dao/user/UserDaoImpl.java
+++ b/backend/src/main/java/com/swappee/dao/user/UserDaoImpl.java
@@ -131,6 +131,7 @@ public User update(User toUpdate) throws BaseDaoException {
originalEntity.setRole(toUpdate.getRole());
originalEntity.setScore(toUpdate.getScore());
originalEntity.setTotalTraded(toUpdate.getTotalTraded());
+ originalEntity.setResetToken(toUpdate.getResetToken());
return this.userRepository.save(originalEntity);
} catch (DataAccessException dae) {
@@ -162,4 +163,49 @@ public User delete(User toDelete) throws BaseDaoException {
logger.info("End delete");
}
}
+
+ @Override
+ public User findUserByResetToken(String resetToken) throws BaseDaoException {
+ logger.info("Start findUserByResetToken - resetToken: {}", resetToken);
+ try {
+ Preconditions.checkNotNull(resetToken);
+ return userRepository.findByResetToken(resetToken).orElse(null);
+ } catch (DataAccessException dae) {
+ throw new BaseDaoException(ErrorCode.DB_ERROR_GET_ONE_FAILED, dae);
+ } catch (Exception ex) {
+ throw new BaseDaoException(ErrorCode.DB_ERROR_GENERIC, ex);
+ } finally {
+ logger.info("End findUserByResetToken");
+ }
+ }
+
+ @Override
+ public User findByEmail(String email) throws BaseDaoException {
+ logger.info("Start findByEmail - email: {}", email);
+ try {
+ Preconditions.checkNotNull(email);
+ return userRepository.findByEmail(email).orElse(null);
+ } catch (DataAccessException dae) {
+ throw new BaseDaoException(ErrorCode.DB_ERROR_GET_ONE_FAILED, dae);
+ } catch (Exception ex) {
+ throw new BaseDaoException(ErrorCode.DB_ERROR_GENERIC, ex);
+ } finally {
+ logger.info("End findByEmail");
+ }
+ }
+
+ @Override
+ public User findByResetToken(String token) throws BaseDaoException {
+ logger.info("Start findByResetToken - token: {}", token);
+ try {
+ Preconditions.checkNotNull(token);
+ return userRepository.findByResetToken(token).orElse(null);
+ } catch (DataAccessException dae) {
+ throw new BaseDaoException(ErrorCode.DB_ERROR_GET_ONE_FAILED, dae);
+ } catch (Exception ex) {
+ throw new BaseDaoException(ErrorCode.DB_ERROR_GENERIC, ex);
+ } finally {
+ logger.info("End findByResetToken");
+ }
+ }
}
diff --git a/backend/src/main/java/com/swappee/domain/user/User.java b/backend/src/main/java/com/swappee/domain/user/User.java
index 4222afb..d062c7c 100644
--- a/backend/src/main/java/com/swappee/domain/user/User.java
+++ b/backend/src/main/java/com/swappee/domain/user/User.java
@@ -73,6 +73,9 @@ public class User implements Serializable {
@Column(name = "deleted", nullable = false)
private boolean deleted;
+ @Column(name = "reset_token", length = 200, nullable = true)
+ private String resetToken;
+
public Long getId() {
return id;
}
@@ -201,6 +204,15 @@ public void setDeleted(boolean deleted) {
this.deleted = deleted;
}
+ public String getResetToken() {
+ return resetToken;
+ }
+
+ public void setResetToken(String resetToken) {
+ this.resetToken = resetToken;
+ }
+
+
@Override
public String toString() {
return "User{" +
@@ -218,6 +230,7 @@ public String toString() {
", lastModifiedDate=" + lastModifiedDate +
", version=" + version +
", deleted=" + deleted +
+ ", resetToken=" + resetToken +
'}';
}
diff --git a/backend/src/main/java/com/swappee/mapper/user/UserDTOMapper.java b/backend/src/main/java/com/swappee/mapper/user/UserDTOMapper.java
index 128cf5a..cb02d6a 100644
--- a/backend/src/main/java/com/swappee/mapper/user/UserDTOMapper.java
+++ b/backend/src/main/java/com/swappee/mapper/user/UserDTOMapper.java
@@ -3,13 +3,18 @@
import com.swappee.domain.user.User;
import com.swappee.mapper.DTOMapper;
import com.swappee.model.user.UserDTO;
+import com.swappee.service.user.UserServiceImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
@Component
public class UserDTOMapper implements DTOMapper {
+ private static final Logger logger = LoggerFactory.getLogger(UserServiceImpl.class);
@Override
public UserDTO mapEntity(User entity) {
+ logger.info("Start mapping - mapEntity: {}", entity);
if (entity == null) {
return null;
}
@@ -33,7 +38,8 @@ public UserDTO mapEntity(User entity) {
dto.setRole(entity.getRole().toString());
dto.setScore(entity.getScore());
dto.setTotalTraded(entity.getTotalTraded());
-
+ dto.setResetToken(entity.getResetToken());
+ logger.info("End mapping - mapEntity: {}", entity);
return dto;
}
@@ -61,6 +67,7 @@ public User mapDto(UserDTO dto) {
entity.setRole(User.Role.valueOf(dto.getRole()));
entity.setScore(dto.getScore());
entity.setTotalTraded(dto.getTotalTraded());
+ entity.setResetToken((dto.getResetToken()));
return entity;
}
diff --git a/backend/src/main/java/com/swappee/model/user/UserDTO.java b/backend/src/main/java/com/swappee/model/user/UserDTO.java
index 5cc55c7..ba69719 100644
--- a/backend/src/main/java/com/swappee/model/user/UserDTO.java
+++ b/backend/src/main/java/com/swappee/model/user/UserDTO.java
@@ -46,6 +46,8 @@ public class UserDTO implements UserDetails {
private Long totalTraded;
+ private String resetToken;
+
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MMM-yyyy HH:mm:ss")
@@ -221,6 +223,14 @@ public boolean isEnabled() {
return true;
}
+ public String getResetToken() {
+ return resetToken;
+ }
+
+ public void setResetToken(String resetToken) {
+ this.resetToken = resetToken;
+ }
+
@Override
public String toString() {
return "UserDTO{" +
@@ -236,6 +246,7 @@ public String toString() {
", totalTraded=" + totalTraded +
", createdDate=" + createdDate +
", lastModifiedDate=" + lastModifiedDate +
+ ", resetToken=" + resetToken +
'}';
}
}
diff --git a/backend/src/main/java/com/swappee/repository/user/UserRepository.java b/backend/src/main/java/com/swappee/repository/user/UserRepository.java
index a027c7a..250496f 100644
--- a/backend/src/main/java/com/swappee/repository/user/UserRepository.java
+++ b/backend/src/main/java/com/swappee/repository/user/UserRepository.java
@@ -12,4 +12,6 @@
@Repository
public interface UserRepository extends JpaRepository {
Optional findByUsername(String username);
+ Optional findByResetToken(String resetToken);
+ Optional findByEmail(String email);
}
diff --git a/backend/src/main/java/com/swappee/service/mail/EmailService.java b/backend/src/main/java/com/swappee/service/mail/EmailService.java
new file mode 100644
index 0000000..7f8c4a5
--- /dev/null
+++ b/backend/src/main/java/com/swappee/service/mail/EmailService.java
@@ -0,0 +1,11 @@
+package com.swappee.service.mail;
+
+
+import org.springframework.mail.SimpleMailMessage;
+
+import javax.mail.SendFailedException;
+
+public interface EmailService {
+ void sendEmail(String to, String subject, String text) throws SendFailedException;
+ void sendEmail(SimpleMailMessage email) throws SendFailedException;
+}
diff --git a/backend/src/main/java/com/swappee/service/mail/EmailServiceImpl.java b/backend/src/main/java/com/swappee/service/mail/EmailServiceImpl.java
new file mode 100644
index 0000000..58cade3
--- /dev/null
+++ b/backend/src/main/java/com/swappee/service/mail/EmailServiceImpl.java
@@ -0,0 +1,32 @@
+package com.swappee.service.mail;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.mail.SimpleMailMessage;
+import org.springframework.mail.javamail.JavaMailSender;
+import org.springframework.stereotype.Service;
+
+import javax.mail.SendFailedException;
+
+
+@Service
+public class EmailServiceImpl implements EmailService {
+ @Autowired
+ private JavaMailSender emailSender;
+
+
+ @Override
+ public void sendEmail(String to, String subject, String text) throws SendFailedException {
+ SimpleMailMessage message = new SimpleMailMessage();
+
+ message.setFrom("hello@swappee.org");
+ message.setTo(to);
+ message.setSubject(subject);
+ message.setText(text);
+ emailSender.send(message);
+ }
+
+ @Override
+ public void sendEmail(SimpleMailMessage email) throws SendFailedException {
+ emailSender.send(email);
+ }
+}
diff --git a/backend/src/main/java/com/swappee/service/user/UserService.java b/backend/src/main/java/com/swappee/service/user/UserService.java
index cf87973..162a3ab 100644
--- a/backend/src/main/java/com/swappee/service/user/UserService.java
+++ b/backend/src/main/java/com/swappee/service/user/UserService.java
@@ -32,4 +32,8 @@ public interface UserService {
UserDTO update(UserDTO toUpdate) throws BaseServiceException;
UserDTO delete(UserDTO toDelete) throws BaseServiceException;
+
+ UserDTO findByEmail(String emailAddress) throws BaseServiceException;
+
+ UserDTO findByResetToken(String token) throws BaseServiceException;
}
diff --git a/backend/src/main/java/com/swappee/service/user/UserServiceImpl.java b/backend/src/main/java/com/swappee/service/user/UserServiceImpl.java
index 08c4b79..f347a7a 100644
--- a/backend/src/main/java/com/swappee/service/user/UserServiceImpl.java
+++ b/backend/src/main/java/com/swappee/service/user/UserServiceImpl.java
@@ -292,7 +292,7 @@ public Boolean reviewUser(ReviewDTO reviewDTO) throws BaseServiceException {
@Transactional(rollbackFor = {BaseServiceException.class})
public UserDTO update(UserDTO toUpdate) throws BaseServiceException {
try {
- logger.info("Start update - toUpdate: {}", toUpdate);
+ logger.info("Start update service- toUpdate: {}", toUpdate);
Preconditions.checkNotNull(toUpdate);
//check if a new password was set, and encode it
if(!toUpdate.getPassword().isEmpty()) {
@@ -306,7 +306,7 @@ public UserDTO update(UserDTO toUpdate) throws BaseServiceException {
} catch (Exception ex) {
throw new BaseServiceException(ErrorMessage.SVC_ERROR_GENERIC, ex);
} finally {
- logger.info("End update");
+ logger.info("End update service");
}
}
@@ -342,6 +342,48 @@ public UserDTO delete(UserDTO toDelete) throws BaseServiceException {
}
}
+ /**
+ * Find user by email address
+ * For password reset
+ *
+ * @param emailAddress
+ * @return UserDTO
+ * @throws BaseServiceException
+ */
+ @Override
+ public UserDTO findByEmail(String emailAddress) throws BaseServiceException {
+ try {
+ logger.info("Start findByEmail - email: {}", emailAddress);
+ Preconditions.checkNotNull(emailAddress);
+ UserDTO userDTO = userDTOMapper.mapEntity(userDao.findByEmail(emailAddress));
+ Preconditions.checkNotNull(userDTO);
+ return userDTO;
+ } catch (BaseDaoException bde) {
+ throw new BaseServiceException(ErrorMessage.USER_ERROR_GET_ONE_FAILED, bde);
+ } catch (Exception ex) {
+ throw new BaseServiceException(ErrorMessage.SVC_ERROR_GENERIC, ex);
+ } finally {
+ logger.info("End findByEmail");
+ }
+ }
+
+ @Override
+ public UserDTO findByResetToken(String token) throws BaseServiceException {
+ try {
+ logger.info("Start findByResetToken - token: {}", token);
+ Preconditions.checkNotNull(token);
+ UserDTO userDTO = userDTOMapper.mapEntity(userDao.findByResetToken(token));
+ Preconditions.checkNotNull(userDTO);
+ return userDTO;
+ } catch (BaseDaoException bde) {
+ throw new BaseServiceException(ErrorMessage.USER_ERROR_GET_ONE_FAILED, bde);
+ } catch (Exception ex) {
+ throw new BaseServiceException(ErrorMessage.SVC_ERROR_GENERIC, ex);
+ } finally {
+ logger.info("End findByResetToken");
+ }
+ }
+
//mapper to map User to UserViewDTO
private UserViewDTO userViewDTOMapper(User user) {
Preconditions.checkNotNull(user);
diff --git a/backend/src/main/java/com/swappee/utils/configuration/JpaAuditingConfig.java b/backend/src/main/java/com/swappee/utils/configuration/JpaAuditingConfig.java
index c7892c6..dc23d2c 100644
--- a/backend/src/main/java/com/swappee/utils/configuration/JpaAuditingConfig.java
+++ b/backend/src/main/java/com/swappee/utils/configuration/JpaAuditingConfig.java
@@ -25,7 +25,7 @@ public static class SecurityAuditor implements AuditorAware {
@Override
public Optional getCurrentAuditor() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
- return Optional.of(auth != null ? auth.getName() : "UknownUser");
+ return Optional.of(auth != null ? auth.getName() : "UnknownUser");
}
}
}
\ No newline at end of file
diff --git a/backend/src/main/java/com/swappee/utils/exception/UserFriendlyMessage.java b/backend/src/main/java/com/swappee/utils/exception/UserFriendlyMessage.java
index 7e945a9..1c385b9 100644
--- a/backend/src/main/java/com/swappee/utils/exception/UserFriendlyMessage.java
+++ b/backend/src/main/java/com/swappee/utils/exception/UserFriendlyMessage.java
@@ -76,4 +76,12 @@ public class UserFriendlyMessage {
//Authentication Error Message
+
+ // Mail Message
+ public static final String EMAIL_SEND_SUCCEED = "Successfully sent email";
+ public static final String EMAIL_SEND_FAILED = "Error in sending email";
+
+ // Password reset
+ public static final String PASSWORD_RESET_SUCCEED = "Successfully reset password";
+ public static final String PASSWORD_RESET_FAILED = "Error in reset password";
}
diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml
index 6594efa..4792384 100644
--- a/backend/src/main/resources/application.yml
+++ b/backend/src/main/resources/application.yml
@@ -18,6 +18,17 @@ spring:
jackson:
date-format: com.fasterxml.jackson.databind.util.StdDateFormat
default-property-inclusion: non-null
+ mail:
+ host: smtp.gmail.com
+ port: 587
+ username: ${SWAPPEE_EMAIL_FROM}
+ password: ${SWAPPEE_EMAIL_APP_PASSWORD}
+ properties:
+ mail:
+ smtp:
+ auth: true
+ starttls:
+ enable: true
server:
servlet:
@@ -46,4 +57,5 @@ logging:
org.springframework.security: INFO
org.springframework.cloud.openfeign: INFO
org.hibernate: INFO
- com.swappee: DEBUG
\ No newline at end of file
+ com.swappee: DEBUG
+
diff --git a/backend/src/test/java/com/swappee/dao/item/ItemDaoImplTest.java b/backend/src/test/java/com/swappee/dao/item/ItemDaoImplTest.java
index 96b3b01..9eb3648 100644
--- a/backend/src/test/java/com/swappee/dao/item/ItemDaoImplTest.java
+++ b/backend/src/test/java/com/swappee/dao/item/ItemDaoImplTest.java
@@ -13,7 +13,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.test.context.junit4.SpringRunner;
@RunWith(MockitoJUnitRunner.class)
@SpringBootTest