diff --git a/notification-service/pom.xml b/notification-service/pom.xml index b9d3ef4..3ee33e8 100644 --- a/notification-service/pom.xml +++ b/notification-service/pom.xml @@ -39,5 +39,17 @@ spring-boot-starter-mail 3.4.6 + + ru.project.iakov + user-service + 1.0-SNAPSHOT + compile + + + ru.project.iakov + user-service + 1.0-SNAPSHOT + compile + \ No newline at end of file diff --git a/notification-service/src/main/java/ru/project/iakov/notificationservice/NotificationServiceApplication.java b/notification-service/src/main/java/ru/project/iakov/notificationservice/NotificationServiceApplication.java index c8f0a56..585e2e0 100644 --- a/notification-service/src/main/java/ru/project/iakov/notificationservice/NotificationServiceApplication.java +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/NotificationServiceApplication.java @@ -2,8 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -@SpringBootApplication +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) public class NotificationServiceApplication { public static void main(String[] args) { diff --git a/notification-service/src/main/java/ru/project/iakov/notificationservice/controller/EmailController.java b/notification-service/src/main/java/ru/project/iakov/notificationservice/controller/EmailController.java index adb42d0..09c3793 100644 --- a/notification-service/src/main/java/ru/project/iakov/notificationservice/controller/EmailController.java +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/controller/EmailController.java @@ -3,6 +3,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import ru.project.iakov.notificationservice.dto.EmailRequest; import ru.project.iakov.notificationservice.model.EventType; @@ -12,6 +13,7 @@ @RestController @RequestMapping("/api/v1/email") @RequiredArgsConstructor +@Validated public class EmailController { private final EmailService emailService; diff --git a/notification-service/src/main/java/ru/project/iakov/notificationservice/controller/NotificationController.java b/notification-service/src/main/java/ru/project/iakov/notificationservice/controller/NotificationController.java new file mode 100644 index 0000000..08bda19 --- /dev/null +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/controller/NotificationController.java @@ -0,0 +1,34 @@ +package ru.project.iakov.notificationservice.controller; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.project.iakov.notificationservice.dto.EmailRequest; +import ru.project.iakov.notificationservice.service.EmailService; + + +@Slf4j +@RestController +@RequestMapping("/api/v1/kafka") +@RequiredArgsConstructor +public class NotificationController { + + private final EmailService emailService; + + @PostMapping("/send") + public ResponseEntity sendEmail(@RequestBody EmailRequest request) { + try { + String body = switch (request.getEventType()) { + case CREATED -> "Здравствуйте! Ваш аккаунт был создан."; + case DELETED -> "Здравствуйте! Ваш аккаунт был удалён."; + }; + + emailService.sendEmail(request.getEmail(), request.getSubject(), body); + return ResponseEntity.ok("Письмо отправлено"); + } catch (Exception e) { + return ResponseEntity.internalServerError() + .body("Ошибка: " + e.getMessage()); + } + } +} diff --git a/notification-service/src/main/java/ru/project/iakov/notificationservice/dto/UserEvent.java b/notification-service/src/main/java/ru/project/iakov/notificationservice/dto/UserEvent.java index 232df3b..19cd9fc 100644 --- a/notification-service/src/main/java/ru/project/iakov/notificationservice/dto/UserEvent.java +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/dto/UserEvent.java @@ -11,5 +11,6 @@ @Builder public class UserEvent { private String email; + private String subject; private EventType eventType; } \ No newline at end of file diff --git a/notification-service/src/main/java/ru/project/iakov/notificationservice/service/KafkaConsumerService.java b/notification-service/src/main/java/ru/project/iakov/notificationservice/service/KafkaConsumerService.java index 08de591..119dc17 100644 --- a/notification-service/src/main/java/ru/project/iakov/notificationservice/service/KafkaConsumerService.java +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/service/KafkaConsumerService.java @@ -18,8 +18,7 @@ public class KafkaConsumerService { private final ObjectMapper objectMapper; @KafkaListener(topics = "user-events", groupId = "notification-group") - public void listen(ConsumerRecord record) { - String message = record.value(); + public void listen(String message) { log.info("Kafka message: {}", message); try { UserEvent event = objectMapper.readValue(message, UserEvent.class); diff --git a/user-service/pom.xml b/user-service/pom.xml index 045cabe..45a14a8 100644 --- a/user-service/pom.xml +++ b/user-service/pom.xml @@ -3,51 +3,73 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 + ru.project.iakov user-service 1.0-SNAPSHOT + - 24 - 24 + 21 + 21 UTF-8 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.5 + + + org.postgresql postgresql 42.7.5 + org.projectlombok lombok 1.18.38 provided + org.springframework.boot spring-boot-starter-web - 3.5.0 + org.springframework.boot spring-boot-starter-data-jpa - 3.5.0 + org.springframework.boot spring-boot-starter-test - 3.5.0 test + org.springframework.kafka spring-kafka - 3.3.6 + org.springframework.boot spring-boot-starter-mail - 3.4.6 + + + + org.springdoc + springdoc-openapi-starter-webmvc-ui + 2.5.0 + + + + org.springframework.boot + spring-boot-starter-hateoas - \ No newline at end of file + diff --git a/user-service/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java b/user-service/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java deleted file mode 100644 index fe58216..0000000 --- a/user-service/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java +++ /dev/null @@ -1,35 +0,0 @@ -package ru.project.iakov.homework2.controller; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.ResponseEntity; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.*; -import ru.project.iakov.homework2.dto.UserEvent; -import ru.project.iakov.homework2.service.KafkaProducerService; - -@Slf4j -@RestController -@RequestMapping("/api/v1/kafka") -@RequiredArgsConstructor -@Validated -public class NotificationController { - - private final KafkaProducerService kafkaProducer; - private final ObjectMapper objectMapper; - - @PostMapping("/publish") - public ResponseEntity send(@RequestBody UserEvent request) { - log.info("Получен запрос на публикацию события: {}", request); - try { - String message = objectMapper.writeValueAsString(request); - kafkaProducer.sendUserEvent(message); - return ResponseEntity.ok().build(); - } catch (JsonProcessingException e) { - log.error("Ошибка сериализации события: {}", e.getMessage(), e); - return ResponseEntity.internalServerError().build(); - } - } -} \ No newline at end of file diff --git a/user-service/src/main/java/ru/project/iakov/homework2/controller/UserController.java b/user-service/src/main/java/ru/project/iakov/homework2/controller/UserController.java index 6f3833d..ecefac8 100644 --- a/user-service/src/main/java/ru/project/iakov/homework2/controller/UserController.java +++ b/user-service/src/main/java/ru/project/iakov/homework2/controller/UserController.java @@ -1,49 +1,70 @@ package ru.project.iakov.homework2.controller; +import io.swagger.v3.oas.annotations.Operation; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.hateoas.CollectionModel; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import ru.project.iakov.homework2.dto.UserDto; +import ru.project.iakov.homework2.model.UserModel; +import ru.project.iakov.homework2.model.UserModelAssembler; import ru.project.iakov.homework2.service.UserService; import java.util.List; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + @RestController @RequestMapping("/users") public class UserController { private final UserService userService; + private final UserModelAssembler userModelAssembler; @Autowired - public UserController(UserService userService) { + public UserController(UserService userService, UserModelAssembler userModelAssembler) { this.userService = userService; + this.userModelAssembler = userModelAssembler; } + @Operation(summary = "Получить пользователя по ID") @GetMapping("/{id}") - public ResponseEntity getUserById(@PathVariable("id") Long id) { - UserDto userDto = userService.findById(id); - return ResponseEntity.ok(userDto); + public ResponseEntity getUserById(@PathVariable("id") Long id) { + UserDto dto = userService.findById(id); + return ResponseEntity.ok(userModelAssembler.toModel(dto)); } + @Operation(summary = "Получить всех пользователей") @GetMapping - public ResponseEntity> getAllUsers() { + public ResponseEntity> getAllUsers() { List users = userService.findAll(); - return ResponseEntity.ok(users); + List models = users.stream() + .map(userModelAssembler::toModel) + .toList(); + + CollectionModel collectionModel = CollectionModel.of(models); + collectionModel.add(linkTo(methodOn(UserController.class).getAllUsers()).withSelfRel()); + + return ResponseEntity.ok(collectionModel); } + @Operation(summary = "Создать пользователя") @PostMapping public ResponseEntity createUser(@RequestBody UserDto userDto) { UserDto createdUser = userService.createUser(userDto); return ResponseEntity.status(HttpStatus.CREATED).body(createdUser); } + @Operation(summary = "Обновить данные пользователя") @PutMapping("/{id}") public ResponseEntity updateUser(@PathVariable("id") Long id, @RequestBody UserDto userDto) { UserDto updatedUser = userService.updateUser(id, userDto); return ResponseEntity.ok(updatedUser); } + @Operation(summary = "Удалить пользователя по ID") @DeleteMapping("/{id}") public ResponseEntity deleteUser(@PathVariable("id") Long id) { userService.deleteUser(id); diff --git a/user-service/src/main/java/ru/project/iakov/homework2/model/UserModel.java b/user-service/src/main/java/ru/project/iakov/homework2/model/UserModel.java new file mode 100644 index 0000000..a85abc9 --- /dev/null +++ b/user-service/src/main/java/ru/project/iakov/homework2/model/UserModel.java @@ -0,0 +1,16 @@ +package ru.project.iakov.homework2.model; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.Setter; +import org.springframework.hateoas.RepresentationModel; + +@Getter +@Setter +@AllArgsConstructor +public class UserModel extends RepresentationModel { + private Long id; + private String name; + private String email; + private int age; +} diff --git a/user-service/src/main/java/ru/project/iakov/homework2/model/UserModelAssembler.java b/user-service/src/main/java/ru/project/iakov/homework2/model/UserModelAssembler.java new file mode 100644 index 0000000..7a97841 --- /dev/null +++ b/user-service/src/main/java/ru/project/iakov/homework2/model/UserModelAssembler.java @@ -0,0 +1,28 @@ +package ru.project.iakov.homework2.model; + +import org.springframework.hateoas.server.mvc.RepresentationModelAssemblerSupport; +import org.springframework.stereotype.Component; +import ru.project.iakov.homework2.controller.UserController; +import ru.project.iakov.homework2.dto.UserDto; + +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.linkTo; +import static org.springframework.hateoas.server.mvc.WebMvcLinkBuilder.methodOn; + +@Component +public class UserModelAssembler extends RepresentationModelAssemblerSupport { + + public UserModelAssembler() { + super(UserController.class, UserModel.class); + } + + @Override + public UserModel toModel(UserDto dto) { + UserModel model = new UserModel(dto.getId(), dto.getName(), dto.getEmail(), dto.getAge()); + + model.add(linkTo(methodOn(UserController.class).getUserById(dto.getId())).withSelfRel()); + model.add(linkTo(methodOn(UserController.class).getAllUsers()).withRel("all-users")); + model.add(linkTo(methodOn(UserController.class).deleteUser(dto.getId())).withRel("delete")); + + return model; + } +} \ No newline at end of file diff --git a/user-service/src/main/java/ru/project/iakov/homework2/service/UserService.java b/user-service/src/main/java/ru/project/iakov/homework2/service/UserService.java index 3de3493..c6293b3 100644 --- a/user-service/src/main/java/ru/project/iakov/homework2/service/UserService.java +++ b/user-service/src/main/java/ru/project/iakov/homework2/service/UserService.java @@ -12,7 +12,6 @@ import ru.project.iakov.homework2.model.EventType; import ru.project.iakov.homework2.repository.UserRepository; -import java.time.LocalDateTime; import java.util.List; import java.util.stream.Collectors; diff --git a/user-service/src/test/java/ru/project/iakov/homework2/controller/EmailControllerTest.java b/user-service/src/test/java/ru/project/iakov/homework2/controller/EmailControllerTest.java deleted file mode 100644 index ae09553..0000000 --- a/user-service/src/test/java/ru/project/iakov/homework2/controller/EmailControllerTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package ru.project.iakov.homework2.controller; - -import com.fasterxml.jackson.databind.ObjectMapper; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -import org.springframework.test.web.servlet.MockMvc; -import ru.project.iakov.homework2.dto.EmailRequest; -import ru.project.iakov.homework2.service.EmailService; - -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - -@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) -@AutoConfigureMockMvc -public class EmailControllerTest { - @Autowired - private MockMvc mockMvc; - - @MockitoBean - private EmailService emailService; - - @Test - public void testSendEmail_success() throws Exception { - EmailRequest request = new EmailRequest("lysenko_iakov@yahoo.com", "Тест", "Тест"); - - mockMvc.perform(post("/api/v1/email/send") - .contentType(MediaType.APPLICATION_JSON) - .content(new ObjectMapper().writeValueAsString(request))) - .andExpect(status().isOk()); - - verify(emailService, times(1)).sendEmail("lysenko_iakov@yahoo.com", "Тест", "Тест"); - } -} \ No newline at end of file diff --git a/user-service/src/test/java/ru/project/iakov/homework2/controller/UserControllerTest.java b/user-service/src/test/java/ru/project/iakov/homework2/controller/UserControllerTest.java index 4873e5d..5fea5ee 100644 --- a/user-service/src/test/java/ru/project/iakov/homework2/controller/UserControllerTest.java +++ b/user-service/src/test/java/ru/project/iakov/homework2/controller/UserControllerTest.java @@ -5,8 +5,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.http.MediaType; -import org.springframework.test.context.bean.override.mockito.MockitoBean; import org.springframework.test.web.servlet.MockMvc; import ru.project.iakov.homework2.dto.UserDto; import ru.project.iakov.homework2.service.UserService; @@ -29,7 +29,7 @@ class UserControllerTest { @Autowired private MockMvc mockMvc; - @MockitoBean + @MockBean private UserService userService; @Autowired diff --git a/user-service/src/test/java/ru/project/iakov/homework2/kafka/KafkaConsumerTest.java b/user-service/src/test/java/ru/project/iakov/homework2/kafka/KafkaConsumerTest.java deleted file mode 100644 index e2854d5..0000000 --- a/user-service/src/test/java/ru/project/iakov/homework2/kafka/KafkaConsumerTest.java +++ /dev/null @@ -1,50 +0,0 @@ -package ru.project.iakov.homework2.kafka; - -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.bean.override.mockito.MockitoBean; -import ru.project.iakov.homework2.dto.UserEvent; -import ru.project.iakov.homework2.service.EmailService; - -import static org.mockito.ArgumentMatchers.*; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; - -@AutoConfigureMockMvc -@SpringBootTest -public class KafkaConsumerTest { - - @Autowired - private KafkaConsumerService kafkaConsumer; - - @MockitoBean - private EmailService emailService; - - @Test - public void testKafkaConsumer_sendsEmailOnCreatedEvent() { - UserEvent event = UserEvent.builder() - .email("lysenko_iakov@yahoo.com") - .subject("Тест: создание") - .eventType("CREATED") - .build(); - - kafkaConsumer.listen(event); - - verify(emailService, times(1)).sendEmail(eq("lysenko_iakov@yahoo.com"), anyString(), contains("создан")); - } - - @Test - public void testKafkaConsumer_sendsEmailOnDeletedEvent() { - UserEvent event = UserEvent.builder() - .email("lysenko_iakov@yahoo.com") - .subject("Тест: удаление") - .eventType("DELETED") - .build(); - - kafkaConsumer.listen(event); - - verify(emailService, times(1)).sendEmail(eq("lysenko_iakov@yahoo.com"), anyString(), contains("удалён")); - } -} \ No newline at end of file