Skip to content

Commit

Permalink
ep 7
Browse files Browse the repository at this point in the history
  • Loading branch information
aware-natthaponp committed Mar 13, 2021
1 parent 9fa7473 commit 50d9a0e
Show file tree
Hide file tree
Showing 18 changed files with 308 additions and 66 deletions.
18 changes: 18 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,28 @@
<org.mapstruct.version>1.4.2.Final</org.mapstruct.version>
</properties>
<dependencies>
<dependency>
<groupId>org.passay</groupId>
<artifactId>passay</artifactId>
<version>1.6.0</version>
</dependency>
<dependency>
<groupId>com.iamnbty.training</groupId>
<artifactId>common</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.kafka</groupId>
<artifactId>spring-kafka-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
Expand Down
19 changes: 15 additions & 4 deletions src/main/java/com/iamnbty/training/backend/api/UserApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@

import com.iamnbty.training.backend.business.UserBusiness;
import com.iamnbty.training.backend.exception.BaseException;
import com.iamnbty.training.backend.model.MLoginRequest;
import com.iamnbty.training.backend.model.MLoginResponse;
import com.iamnbty.training.backend.model.MRegisterRequest;
import com.iamnbty.training.backend.model.MRegisterResponse;
import com.iamnbty.training.backend.model.*;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
Expand All @@ -32,6 +30,19 @@ public ResponseEntity<MRegisterResponse> register(@RequestBody MRegisterRequest
return ResponseEntity.ok(response);
}

@PostMapping("/activate")
public ResponseEntity<MActivateResponse> activate(@RequestBody MActivateRequest request) throws BaseException {
MActivateResponse response = business.activate(request);
return ResponseEntity.ok(response);
}

@PostMapping("/resend-activation-email")
public ResponseEntity<Void> resendActivationEmail(@RequestBody MResendActivationEmailRequest request) throws BaseException {
business.resendActivationEmail(request);
return ResponseEntity.status(HttpStatus.OK).build();
}


