diff --git a/api-gateway/src/main/java/com/petclinic/bffapigateway/domainclientlayer/VisitsServiceClient.java b/api-gateway/src/main/java/com/petclinic/bffapigateway/domainclientlayer/VisitsServiceClient.java index d0b2f84b13..64d0b41739 100755 --- a/api-gateway/src/main/java/com/petclinic/bffapigateway/domainclientlayer/VisitsServiceClient.java +++ b/api-gateway/src/main/java/com/petclinic/bffapigateway/domainclientlayer/VisitsServiceClient.java @@ -2,7 +2,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.petclinic.bffapigateway.BFFApiGatewayApplication; import com.petclinic.bffapigateway.dtos.Visits.*; import com.petclinic.bffapigateway.exceptions.BadRequestException; import com.petclinic.bffapigateway.exceptions.DuplicateTimeException; @@ -25,10 +24,8 @@ */ @Component -@Slf4j public class VisitsServiceClient { private final WebClient webClient; - @Autowired public VisitsServiceClient( @Value("${app.visits-service-new.host}") String visitsServiceHost, @@ -126,40 +123,12 @@ else if (httpStatus == HttpStatus.CONFLICT){ else { return Mono.error(new BadRequestException(message)); } - } catch (IOException e) { // Handle parsing error return Mono.error(new BadRequestException("Bad Request")); } }); }) - .onStatus(HttpStatusCode::is2xxSuccessful, response -> { - HttpStatusCode httpStatus = response.statusCode(); - if (httpStatus == HttpStatus.CREATED) { - - return null; -// return response.bodyToMono(VisitResponseDTO.class); - } - return null; - -// return response.bodyToMono(String.class) -// .flatMap(errorMessage -> { -// try { -// ObjectMapper objectMapper = new ObjectMapper(); -// JsonNode errorNode = objectMapper.readTree(errorMessage); -// String message = errorNode.get("message").asText(); -// -// if (httpStatus == HttpStatus.NOT_FOUND) { -// return Mono.error(new NotFoundException(message)); -// } else { -// return Mono.error(new BadRequestException(message)); -// } -// } catch (IOException e) { -// // Handle parsing error -// return Mono.error(new BadRequestException("Bad Request")); -// } -// }); - }) .bodyToMono(VisitResponseDTO.class); } diff --git a/api-gateway/src/main/java/com/petclinic/bffapigateway/dtos/Visits/VisitRequestDTO.java b/api-gateway/src/main/java/com/petclinic/bffapigateway/dtos/Visits/VisitRequestDTO.java index 4efc69f4ae..464f807503 100644 --- a/api-gateway/src/main/java/com/petclinic/bffapigateway/dtos/Visits/VisitRequestDTO.java +++ b/api-gateway/src/main/java/com/petclinic/bffapigateway/dtos/Visits/VisitRequestDTO.java @@ -19,13 +19,17 @@ public class VisitRequestDTO { private LocalDateTime visitDate; private String description; private String petId; + private String ownerId; + private String jwtToken;//used to get the userDetails from the Auth-Service when sending visit emails private String practitionerId; private Status status; - public VisitRequestDTO(LocalDateTime now, String description, String petId, String practitionerId) { + public VisitRequestDTO(LocalDateTime now, String description, String petId, String ownerId, String jwtToken, String practitionerId) { this.visitDate = now; this.description = description; this.petId = petId; + this.ownerId = ownerId; + this.jwtToken = jwtToken; this.practitionerId = practitionerId; this.status = Status.UPCOMING; } diff --git a/api-gateway/src/main/java/com/petclinic/bffapigateway/presentationlayer/BFFApiGatewayController.java b/api-gateway/src/main/java/com/petclinic/bffapigateway/presentationlayer/BFFApiGatewayController.java index 1888d91651..9d52aeeb11 100644 --- a/api-gateway/src/main/java/com/petclinic/bffapigateway/presentationlayer/BFFApiGatewayController.java +++ b/api-gateway/src/main/java/com/petclinic/bffapigateway/presentationlayer/BFFApiGatewayController.java @@ -264,8 +264,8 @@ public Mono getVisitByVisitId(@PathVariable String visitId){ } @PostMapping(value = "visit/owners/{ownerId}/pets/{petId}/visits", consumes = "application/json", produces = "application/json") Mono> addVisit(@RequestBody VisitRequestDTO visit, @PathVariable String ownerId, @PathVariable String petId, @CookieValue("Bearer") String auth) { - // visit.setPetId(petId); - Mono user = getUserById(auth, ownerId); + visit.setOwnerId(ownerId); + visit.setJwtToken(auth); return visitsServiceClient.createVisitForPet(visit).map(ResponseEntity.status(HttpStatus.CREATED)::body) .defaultIfEmpty(ResponseEntity.badRequest().build()); } diff --git a/api-gateway/src/test/java/com/petclinic/bffapigateway/domainclientlayer/VisitsServiceClientIntegrationTest.java b/api-gateway/src/test/java/com/petclinic/bffapigateway/domainclientlayer/VisitsServiceClientIntegrationTest.java index 604d07cb6c..993ecc5225 100755 --- a/api-gateway/src/test/java/com/petclinic/bffapigateway/domainclientlayer/VisitsServiceClientIntegrationTest.java +++ b/api-gateway/src/test/java/com/petclinic/bffapigateway/domainclientlayer/VisitsServiceClientIntegrationTest.java @@ -150,6 +150,8 @@ void createVisitForPet_Valid() throws JsonProcessingException { LocalDateTime.parse("2024-11-25 13:45", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")), "Test Visit", "1", + "f470653d-05c5-4c45-b7a0-7d70f003d2ac", + "testJwtToken", "2" ); @@ -178,6 +180,8 @@ void createVisitForPet_Valid() throws JsonProcessingException { // Assert StepVerifier.create(resultMono) + .expectNext() + .expectNext() .expectNextMatches(visitResponse -> Objects.equals(visitResponse.getVisitId(), visitResponseDTO.getVisitId())) .verifyComplete(); @@ -191,6 +195,8 @@ void createVisitForPet_DuplicateTime_ThrowsDuplicateTimeException() throws JsonP LocalDateTime.parse("2024-11-25 13:45", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")), "Test Visit", "1", + "f470653d-05c5-4c45-b7a0-7d70f003d2ac", + "testJwtToken", "2" ); @@ -218,6 +224,8 @@ void createVisitForPet_NotFound_ThrowsNotFoundException() throws JsonProcessingE LocalDateTime.parse("2024-11-25 13:45", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")), "Test Visit", "1", + "f470653d-05c5-4c45-b7a0-7d70f003d2ac", + "testJwtToken", "2" ); @@ -245,6 +253,8 @@ void createVisitForPet_BadRequest_ThrowsBadRequestException() throws JsonProcess LocalDateTime.parse("2024-11-25 13:45", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")), "Test Visit", "1", + "f470653d-05c5-4c45-b7a0-7d70f003d2ac", + "testJwtToken", "2" ); @@ -272,6 +282,8 @@ void createVisitForPet_InvalidErrorResponse_ThrowsBadRequestException() throws J LocalDateTime.parse("2024-11-25 13:45", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")), "Test Visit", "1", + "f470653d-05c5-4c45-b7a0-7d70f003d2ac", + "testJwtToken", "2" ); @@ -322,7 +334,7 @@ void getVisitById() throws Exception { .visitDate(LocalDateTime.parse("2024-11-25 13:45", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"))) .description("this is a dummy description") .petId("2") - .petName("YourPetNameHere") + .petName("YourPetNameHere") .petBirthDate(new Date()) .practitionerId("2") .vetFirstName("VetFirstNameHere") diff --git a/auth-service/src/test/java/com/petclinic/authservice/businesslayer/AuthMailServiceTests.java b/auth-service/src/test/java/com/petclinic/authservice/businesslayer/AuthMailServiceTests.java index 2d46c41905..7f13eead39 100644 --- a/auth-service/src/test/java/com/petclinic/authservice/businesslayer/AuthMailServiceTests.java +++ b/auth-service/src/test/java/com/petclinic/authservice/businesslayer/AuthMailServiceTests.java @@ -49,7 +49,7 @@ void setUp(){ .thenReturn(Calls.response(format("Message sent to %s", EMAIL_VALID.getTo()))); when(mockMailCall.sendMail(EMAIL_EMPTY_INVALID)) - .thenReturn(Calls.response(Response.error(400, ResponseBody.create(JSON, "Bad request")))); + .thenReturn(Calls.response(Response.error(400, ResponseBody.create("Bad request", JSON)))); } @Test diff --git a/docker-compose.yml b/docker-compose.yml index b04dd18273..08c4acc8f7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -13,16 +13,7 @@ services: - SPRING_PROFILES_ACTIVE=docker depends_on: - mongo2 - -# visits: -# build: visits-service -# hostname: visits -# #mem_limit: 350m -# environment: -# - SPRING_PROFILES_ACTIVE=docker -# depends_on: -# mysql1: -# condition: service_healthy + - mailer-service inventory-service: build: inventory-service @@ -47,16 +38,6 @@ services: depends_on: - mongo-customers -# customers: -# build: customers-service -# hostname: customers -# #mem_limit: 350m -# environment: -# - SPRING_PROFILES_ACTIVE=docker -# depends_on: -# mysql3: -# condition: service_healthy - api-gateway: build: api-gateway #mem_limit: 350m @@ -101,6 +82,25 @@ services: env_file: - mailer.env +# visits: +# build: visits-service +# hostname: visits +# #mem_limit: 350m +# environment: +# - SPRING_PROFILES_ACTIVE=docker +# depends_on: +# mysql1: +# condition: service_healthy + +# customers: +# build: customers-service +# hostname: customers +# #mem_limit: 350m +# environment: +# - SPRING_PROFILES_ACTIVE=docker +# depends_on: +# mysql3: +# condition: service_healthy # mysql1: # image: mysql:5.7 diff --git a/visits-service-new/build.gradle b/visits-service-new/build.gradle index f82e3b1aaa..f7fafcaa70 100644 --- a/visits-service-new/build.gradle +++ b/visits-service-new/build.gradle @@ -22,7 +22,8 @@ dependencies { 'org.springframework.boot:spring-boot-starter-webflux', 'org.yaml:snakeyaml:2.2', "org.mapstruct:mapstruct:${mapstructVersion}", - 'org.simplejavamail:simple-java-mail:8.3.1', 'net.markenwerk:utils-mail-dkim:2.0.1' //Simple Java Mail and DKIM a framework for DKIM signing and verification + 'org.simplejavamail:simple-java-mail:8.3.1', 'net.markenwerk:utils-mail-dkim:2.0.1', //Simple Java Mail and DKIM a framework for DKIM signing and verification + 'com.squareup.retrofit2:retrofit:2.9.0', 'com.squareup.retrofit2:converter-jackson:2.9.0' compileOnly 'org.projectlombok:lombok', "org.mapstruct:mapstruct-processor:${mapstructVersion}" @@ -31,13 +32,13 @@ dependencies { annotationProcessor 'org.projectlombok:lombok', "org.mapstruct:mapstruct-processor:${mapstructVersion}" - testImplementation 'com.squareup.okhttp3:mockwebserver:4.11.0', + testImplementation 'com.squareup.okhttp3:mockwebserver:4.11.0', 'com.squareup.okhttp3:okhttp:4.11.0', 'org.simplejavamail:simple-java-mail:8.3.1', 'net.markenwerk:utils-mail-dkim:2.0.1', //Simple Java Mail and DKIM a framework for DKIM signing and verification 'org.springframework.boot:spring-boot-starter-test', 'org.yaml:snakeyaml:2.2', 'de.flapdoodle.embed:de.flapdoodle.embed.mongo.spring30x:4.9.2', 'io.projectreactor:reactor-test', - 'com.squareup.okhttp3:okhttp:4.11.0' + 'com.squareup.retrofit2:retrofit-mock:2.9.0' } jacoco { diff --git a/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/BusinessLayer/VisitServiceImpl.java b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/BusinessLayer/VisitServiceImpl.java index 09a6d9bbdc..f14b5d10cb 100644 --- a/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/BusinessLayer/VisitServiceImpl.java +++ b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/BusinessLayer/VisitServiceImpl.java @@ -3,6 +3,10 @@ import com.petclinic.visits.visitsservicenew.DataLayer.Status; import com.petclinic.visits.visitsservicenew.DataLayer.VisitRepo; import com.petclinic.visits.visitsservicenew.DomainClientLayer.*; +import com.petclinic.visits.visitsservicenew.DomainClientLayer.Auth.AuthServiceClient; +import com.petclinic.visits.visitsservicenew.DomainClientLayer.Auth.UserDetails; +import com.petclinic.visits.visitsservicenew.DomainClientLayer.Mailing.Mail; +import com.petclinic.visits.visitsservicenew.DomainClientLayer.Mailing.MailService; import com.petclinic.visits.visitsservicenew.Exceptions.BadRequestException; import com.petclinic.visits.visitsservicenew.Exceptions.DuplicateTimeException; import com.petclinic.visits.visitsservicenew.Exceptions.NotFoundException; @@ -15,6 +19,8 @@ import reactor.core.publisher.Mono; import java.time.LocalDateTime; +import static java.lang.String.format; + @Service @RequiredArgsConstructor @@ -24,6 +30,8 @@ public class VisitServiceImpl implements VisitService { private final PetsClient petsClient; private final EntityDtoUtil entityDtoUtil; + private final AuthServiceClient authServiceClient; + private final MailService mailService; @Override public Flux getAllVisits() { return repo.findAll().flatMap(visit -> entityDtoUtil.toVisitResponseDTO(visit)); @@ -77,11 +85,20 @@ public Mono addVisit(Mono visitRequestDTOMono .then(validatePetId(visitRequestDTO.getPetId())) .then(validateVetId(visitRequestDTO.getPractitionerId())) .then(Mono.just(visitRequestDTO)) + .doOnNext(s->{ + + authServiceClient.getUserById(visitRequestDTO.getJwtToken(), visitRequestDTO.getOwnerId()).subscribe(user->mailService.sendMail(generateVisitRequestEmail(user, visitRequestDTO.getPetId(), visitRequestDTO.getVisitDate()))); + + // Mono user = getUserById(auth, ownerId); + // try{ + // simpleJavaMailClient.sendMail(emailBuilder("test@email.com")); + // }catch(Exception e){System.out.println("Email failed to send: "+e.getMessage());} + }) ) - .doOnNext(v -> System.out.println("Request Date: " + v.getVisitDate())) // Debugging +// .doOnNext(v -> System.out.println("Request Date: " + v.getVisitDate())) // Debugging .map(visitRequestDTO -> entityDtoUtil.toVisitEntity(visitRequestDTO)) - .doOnNext(x -> x.setVisitId(entityDtoUtil.generateVisitIdString())) - .doOnNext(v -> System.out.println("Entity Date: " + v.getVisitDate())) // Debugging +// .doOnNext(x -> x.setVisitId(entityDtoUtil.generateVisitIdString())) +// .doOnNext(v -> System.out.println("Entity Date: " + v.getVisitDate())) // Debugging .flatMap(visit -> repo.findByVisitDateAndPractitionerId(visit.getVisitDate(), visit.getPractitionerId()) // FindVisits method in repository .collectList() @@ -210,4 +227,57 @@ else if (dto.getStatus() != Status.UPCOMING){ return Mono.just(dto); } } + + private Mail generateVisitRequestEmail(UserDetails user, String petName, LocalDateTime visitDate) { + return Mail.builder() + .message( + format(""" + + + + + + Email Verification + + + +
+

Dear %s,

+

We have received a request to schedule a visit for one of your pet: %s at the following date and time: %s.

+ \s +

If you do not wish to create an account, please disregard this email.

+ \s +

Thank you for choosing Pet Clinic.

+
+ + + """, user.getUsername(), petName, visitDate.toString())) + .subject("PetClinic Visit request") + .to(user.getEmail()) + .build(); + } } \ No newline at end of file diff --git a/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Auth/AuthServiceClient.java b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Auth/AuthServiceClient.java new file mode 100644 index 0000000000..e11ebc3223 --- /dev/null +++ b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Auth/AuthServiceClient.java @@ -0,0 +1,42 @@ +package com.petclinic.visits.visitsservicenew.DomainClientLayer.Auth; + +import com.petclinic.visits.visitsservicenew.Exceptions.GenericHttpException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.*; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import static org.springframework.http.HttpStatus.NOT_FOUND; + +@Slf4j +@Component +public class AuthServiceClient { + private final WebClient.Builder webClientBuilder; + private final String authServiceUrl; + @Autowired + private Rethrower rethrower; + + public AuthServiceClient( + WebClient.Builder webClientBuilder, + @Value("${app.auth-service.host}") String authServiceHost, + @Value("${app.auth-service.port}") String authServicePort) { + this.webClientBuilder = webClientBuilder; + authServiceUrl = "http://" + authServiceHost + ":" + authServicePort; + } + + public Mono getUserById(String jwtToken, String userId) { + return webClientBuilder.build() + .get() + .uri(authServiceUrl + "/users/{userId}", userId) + .cookie("Bearer", jwtToken) + .retrieve() + .onStatus(HttpStatusCode::is4xxClientError, + n -> rethrower.rethrow(n, + x -> new GenericHttpException(x.get("message").toString(), NOT_FOUND)) + ) + .bodyToMono(UserDetails.class); + } +} + diff --git a/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Auth/Rethrower.java b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Auth/Rethrower.java new file mode 100644 index 0000000000..be044ba793 --- /dev/null +++ b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Auth/Rethrower.java @@ -0,0 +1,31 @@ +package com.petclinic.visits.visitsservicenew.DomainClientLayer.Auth; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.ClientResponse; +import reactor.core.publisher.Mono; + +import java.util.Map; +import java.util.function.Function; + +@RequiredArgsConstructor +@Component +public class Rethrower { + + private final ObjectMapper objectMapper; + public Mono rethrow(ClientResponse clientResponse, Function exceptionProvider) { + return clientResponse.createException().flatMap(n -> + { + try { + final Map map = + objectMapper.readValue(n.getResponseBodyAsString(), Map.class); + return Mono.error(exceptionProvider.apply(map)); + } catch (JsonProcessingException e) { + e.printStackTrace(); + return Mono.error(e); + } + }); + } +} diff --git a/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Auth/Role.java b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Auth/Role.java new file mode 100644 index 0000000000..d65dbd0f70 --- /dev/null +++ b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Auth/Role.java @@ -0,0 +1,24 @@ +package com.petclinic.visits.visitsservicenew.DomainClientLayer.Auth; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * Created by IntelliJ IDEA. + * + * User: @Fube + * Date: 2021-10-15 + * Ticket: feat(APIG-CPC-354) + */ + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder(toBuilder = true) +public class Role { + + private int id; + private String name; +} diff --git a/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Auth/UserDetails.java b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Auth/UserDetails.java new file mode 100644 index 0000000000..4ce1a6326a --- /dev/null +++ b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Auth/UserDetails.java @@ -0,0 +1,23 @@ +package com.petclinic.visits.visitsservicenew.DomainClientLayer.Auth; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Set; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder(toBuilder = true) +public class UserDetails { + + private String userId; + private String username; + + private String email; + + private Set roles; +} + diff --git a/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Mailing/Mail.java b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Mailing/Mail.java new file mode 100644 index 0000000000..0f6c91be3e --- /dev/null +++ b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Mailing/Mail.java @@ -0,0 +1,19 @@ +package com.petclinic.visits.visitsservicenew.DomainClientLayer.Mailing; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder(toBuilder = true) +public class Mail { + + private String + to, + subject, + message; +} diff --git a/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Mailing/MailService.java b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Mailing/MailService.java new file mode 100644 index 0000000000..978ecfa592 --- /dev/null +++ b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Mailing/MailService.java @@ -0,0 +1,6 @@ +package com.petclinic.visits.visitsservicenew.DomainClientLayer.Mailing; + +public interface MailService { + + String sendMail(Mail mail); +} diff --git a/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Mailing/MailServiceCall.java b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Mailing/MailServiceCall.java new file mode 100644 index 0000000000..5035c7978b --- /dev/null +++ b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Mailing/MailServiceCall.java @@ -0,0 +1,11 @@ +package com.petclinic.visits.visitsservicenew.DomainClientLayer.Mailing; + +import retrofit2.Call; +import retrofit2.http.Body; +import retrofit2.http.POST; + +public interface MailServiceCall { + + @POST("/mail") + Call sendMail(@Body Mail mail); +} diff --git a/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Mailing/MailServiceImpl.java b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Mailing/MailServiceImpl.java new file mode 100644 index 0000000000..f21d94136a --- /dev/null +++ b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/Mailing/MailServiceImpl.java @@ -0,0 +1,34 @@ +package com.petclinic.visits.visitsservicenew.DomainClientLayer.Mailing; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import org.springframework.web.client.HttpClientErrorException; +import retrofit2.Response; + +import java.io.IOException; + +@Service +@Slf4j +@RequiredArgsConstructor +public class MailServiceImpl implements MailService { + + private final MailServiceCall mailServiceCall; + @Override + public String sendMail(Mail mail) { + try { + Response execute = mailServiceCall.sendMail(mail).execute(); + if (execute.code() == 400) { + log.error(execute.message()); + log.error(execute.errorBody().string()); + throw new HttpClientErrorException(HttpStatus.BAD_REQUEST, execute.errorBody().string()); + } + log.info("Mail service returned {} status code", execute.code()); + return execute.body(); + } catch (IOException e) { + log.error(e.toString()); + throw new HttpClientErrorException(HttpStatus.INTERNAL_SERVER_ERROR, "Unable to send mail"); + } + } +} \ No newline at end of file diff --git a/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/SimpleJavaMailClient.java b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/SimpleJavaMail/SimpleJavaMailClient.java similarity index 64% rename from visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/SimpleJavaMailClient.java rename to visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/SimpleJavaMail/SimpleJavaMailClient.java index ea1dc65464..c6bcf64881 100644 --- a/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/SimpleJavaMailClient.java +++ b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/SimpleJavaMail/SimpleJavaMailClient.java @@ -1,4 +1,4 @@ -package com.petclinic.visits.visitsservicenew.DomainClientLayer; +package com.petclinic.visits.visitsservicenew.DomainClientLayer.SimpleJavaMail; import lombok.NoArgsConstructor; @@ -11,12 +11,7 @@ @NoArgsConstructor public class SimpleJavaMailClient { - private final Email email = EmailBuilder.startingBlank() - .from("From", "champlain.petclinic@gmail.com") - .to("William", "william.chalifoux@gmail.com") - .withSubject("Email test with simple java mail") - .withPlainText("Email Body") - .buildEmail(); + private final Mailer mailer = MailerBuilder .withSMTPServer("smtp.gmail.com", 587, "champlain.petclinic@gmail.com", System.getenv("SMTP_PASS")).withTransportStrategy(TransportStrategy.SMTP_TLS) @@ -24,8 +19,7 @@ public class SimpleJavaMailClient { .buildMailer(); - public void sendMail(){ - /*Email email*/ + public void sendMail(Email email){ mailer.sendMail(email); } diff --git a/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/Exceptions/GenericHttpException.java b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/Exceptions/GenericHttpException.java new file mode 100644 index 0000000000..438c25b890 --- /dev/null +++ b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/Exceptions/GenericHttpException.java @@ -0,0 +1,16 @@ +package com.petclinic.visits.visitsservicenew.Exceptions; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.springframework.http.HttpStatus; + +@Data +@EqualsAndHashCode(callSuper = true) +public class GenericHttpException extends RuntimeException { + + private HttpStatus httpStatus; + public GenericHttpException(String message, HttpStatus httpStatus) { + super(message); + this.httpStatus = httpStatus; + } +} diff --git a/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/PresentationLayer/VisitRequestDTO.java b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/PresentationLayer/VisitRequestDTO.java index fcec85a820..8f34cef993 100644 --- a/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/PresentationLayer/VisitRequestDTO.java +++ b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/PresentationLayer/VisitRequestDTO.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.petclinic.visits.visitsservicenew.DataLayer.Status; +import com.petclinic.visits.visitsservicenew.DomainClientLayer.Auth.UserDetails; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; @@ -14,14 +15,12 @@ @NoArgsConstructor @Builder public class VisitRequestDTO { - @JsonFormat(pattern = "yyyy-MM-dd HH:mm") private LocalDateTime visitDate; -/* private int year; - private int month; - private int day;*/ private String description; private String petId; + private String ownerId; + private String jwtToken;//used to get the userDetails from the Auth-Service when sending visit emails private String practitionerId; private Status status; } diff --git a/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/Utils/Configuration/Mail/MailServiceConfig.java b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/Utils/Configuration/Mail/MailServiceConfig.java new file mode 100644 index 0000000000..20c3cc4e76 --- /dev/null +++ b/visits-service-new/src/main/java/com/petclinic/visits/visitsservicenew/Utils/Configuration/Mail/MailServiceConfig.java @@ -0,0 +1,32 @@ +package com.petclinic.visits.visitsservicenew.Utils.Configuration.Mail; + +import com.petclinic.visits.visitsservicenew.DomainClientLayer.Mailing.MailServiceCall; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import retrofit2.Retrofit; +import retrofit2.converter.jackson.JacksonConverterFactory; + +import static java.lang.String.format; + +@Configuration +public class MailServiceConfig { + + private final String MAIL_BASE_URL; + + public MailServiceConfig( + @Value("${app.mailer-service.host}") String mailURL, + @Value("${app.mailer-service.port}") String mailPORT + ) { + MAIL_BASE_URL = format("http://%s:%s", mailURL, mailPORT); + } + + @Bean + public MailServiceCall getMailerServiceCall() { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl(MAIL_BASE_URL) + .addConverterFactory(JacksonConverterFactory.create()) + .build(); + return retrofit.create(MailServiceCall.class); + } +} diff --git a/visits-service-new/src/main/resources/application.yml b/visits-service-new/src/main/resources/application.yml index 68a98165df..6a3ff3720b 100644 --- a/visits-service-new/src/main/resources/application.yml +++ b/visits-service-new/src/main/resources/application.yml @@ -8,6 +8,12 @@ app: customers-service-reactive: host: localhost port: 8090 + auth-service: + host: localhost + port: 7005 + mailer-service: + host: localhost + port: 8888 logging: @@ -62,12 +68,19 @@ app: vet-service: host: vet-service port: 8080 - customers-service-reactive: host: customers-service-reactive port: 8080 + auth-service: + host: auth + port: 8080 + mailer-service: + host: mailer-service + port: 8080 logging: level: root: INFO - com.petclinic: DEBUG \ No newline at end of file + com.petclinic: DEBUG + +server.port: 8080 \ No newline at end of file diff --git a/visits-service-new/src/test/java/com/petclinic/visits/visitsservicenew/BusinessLayer/MailServiceTests.java b/visits-service-new/src/test/java/com/petclinic/visits/visitsservicenew/BusinessLayer/MailServiceTests.java new file mode 100644 index 0000000000..21b97421fb --- /dev/null +++ b/visits-service-new/src/test/java/com/petclinic/visits/visitsservicenew/BusinessLayer/MailServiceTests.java @@ -0,0 +1,73 @@ +package com.petclinic.visits.visitsservicenew.BusinessLayer; + + +import com.petclinic.visits.visitsservicenew.DomainClientLayer.Mailing.Mail; +import com.petclinic.visits.visitsservicenew.DomainClientLayer.Mailing.MailService; +import com.petclinic.visits.visitsservicenew.DomainClientLayer.Mailing.MailServiceCall; +import okhttp3.MediaType; +import okhttp3.ResponseBody; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.web.client.HttpClientErrorException; +import retrofit2.Response; +import retrofit2.mock.Calls; + +import java.io.IOException; + +import static java.lang.String.format; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +@SpringBootTest() +public class MailServiceTests { + @Autowired + private MailService mailService; + + @MockBean + private MailServiceCall mockMailCall; + + private final Mail + EMAIL_VALID = new Mail("to@test.com", "test-subject", "test-message"), + EMAIL_EMPTY_INVALID = new Mail(); + public final MediaType JSON + = MediaType.parse("application/json; charset=utf-8"); + + @BeforeEach + void setUp(){ + when(mockMailCall.sendMail(EMAIL_VALID)).thenReturn(Calls.response(format("Message sent to %s", EMAIL_VALID.getTo()))); + when(mockMailCall.sendMail(EMAIL_EMPTY_INVALID)).thenReturn(Calls.response(Response.error(400, ResponseBody.create("Bad request", JSON)))); + } + + @Test + void loads(){} + + @Test + @DisplayName("Send valid email") + void send_valid_email() { + assertEquals("Message sent to " + EMAIL_VALID.getTo(), mailService.sendMail(EMAIL_VALID)); + } + + @Test + @DisplayName("Send invalid empty email") + void send_invalid_empty_email() { + HttpClientErrorException httpClientErrorException = + assertThrows(HttpClientErrorException.class, () -> mailService.sendMail(EMAIL_EMPTY_INVALID)); + assertEquals("400 Bad Request", httpClientErrorException.getMessage()); + } + + @Test + @DisplayName("IOException graceful handling") + void io_exception_graceful_handling() { + when(mockMailCall.sendMail(EMAIL_EMPTY_INVALID)).thenReturn(Calls.failure(new IOException())); + HttpClientErrorException httpClientErrorException = + assertThrows(HttpClientErrorException.class, () -> mailService.sendMail(EMAIL_EMPTY_INVALID)); + assertEquals("500 Unable to send mail", httpClientErrorException.getMessage()); + } + + +} diff --git a/visits-service-new/src/test/java/com/petclinic/visits/visitsservicenew/BusinessLayer/VisitServiceImplTest.java b/visits-service-new/src/test/java/com/petclinic/visits/visitsservicenew/BusinessLayer/VisitServiceImplTest.java index 23b4967d4e..07f994e823 100644 --- a/visits-service-new/src/test/java/com/petclinic/visits/visitsservicenew/BusinessLayer/VisitServiceImplTest.java +++ b/visits-service-new/src/test/java/com/petclinic/visits/visitsservicenew/BusinessLayer/VisitServiceImplTest.java @@ -4,6 +4,7 @@ import com.petclinic.visits.visitsservicenew.DataLayer.Visit; import com.petclinic.visits.visitsservicenew.DataLayer.VisitRepo; import com.petclinic.visits.visitsservicenew.DomainClientLayer.*; +import com.petclinic.visits.visitsservicenew.DomainClientLayer.Mailing.MailService; import com.petclinic.visits.visitsservicenew.Exceptions.DuplicateTimeException; import com.petclinic.visits.visitsservicenew.Exceptions.BadRequestException; import com.petclinic.visits.visitsservicenew.Exceptions.NotFoundException; @@ -44,9 +45,10 @@ class VisitServiceImplTest { @MockBean private VetsClient vetsClient; - @MockBean private SimpleJavaMailClient simpleJavaMailClient; @MockBean private PetsClient petsClient; + @MockBean + private MailService mailService; @MockBean private EntityDtoUtil entityDtoUtil; @@ -701,10 +703,4 @@ private Mono buildRequestDtoMono () { VisitRequestDTO requestDTO = buildVisitRequestDTO(); return Mono.just(requestDTO); } - - -} - - - - +} \ No newline at end of file diff --git a/visits-service-new/src/test/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/AuthServiceClientIntegrationTest.java b/visits-service-new/src/test/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/AuthServiceClientIntegrationTest.java new file mode 100644 index 0000000000..e05dd168cc --- /dev/null +++ b/visits-service-new/src/test/java/com/petclinic/visits/visitsservicenew/DomainClientLayer/AuthServiceClientIntegrationTest.java @@ -0,0 +1,65 @@ +package com.petclinic.visits.visitsservicenew.DomainClientLayer; + + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.petclinic.visits.visitsservicenew.DomainClientLayer.Auth.AuthServiceClient; +import com.petclinic.visits.visitsservicenew.DomainClientLayer.Auth.Rethrower; +import com.petclinic.visits.visitsservicenew.DomainClientLayer.Auth.UserDetails; +import lombok.RequiredArgsConstructor; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Mono; +import reactor.test.StepVerifier; + +import java.io.IOException; +@RequiredArgsConstructor +public class AuthServiceClientIntegrationTest { + private MockWebServer server; + private AuthServiceClient authServiceClient; + @BeforeEach + void setup() { + server = new MockWebServer(); + authServiceClient = new AuthServiceClient( + WebClient.builder(), + server.getHostName(), + String.valueOf(server.getPort())); + } + + @AfterEach + void shutdown() throws IOException { + server.shutdown(); + } + + + + + @Test + @DisplayName("Should return user details when valid userId is provided") + void shouldReturnUserDetails_WhenValidUserIdIsProvided() throws IOException { + // Arrange + UserDetails expectedUser = UserDetails.builder() + .username("username") + .userId("userId") + .email("email") + .build(); + String jwtToken = "jwtToken"; + String userId = "userId"; + + server.enqueue(new MockResponse() + .setHeader("Content-Type", "application/json") + .setBody(new ObjectMapper().writeValueAsString(expectedUser))); + + // Act + Mono result = authServiceClient.getUserById(jwtToken, userId); + + // Assert + StepVerifier.create(result) + .expectNext(expectedUser) + .verifyComplete(); + } +}