@GetMapping("/refresh-token")
public ResponseEntity<String> refreshToken() throws BaseException {
String response = business.refreshToken();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,28 @@

import com.iamnbty.training.backend.exception.BaseException;
import com.iamnbty.training.backend.exception.EmailException;
import com.iamnbty.training.backend.service.EmailService;
import com.iamnbty.training.common.EmailRequest;
import lombok.extern.log4j.Log4j2;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.SendResult;
import org.springframework.stereotype.Service;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.ResourceUtils;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

@Service
@Log4j2
public class EmailBusiness {

private final EmailService emailService;
private final KafkaTemplate<String, EmailRequest> kafkaEmailTemplate;

public EmailBusiness(EmailService emailService) {
this.emailService = emailService;
public EmailBusiness(KafkaTemplate<String, EmailRequest> kafkaEmailTemplate) {
this.kafkaEmailTemplate = kafkaEmailTemplate;
}

public void sendActivateUserEmail(String email, String name, String token) throws BaseException {
Expand All @@ -29,15 +35,31 @@ public void sendActivateUserEmail(String email, String name, String token) throw
throw EmailException.templateNotFound();
}

log.info("Token = " + token);

String finalLink = "http://localhost:4200/activate/" + token;
html = html.replace("${P_NAME}", name);
html = html.replace("${LINK}", finalLink);
html = html.replace("${P_LINK}", finalLink);

// prepare subject
String subject = "Please activate your account";
EmailRequest request = new EmailRequest();
request.setTo(email);
request.setSubject("Please activate your account");
request.setContent(html);

ListenableFuture<SendResult<String, EmailRequest>> future = kafkaEmailTemplate.send("activation-email", request);
future.addCallback(new ListenableFutureCallback<>() {
@Override
public void onFailure(Throwable throwable) {
log.error("Kafka send fail");
log.error(throwable);
}

emailService.send(email, subject, html);
@Override
public void onSuccess(SendResult<String, EmailRequest> result) {
log.info("Kafka send success");
log.info(result);
}
});
}

private String readEmailTemplate(String filename) throws IOException {
Expand Down
100 changes: 91 additions & 9 deletions src/main/java/com/iamnbty/training/backend/business/UserBusiness.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,20 @@
import com.iamnbty.training.backend.exception.FileException;
import com.iamnbty.training.backend.exception.UserException;
import com.iamnbty.training.backend.mapper.UserMapper;
import com.iamnbty.training.backend.model.MLoginRequest;
import com.iamnbty.training.backend.model.MLoginResponse;
import com.iamnbty.training.backend.model.MRegisterRequest;
import com.iamnbty.training.backend.model.MRegisterResponse;
import com.iamnbty.training.backend.model.*;
import com.iamnbty.training.backend.service.TokenService;
import com.iamnbty.training.backend.service.UserService;
import com.iamnbty.training.backend.util.SecurityUtil;
import io.netty.util.internal.StringUtil;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.*;

@Service
@Log4j2
public class UserBusiness {

private final UserService userService;
Expand All @@ -29,10 +27,13 @@ public class UserBusiness {

private final UserMapper userMapper;

public UserBusiness(UserService userService, TokenService tokenService, UserMapper userMapper) {
private final EmailBusiness emailBusiness;

public UserBusiness(UserService userService, TokenService tokenService, UserMapper userMapper, EmailBusiness emailBusiness) {
this.userService = userService;
this.tokenService = tokenService;
this.userMapper = userMapper;
this.emailBusiness = emailBusiness;
}

public MLoginResponse login(MLoginRequest request) throws BaseException {
Expand All @@ -45,10 +46,17 @@ public MLoginResponse login(MLoginRequest request) throws BaseException {
}

User user = opt.get();

// verify password
if (!userService.matchPassword(request.getPassword(), user.getPassword())) {
throw UserException.loginFailPasswordIncorrect();
}

// verify activate status
if (!user.isActivated()) {
throw UserException.loginFailUserUnactivated();
}

MLoginResponse response = new MLoginResponse();
response.setToken(tokenService.tokenize(user));
return response;
Expand All @@ -72,11 +80,85 @@ public String refreshToken() throws BaseException {
}

public MRegisterResponse register(MRegisterRequest request) throws BaseException {
User user = userService.create(request.getEmail(), request.getPassword(), request.getName());
String token = SecurityUtil.generateToken();
User user = userService.create(request.getEmail(), request.getPassword(), request.getName(), token, nextXMinute(30));

sendEmail(user);

return userMapper.toRegisterResponse(user);
}

public MActivateResponse activate(MActivateRequest request) throws BaseException {
String token = request.getToken();
if (StringUtil.isNullOrEmpty(token)) {
throw UserException.activateNoToken();
}

Optional<User> opt = userService.findByToken(token);
if (opt.isEmpty()) {
throw UserException.activateFail();
}

User user = opt.get();

if (user.isActivated()) {
throw UserException.activateAlready();
}

Date now = new Date();
Date expireDate = user.getTokenExpire();
if (now.after(expireDate)) {
throw UserException.activateTokenExpire();
}

user.setActivated(true);
userService.update(user);

MActivateResponse response = new MActivateResponse();
response.setSuccess(true);
return response;
}

public void resendActivationEmail(MResendActivationEmailRequest request) throws BaseException {
String email = request.getEmail();
if (StringUtil.isNullOrEmpty(email)) {
throw UserException.resendActivationEmailNoEmail();
}

Optional<User> opt = userService.findByEmail(email);
if (opt.isEmpty()) {
throw UserException.resendActivationEmailNotFound();
}

User user = opt.get();

if (user.isActivated()) {
throw UserException.activateAlready();
}

user.setToken(SecurityUtil.generateToken());
user.setTokenExpire(nextXMinute(30));
user = userService.update(user);

sendEmail(user);
}

private Date nextXMinute(int minute) {
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MINUTE, minute);
return calendar.getTime();
}

private void sendEmail(User user) {
String token = user.getToken();

try {
emailBusiness.sendActivateUserEmail(user.getEmail(), user.getName(), token);
} catch (BaseException e) {
e.printStackTrace();
}
}

public String uploadProfilePicture(MultipartFile file) throws BaseException {
// validate file
if (file == null) {
Expand Down
39 changes: 39 additions & 0 deletions src/main/java/com/iamnbty/training/backend/config/KafkaConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.iamnbty.training.backend.config;

import com.iamnbty.training.common.EmailRequest;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.support.serializer.JsonSerializer;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class KafkaConfig {

@Value("${spring.kafka.bootstrap-servers}")
private String server;

@Bean
public Map<String, Object> producerConfigs() {
Map<String, Object> map = new HashMap<>();

map.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, server);
map.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
map.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class);

return map;
}

@Bean
public KafkaTemplate<String, EmailRequest> kafkaEmailTemplate() {
DefaultKafkaProducerFactory<String, EmailRequest> factory = new DefaultKafkaProducerFactory<>(producerConfigs());
return new KafkaTemplate<>(factory);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
"/actuator/**",
"/user/register",
"/user/login",
"/user/activate",
"/user/resend-activation-email",
"/socket/**"
};

Expand Down
7 changes: 7 additions & 0 deletions src/main/java/com/iamnbty/training/backend/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import lombok.EqualsAndHashCode;

import javax.persistence.*;
import java.util.Date;
import java.util.List;

@EqualsAndHashCode(callSuper = true)
Expand All @@ -28,4 +29,10 @@ public class User extends BaseEntity {
@OneToMany(mappedBy = "user", orphanRemoval = true, fetch = FetchType.EAGER)
private List<Address> addresses;

private String token;

private Date tokenExpire;

private boolean activated;

}
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,36 @@ public static UserException loginFailPasswordIncorrect() {
return new UserException("login.fail");
}

public static UserException loginFailUserUnactivated() {
return new UserException("login.fail.unactivated");
}

// ACTIVATE

public static UserException activateNoToken() {
return new UserException("activate.no.token");
}

public static UserException activateAlready() {
return new UserException("activate.already");
}

public static UserException activateFail() {
return new UserException("activate.fail");
}

public static UserException activateTokenExpire() {
return new UserException("activate.token.expire");
}

// RESEND ACTIVATION EMAIL

public static UserException resendActivationEmailNoEmail() {
return new UserException("resend.activation.no.email");
}

public static UserException resendActivationEmailNotFound() {
return new UserException("resend.activation.fail");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.iamnbty.training.backend.model;

import lombok.Data;

@Data
public class MActivateRequest {

private String token;

}
Loading

0 comments on commit 50d9a0e

Please sign in to comment.