diff --git a/Homework/pom.xml b/Homework/pom.xml deleted file mode 100644 index c051b94..0000000 --- a/Homework/pom.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - 4.0.0 - ru.project.iakov - Homework - 1.0-SNAPSHOT - - 24 - 24 - UTF-8 - - - - org.hibernate.orm - hibernate-core - 6.6.15.Final - - - org.postgresql - postgresql - 42.7.5 - - - org.slf4j - slf4j-api - 2.0.13 - - - org.projectlombok - lombok - 1.18.38 - provided - - - ch.qos.logback - logback-classic - 1.5.18 - compile - - - org.junit.jupiter - junit-jupiter - 5.10.0 - test - - - org.testcontainers - postgresql - 1.19.0 - test - - - org.testcontainers - junit-jupiter - 1.21.1 - test - - - org.slf4j - slf4j-log4j12 - 2.1.0-alpha1 - pom - - - org.mockito - mockito-core - 5.8.0 - test - - - org.assertj - assertj-core - 3.25.1 - test - - - org.springframework.boot - spring-boot - 3.5.0 - - - 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 - - - com.fasterxml.jackson.core - jackson-annotations - 3.0-rc5 - - - \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/legacy/HibernateUtil.java b/Homework/src/main/java/ru/project/iakov/homework2/legacy/HibernateUtil.java deleted file mode 100644 index a6542a9..0000000 --- a/Homework/src/main/java/ru/project/iakov/homework2/legacy/HibernateUtil.java +++ /dev/null @@ -1,40 +0,0 @@ -/* -package ru.project.iakov.homework2.legacy; - -import lombok.Getter; -import org.hibernate.SessionFactory; -import org.hibernate.cfg.Configuration; -import ru.project.iakov.homework2.User; - -public class HibernateUtil { - @Getter - private static SessionFactory sessionFactory = buildSessionFactory(); - - private static SessionFactory buildSessionFactory() { - try { - Configuration configuration = new Configuration(); - configuration.configure(); - configuration.addAnnotatedClass(User.class); - return configuration.buildSessionFactory(); - } catch (Exception e) { - System.err.println(e.getMessage()); - throw new ExceptionInInitializerError(e); - } - } - - public static void rebuildSessionFactoryForTests() { - if (sessionFactory != null) { - sessionFactory.close(); - } - - Configuration configuration = new Configuration(); - configuration.configure("hibernate.cfg.xml"); - configuration.setProperty("hibernate.connection.url", System.getProperty("DB_URL")); - configuration.setProperty("hibernate.connection.username", System.getProperty("DB_USERNAME")); - configuration.setProperty("hibernate.connection.password", System.getProperty("DB_PASSWORD")); - - configuration.addAnnotatedClass(User.class); - - sessionFactory = configuration.buildSessionFactory(); - } -}*/ diff --git a/Homework/src/main/java/ru/project/iakov/homework2/legacy/Runner.java b/Homework/src/main/java/ru/project/iakov/homework2/legacy/Runner.java deleted file mode 100644 index ae9fe60..0000000 --- a/Homework/src/main/java/ru/project/iakov/homework2/legacy/Runner.java +++ /dev/null @@ -1,138 +0,0 @@ -/* -package ru.project.iakov.homework2.legacy; - -import ru.project.iakov.homework2.User; -import ru.project.iakov.homework2.dao.UserDaoImpl; -import ru.project.iakov.homework2.service.UserService; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; -import java.util.Scanner; - -public class Runner { - private static final Scanner scanner = new Scanner(System.in); - private static final UserService userService = new UserService(new UserDaoImpl()); - - public static void main(String[] args) { - while (true) { - printMenu(); - int choice = readInt("Выберите пункт меню: "); - - switch (choice) { - case 1 -> createUser(); - case 2 -> findUserById(); - case 3 -> findAllUsers(); - case 4 -> updateUser(); - case 5 -> deleteUser(); - case 6 -> exitApp(); - default -> System.out.println("Неверный пункт меню. Попробуйте снова."); - } - } - } - - private static void printMenu() { - System.out.println(""" - ===== Меню ===== - 1. Создать пользователя - 2. Найти пользователя по ID - 3. Показать всех пользователей - 4. Обновить пользователя - 5. Удалить пользователя - 6. Выход - """); - } - - private static void createUser() { - String name = readLine("Имя: "); - String email = readLine("Email: "); - int age = readInt("Возраст: "); - - User user = User.builder() - .name(name) - .email(email) - .age(age) - .createdAt(LocalDateTime.now()) - .build(); - - userService.createUser(user); - System.out.println("Пользователь создан."); - } - - private static void findUserById() { - long id = readLong("Введите ID пользователя: "); - Optional userOpt = userService.findById(id); - userOpt.ifPresentOrElse( - System.out::println, - () -> System.out.println("Пользователь не найден.") - ); - } - - private static void findAllUsers() { - List users = userService.findAll(); - if (users.isEmpty()) { - System.out.println("Нет пользователей в базе."); - } else { - users.forEach(System.out::println); - } - } - - private static void updateUser() { - long id = readLong("ID обновляемого пользователя: "); - Optional userOpt = userService.findById(id); - - if (userOpt.isPresent()) { - User user = userOpt.get(); - String name = readLine("Новое имя (" + user.getName() + "): "); - String email = readLine("Новый email (" + user.getEmail() + "): "); - int age = readInt("Новый возраст (" + user.getAge() + "): "); - - user.setName(name.isEmpty() ? user.getName() : name); - user.setEmail(email.isEmpty() ? user.getEmail() : email); - user.setAge(age <= 0 ? user.getAge() : age); - - userService.update(user); - System.out.println("Пользователь обновлён."); - } else { - System.out.println("Пользователь с таким ID не найден."); - } - } - - private static void deleteUser() { - long id = readLong("ID пользователя для удаления: "); - userService.delete(id); - System.out.println("Пользователь удалён (если существовал)."); - } - - private static void exitApp() { - System.out.println("Завершение работы."); - System.exit(0); - } - - private static int readInt(String prompt) { - System.out.print(prompt); - while (!scanner.hasNextInt()) { - scanner.nextLine(); - System.out.print("Введите целое число: "); - } - int value = scanner.nextInt(); - scanner.nextLine(); - return value; - } - - private static long readLong(String prompt) { - System.out.print(prompt); - while (!scanner.hasNextLong()) { - scanner.nextLine(); - System.out.print("Введите корректный ID: "); - } - long value = scanner.nextLong(); - scanner.nextLine(); - return value; - } - - private static String readLine(String prompt) { - System.out.print(prompt); - return scanner.nextLine().trim(); - } -}*/ diff --git a/Homework/src/main/java/ru/project/iakov/homework2/legacy/dao/UserDao.java b/Homework/src/main/java/ru/project/iakov/homework2/legacy/dao/UserDao.java deleted file mode 100644 index 5063c18..0000000 --- a/Homework/src/main/java/ru/project/iakov/homework2/legacy/dao/UserDao.java +++ /dev/null @@ -1,15 +0,0 @@ -/* -package ru.project.iakov.homework2.legacy.dao; - -import ru.project.iakov.homework2.User; - -import java.util.List; -import java.util.Optional; - -public interface UserDao { - void create(User user); - Optional findById(Long id); - List findAll(); - void update(User user); - void delete(Long id); -}*/ diff --git a/Homework/src/main/java/ru/project/iakov/homework2/legacy/dao/UserDaoImpl.java b/Homework/src/main/java/ru/project/iakov/homework2/legacy/dao/UserDaoImpl.java deleted file mode 100644 index 251369c..0000000 --- a/Homework/src/main/java/ru/project/iakov/homework2/legacy/dao/UserDaoImpl.java +++ /dev/null @@ -1,57 +0,0 @@ -/* -package ru.project.iakov.homework2.legacy.dao; - -import ru.project.iakov.homework2.legacy.HibernateUtil; -import ru.project.iakov.homework2.User; -import org.hibernate.Session; -import org.hibernate.Transaction; - -import java.util.List; -import java.util.Optional; - -public class UserDaoImpl implements UserDao { - - @Override - public void create(User user) { - try (Session session = HibernateUtil.getSessionFactory().openSession()) { - Transaction tx = session.beginTransaction(); - session.persist(user); - tx.commit(); - } - } - - @Override - public Optional findById(Long id) { - try (Session session = HibernateUtil.getSessionFactory().openSession()) { - return Optional.ofNullable(session.get(User.class, id)); - } - } - - @Override - public List findAll() { - try (Session session = HibernateUtil.getSessionFactory().openSession()) { - return session.createQuery("from User", User.class).list(); - } - } - - @Override - public void update(User user) { - try (Session session = HibernateUtil.getSessionFactory().openSession()) { - Transaction tx = session.beginTransaction(); - session.merge(user); - tx.commit(); - } - } - - @Override - public void delete(Long id) { - try (Session session = HibernateUtil.getSessionFactory().openSession()) { - Transaction tx = session.beginTransaction(); - User user = session.get(User.class, id); - if (user != null) { - session.remove(user); - } - tx.commit(); - } - } -}*/ diff --git a/Homework/src/main/java/ru/project/iakov/homework2/service/UserService.java b/Homework/src/main/java/ru/project/iakov/homework2/service/UserService.java deleted file mode 100644 index 5910e1e..0000000 --- a/Homework/src/main/java/ru/project/iakov/homework2/service/UserService.java +++ /dev/null @@ -1,53 +0,0 @@ -package ru.project.iakov.homework2.service; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import ru.project.iakov.homework2.User; -import ru.project.iakov.homework2.UserDto; -import ru.project.iakov.homework2.UserMapper; -import ru.project.iakov.homework2.UserRepository; -import java.util.List; -import java.util.stream.Collectors; - -@Service -public class UserService { - private UserRepository userRepository; - private UserMapper userMapper; - - @Autowired - public UserService(UserRepository userRepository, UserMapper userMapper) { - this.userRepository = userRepository; - this.userMapper = userMapper; - } - - public UserDto createUser(UserDto userDto) { - User user = userMapper.toEntity(userDto); - User savedUser = userRepository.save(user); - return userMapper.toDto(savedUser); - } - public UserDto findById(Long id) { - User user = userRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException()); - return userMapper.toDto(user); - } - public List findAll() { - List users = userRepository.findAll(); - return users.stream() - .map(userMapper::toDto) - .collect(Collectors.toList()); - } - public UserDto updateUser(Long id, UserDto userDto) { - User existingUser = userRepository.findById(id) - .orElseThrow(() -> new IllegalArgumentException()); - existingUser.setName(userDto.getName()); - existingUser.setEmail(userDto.getEmail()); - User updatedUser = userRepository.save(existingUser); - return userMapper.toDto(updatedUser); - } - public void deleteUser(Long id) { - if (!userRepository.existsById(id)) { - throw new IllegalArgumentException(); - } - userRepository.deleteById(id); - } -} \ No newline at end of file diff --git a/Homework/src/main/resources/application.yml b/Homework/src/main/resources/application.yml deleted file mode 100644 index dc80496..0000000 --- a/Homework/src/main/resources/application.yml +++ /dev/null @@ -1,18 +0,0 @@ -spring: - datasource: - url: jdbc:postgresql://localhost:5432/postgres - username: postgres - password: postgres - jpa: - hibernate: - ddl-auto: update - show-sql: true - properties: - hibernate: - format_sql: true - jackson: - serialization: - INDENT_OUTPUT: true - -server: - port: 8080 \ No newline at end of file diff --git a/Homework/src/test/java/ru/project/iakov/homework2/legacy/SimpleContainerTest.java b/Homework/src/test/java/ru/project/iakov/homework2/legacy/SimpleContainerTest.java deleted file mode 100644 index 5769800..0000000 --- a/Homework/src/test/java/ru/project/iakov/homework2/legacy/SimpleContainerTest.java +++ /dev/null @@ -1,161 +0,0 @@ -/* -package ru.project.iakov.homework2.legacy; - -import jakarta.persistence.PersistenceException; -import org.junit.jupiter.api.*; -import org.testcontainers.containers.PostgreSQLContainer; -import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; -import ru.project.iakov.homework2.User; -import ru.project.iakov.homework2.legacy.dao.UserDao; -import ru.project.iakov.homework2.legacy.dao.UserDaoImpl; -import ru.project.iakov.homework2.legacy.HibernateUtil; - -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; - -@Testcontainers -public class SimpleContainerTest { - private static UserDao userDao; - @Container - public static PostgreSQLContainer postgres = new PostgreSQLContainer<>("postgres") - .withDatabaseName("postgres") - .withUsername("postgres") - .withPassword("postgres"); - - @BeforeAll - static void setUp() { - postgres.start(); - System.setProperty("DB_URL", postgres.getJdbcUrl()); - System.setProperty("DB_USERNAME", postgres.getUsername()); - System.setProperty("DB_PASSWORD", postgres.getPassword()); - - HibernateUtil.rebuildSessionFactoryForTests(); - userDao = new UserDaoImpl(); - } - - @DisplayName("Создание и поиск по ID") - @Test - @Order(1) - void shouldCreateAndFindUserById() { - User user = User.builder() - .name("Iakov") - .email("lysenko_iakov@yahoo.com") - .age(25) - .createdAt(LocalDateTime.now()) - .build(); - - userDao.create(user); - - assertNotNull(user.getId()); - - Optional saved = userDao.findById(user.getId()); - assertTrue(saved.isPresent()); - assertEquals("Iakov", saved.get().getName()); - } - - @DisplayName("Обновление пользователя") - @Test - @Order(2) - void shouldUpdateUser() { - User user = User.builder() - .name("Original") - .email("original@email.com") - .age(30) - .createdAt(LocalDateTime.now()) - .build(); - - userDao.create(user); - user.setName("Updated"); - user.setEmail("updated@email.com"); - - userDao.update(user); - - Optional updated = userDao.findById(user.getId()); - assertTrue(updated.isPresent()); - assertEquals("Updated", updated.get().getName()); - assertEquals("updated@email.com", updated.get().getEmail()); - } - - @DisplayName("Удаление существующего пользователя") - @Test - @Order(3) - void shouldDeleteUser() { - User user = User.builder() - .name("ToDelete") - .email("delete@example.com") - .age(40) - .createdAt(LocalDateTime.now()) - .build(); - - userDao.create(user); - Long id = user.getId(); - - userDao.delete(id); - - Optional deleted = userDao.findById(id); - assertFalse(deleted.isPresent()); - } - - @DisplayName("Поиск по несуществующему ID") - @Test - @Order(4) - void shouldReturnEmptyForNonExistingUser() { - Optional user = userDao.findById(99999L); - assertTrue(user.isEmpty()); - } - - @DisplayName("Удаление несуществующего пользователя") - @Test - @Order(5) - void shouldNotThrowWhenDeletingNonExistingUser() { - assertDoesNotThrow(() -> userDao.delete(99999L)); - } - - @DisplayName("Создание пользователя с null-именем должно выбросить исключение") - @Test - @Order(6) - void shouldThrowWhenCreatingUserWithNullName() { - User user = User.builder() - .email("nullname@example.com") - .age(30) - .createdAt(LocalDateTime.now()) - .build(); - - assertThrows(PersistenceException.class, () -> userDao.create(user)); - } - - @DisplayName("Найти всех пользователей") - @Test - @Order(7) - void shouldFindAllUsers() { - User user1 = User.builder() - .name("User1") - .email("user1@email.com") - .age(20) - .createdAt(LocalDateTime.now()) - .build(); - - User user2 = User.builder() - .name("User2") - .email("user2@email.com") - .age(25) - .createdAt(LocalDateTime.now()) - .build(); - - userDao.create(user1); - userDao.create(user2); - - List allUsers = userDao.findAll(); - - assertTrue(allUsers.size() >= 2); - } - - @AfterAll - static void stopContainer() { - postgres.stop(); - } -}*/ diff --git a/Homework/src/test/java/ru/project/iakov/homework2/legacy/service/UserServiceTest.java b/Homework/src/test/java/ru/project/iakov/homework2/legacy/service/UserServiceTest.java deleted file mode 100644 index d78267f..0000000 --- a/Homework/src/test/java/ru/project/iakov/homework2/legacy/service/UserServiceTest.java +++ /dev/null @@ -1,126 +0,0 @@ -/* -package ru.project.iakov.homework2.legacy.service; - -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.BeforeEach; -import ru.project.iakov.homework2.User; -import ru.project.iakov.homework2.legacy.dao.UserDao; - -import java.util.Arrays; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Optional; - -import static org.junit.jupiter.api.Assertions.*; -import static org.mockito.Mockito.*; - -public class UserServiceTest { - private UserDao userDao; - private UserService userService; - - @BeforeEach - void setUp() { - userDao = mock(UserDao.class); - userService = new UserService(userDao); - } - - @DisplayName("Создание пользователя") - @Test - public void createUser() { - User user = new User(); - userService.createUser(user); - - verify(userDao, times(1)).create(user); - } - - @DisplayName("Пользователь найден по ID") - @Test - public void findById_shouldReturnUser_whenUserExist() { - User user = new User(); - user.setId(1L); - - when(userDao.findById(1L)).thenReturn(Optional.of(user)); - - Optional result = userService.findById(1L); - - assertTrue(result.isPresent()); - assertEquals(1L, result.get().getId()); - verify(userDao, times(1)).findById(1L); - } - - @DisplayName("Такого пользователя нет") - @Test - public void findById_shouldReturnEmpty_whenUserNotExist() { - when(userDao.findById(1L)).thenReturn(Optional.empty()); - - Optional result = userService.findById(1L); - - assertFalse(result.isPresent()); - verify(userDao, times(1)).findById(1L); - } - - @DisplayName("Отрицательный ID — выбрасывается исключение") - @Test - void findById_shouldThrowException_whenIdIsNegative() { - assertThrows(IllegalArgumentException.class, () -> userService.findById(-1L)); - verifyNoInteractions(userDao); - } - - @DisplayName("ID равен нулю — выбрасывается исключение") - @Test - void findById_shouldThrowException_whenIdIsZero() { - assertThrows(IllegalArgumentException.class, () -> userService.findById(0L)); - verifyNoInteractions(userDao); - } - - @DisplayName("ID равен null — выбрасывается исключение") - @Test - void findById_shouldThrowException_whenIdIsNull() { - assertThrows(IllegalArgumentException.class, () -> userService.findById(null)); - verifyNoInteractions(userDao); - } - - @DisplayName("Получение списка всех пользователей") - @Test - public void testGetAllUsers() { - List users = Arrays.asList(new User(), new User()); - when(userDao.findAll()).thenReturn(users); - - List result = userService.findAll(); - - assertEquals(2, result.size()); - verify(userDao, times(1)).findAll(); - } - - @DisplayName("Обновление данных пользователя") - @Test - public void updateUser() { - User user = new User(); - user.setId(2L); - - userService.update(user); - - verify(userDao, times(1)).update(user); - } - - @DisplayName("Пользователь успешно удален") - @Test - public void deleteUser_whenUserExist() { - userService.delete(3L); - - verify(userDao, times(1)).delete(3L); - } - - @DisplayName("При удалении пользователь не найден") - @Test - public void deleteUser_whenUserNotExist() { - Long noExistId = 999L; - doThrow(new NoSuchElementException("Пользователь не найден")) - .when(userDao).delete(noExistId); - - assertThrows(NoSuchElementException.class, () -> userService.delete(noExistId)); - - verify(userDao).delete(noExistId); - } -}*/ diff --git a/notification-service/pom.xml b/notification-service/pom.xml new file mode 100644 index 0000000..b9d3ef4 --- /dev/null +++ b/notification-service/pom.xml @@ -0,0 +1,43 @@ + + + 4.0.0 + ru.project.iakov + user-service + 1.0-SNAPSHOT + + 24 + 24 + UTF-8 + + + + org.projectlombok + lombok + 1.18.38 + provided + + + org.springframework.boot + spring-boot-starter-web + 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 + + + \ 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 new file mode 100644 index 0000000..c8f0a56 --- /dev/null +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/NotificationServiceApplication.java @@ -0,0 +1,13 @@ +package ru.project.iakov.notificationservice; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class NotificationServiceApplication { + + public static void main(String[] args) { + SpringApplication.run(NotificationServiceApplication.class, 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 new file mode 100644 index 0000000..adb42d0 --- /dev/null +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/controller/EmailController.java @@ -0,0 +1,35 @@ +package ru.project.iakov.notificationservice.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import ru.project.iakov.notificationservice.dto.EmailRequest; +import ru.project.iakov.notificationservice.model.EventType; +import ru.project.iakov.notificationservice.service.EmailService; + + +@RestController +@RequestMapping("/api/v1/email") +@RequiredArgsConstructor +public class EmailController { + + private final EmailService emailService; + + @PostMapping("/send") + public ResponseEntity sendEmail(@RequestBody EmailRequest request) { + try { + emailService.sendEmail(request.getEmail(), request.getSubject(), generateText(request.getEventType())); + return ResponseEntity.ok("Письмо отправлено"); + } catch (Exception e) { + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Ошибка при отправке письма: " + e.getMessage()); + } + } + private String generateText(EventType eventType) { + return switch (eventType) { + case CREATED -> "Здравствуйте! Ваш аккаунт был создан."; + case DELETED -> "Здравствуйте! Ваш аккаунт был удалён."; + }; + } +} \ No newline at end of file diff --git a/notification-service/src/main/java/ru/project/iakov/notificationservice/dto/EmailRequest.java b/notification-service/src/main/java/ru/project/iakov/notificationservice/dto/EmailRequest.java new file mode 100644 index 0000000..ae59c07 --- /dev/null +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/dto/EmailRequest.java @@ -0,0 +1,13 @@ +package ru.project.iakov.notificationservice.dto; + +import lombok.*; +import ru.project.iakov.notificationservice.model.EventType; + +@Data +@NoArgsConstructor +@AllArgsConstructor +public class EmailRequest { + 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/dto/UserEvent.java b/notification-service/src/main/java/ru/project/iakov/notificationservice/dto/UserEvent.java new file mode 100644 index 0000000..232df3b --- /dev/null +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/dto/UserEvent.java @@ -0,0 +1,15 @@ +package ru.project.iakov.notificationservice.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.*; +import ru.project.iakov.notificationservice.model.EventType; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +@Builder +public class UserEvent { + private String email; + private EventType eventType; +} \ No newline at end of file diff --git a/notification-service/src/main/java/ru/project/iakov/notificationservice/model/EventType.java b/notification-service/src/main/java/ru/project/iakov/notificationservice/model/EventType.java new file mode 100644 index 0000000..b34bd25 --- /dev/null +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/model/EventType.java @@ -0,0 +1,6 @@ +package ru.project.iakov.notificationservice.model; + +public enum EventType { + CREATED, + DELETED +} diff --git a/notification-service/src/main/java/ru/project/iakov/notificationservice/service/EmailService.java b/notification-service/src/main/java/ru/project/iakov/notificationservice/service/EmailService.java new file mode 100644 index 0000000..c81a712 --- /dev/null +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/service/EmailService.java @@ -0,0 +1,36 @@ +package ru.project.iakov.notificationservice.service; + + +import jakarta.mail.internet.MimeMessage; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.mail.javamail.JavaMailSender; +import org.springframework.mail.javamail.MimeMessageHelper; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class EmailService { + + private final JavaMailSender mailSender; + + @Value("${spring.mail.username}") + private String fromEmail; + + public void sendEmail(String to, String subject, String text) { + try { + MimeMessage mimeMessage = mailSender.createMimeMessage(); + MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, false, "utf-8"); + helper.setFrom(fromEmail); + helper.setTo(to); + helper.setSubject(subject); + helper.setText(text); + mailSender.send(mimeMessage); + } catch (Exception e) { + log.error("Ошибка при отправке email на {}: {}", to, e.getMessage(), e); + throw new RuntimeException("Не удалось отправить email", e); + } + } +} \ 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 new file mode 100644 index 0000000..08de591 --- /dev/null +++ b/notification-service/src/main/java/ru/project/iakov/notificationservice/service/KafkaConsumerService.java @@ -0,0 +1,39 @@ +package ru.project.iakov.notificationservice.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.kafka.clients.consumer.ConsumerRecord; +import org.springframework.kafka.annotation.KafkaListener; +import org.springframework.stereotype.Component; +import ru.project.iakov.notificationservice.dto.UserEvent; +import ru.project.iakov.notificationservice.model.EventType; + +@Component +@RequiredArgsConstructor +@Slf4j +public class KafkaConsumerService { + + private final EmailService emailService; + private final ObjectMapper objectMapper; + + @KafkaListener(topics = "user-events", groupId = "notification-group") + public void listen(ConsumerRecord record) { + String message = record.value(); + log.info("Kafka message: {}", message); + try { + UserEvent event = objectMapper.readValue(message, UserEvent.class); + log.info("Получено сообщение из Kafka: {}", event); + emailService.sendEmail(event.getEmail(), "Уведомление", generateText(event.getEventType())); + } catch (Exception e) { + log.error("Ошибка обработки сообщения: {}", e.getMessage(), e); + } + } + + private String generateText(EventType eventType) { + return switch (eventType) { + case CREATED -> "Здравствуйте! Ваш аккаунт был создан."; + case DELETED -> "Здравствуйте! Ваш аккаунт был удалён."; + }; + } +} \ No newline at end of file diff --git a/notification-service/src/main/resources/application.yml b/notification-service/src/main/resources/application.yml new file mode 100644 index 0000000..be161d0 --- /dev/null +++ b/notification-service/src/main/resources/application.yml @@ -0,0 +1,24 @@ +spring: + kafka: + bootstrap-servers: localhost:9092 + consumer: + group-id: notification-group + auto-offset-reset: earliest + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: org.apache.kafka.common.serialization.StringDeserializer + + mail: + host: smtp.mail.ru + port: 587 + username: ${EMAIL_USERNAME} + password: ${EMAIL_PASSWORD} + properties: + mail.smtp.auth: true + mail.smtp.starttls.enable: true + +logging: + level: + root: INFO + +server: + port: 8081 \ No newline at end of file diff --git a/user-service/pom.xml b/user-service/pom.xml new file mode 100644 index 0000000..045cabe --- /dev/null +++ b/user-service/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + ru.project.iakov + user-service + 1.0-SNAPSHOT + + 24 + 24 + UTF-8 + + + + 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 + + + \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/UserServiceApplication.java b/user-service/src/main/java/ru/project/iakov/homework2/UserServiceApplication.java similarity index 100% rename from Homework/src/main/java/ru/project/iakov/homework2/UserServiceApplication.java rename to user-service/src/main/java/ru/project/iakov/homework2/UserServiceApplication.java 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 new file mode 100644 index 0000000..fe58216 --- /dev/null +++ b/user-service/src/main/java/ru/project/iakov/homework2/controller/NotificationController.java @@ -0,0 +1,35 @@ +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/Homework/src/main/java/ru/project/iakov/homework2/UserController.java b/user-service/src/main/java/ru/project/iakov/homework2/controller/UserController.java similarity index 95% rename from Homework/src/main/java/ru/project/iakov/homework2/UserController.java rename to user-service/src/main/java/ru/project/iakov/homework2/controller/UserController.java index 6c3ca40..6f3833d 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/UserController.java +++ b/user-service/src/main/java/ru/project/iakov/homework2/controller/UserController.java @@ -1,9 +1,10 @@ -package ru.project.iakov.homework2; +package ru.project.iakov.homework2.controller; import org.springframework.beans.factory.annotation.Autowired; 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.service.UserService; import java.util.List; diff --git a/Homework/src/main/java/ru/project/iakov/homework2/UserDto.java b/user-service/src/main/java/ru/project/iakov/homework2/dto/UserDto.java similarity index 78% rename from Homework/src/main/java/ru/project/iakov/homework2/UserDto.java rename to user-service/src/main/java/ru/project/iakov/homework2/dto/UserDto.java index a789830..7660dce 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/UserDto.java +++ b/user-service/src/main/java/ru/project/iakov/homework2/dto/UserDto.java @@ -1,4 +1,4 @@ -package ru.project.iakov.homework2; +package ru.project.iakov.homework2.dto; import lombok.*; import java.time.LocalDateTime; @@ -11,8 +11,11 @@ public class UserDto { @Getter @Setter private Long id; + @NonNull private String name; + @NonNull private String email; + @NonNull private Integer age; private LocalDateTime createdAt; } \ No newline at end of file diff --git a/user-service/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java b/user-service/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java new file mode 100644 index 0000000..e610159 --- /dev/null +++ b/user-service/src/main/java/ru/project/iakov/homework2/dto/UserEvent.java @@ -0,0 +1,15 @@ +package ru.project.iakov.homework2.dto; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.*; +import ru.project.iakov.homework2.model.EventType; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +@JsonIgnoreProperties(ignoreUnknown = true) +public class UserEvent { + private String email; + private EventType eventType; +} \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/User.java b/user-service/src/main/java/ru/project/iakov/homework2/entity/User.java similarity index 75% rename from Homework/src/main/java/ru/project/iakov/homework2/User.java rename to user-service/src/main/java/ru/project/iakov/homework2/entity/User.java index d03b7ae..c24753b 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/User.java +++ b/user-service/src/main/java/ru/project/iakov/homework2/entity/User.java @@ -1,4 +1,4 @@ -package ru.project.iakov.homework2; +package ru.project.iakov.homework2.entity; import jakarta.persistence.*; import lombok.AllArgsConstructor; @@ -24,4 +24,10 @@ public class User { @Column(nullable = false) private int age; private LocalDateTime createdAt; + @PrePersist + public void prePersist() { + if (createdAt == null) { + createdAt = LocalDateTime.now(); + } + } } \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/UserMapper.java b/user-service/src/main/java/ru/project/iakov/homework2/mapper/UserMapper.java similarity index 81% rename from Homework/src/main/java/ru/project/iakov/homework2/UserMapper.java rename to user-service/src/main/java/ru/project/iakov/homework2/mapper/UserMapper.java index e3fc5b6..b75741e 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/UserMapper.java +++ b/user-service/src/main/java/ru/project/iakov/homework2/mapper/UserMapper.java @@ -1,5 +1,7 @@ -package ru.project.iakov.homework2; +package ru.project.iakov.homework2.mapper; import org.springframework.stereotype.Component; +import ru.project.iakov.homework2.entity.User; +import ru.project.iakov.homework2.dto.UserDto; @Component public class UserMapper { diff --git a/user-service/src/main/java/ru/project/iakov/homework2/model/EventType.java b/user-service/src/main/java/ru/project/iakov/homework2/model/EventType.java new file mode 100644 index 0000000..897bd29 --- /dev/null +++ b/user-service/src/main/java/ru/project/iakov/homework2/model/EventType.java @@ -0,0 +1,6 @@ +package ru.project.iakov.homework2.model; + +public enum EventType { + CREATED, + DELETED +} \ No newline at end of file diff --git a/Homework/src/main/java/ru/project/iakov/homework2/UserRepository.java b/user-service/src/main/java/ru/project/iakov/homework2/repository/UserRepository.java similarity index 58% rename from Homework/src/main/java/ru/project/iakov/homework2/UserRepository.java rename to user-service/src/main/java/ru/project/iakov/homework2/repository/UserRepository.java index ef9f458..0479fba 100644 --- a/Homework/src/main/java/ru/project/iakov/homework2/UserRepository.java +++ b/user-service/src/main/java/ru/project/iakov/homework2/repository/UserRepository.java @@ -1,5 +1,6 @@ -package ru.project.iakov.homework2; +package ru.project.iakov.homework2.repository; import org.springframework.data.jpa.repository.JpaRepository; +import ru.project.iakov.homework2.entity.User; public interface UserRepository extends JpaRepository { } \ No newline at end of file diff --git a/user-service/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java b/user-service/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java new file mode 100644 index 0000000..8671267 --- /dev/null +++ b/user-service/src/main/java/ru/project/iakov/homework2/service/KafkaProducerService.java @@ -0,0 +1,20 @@ +package ru.project.iakov.homework2.service; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.kafka.core.KafkaTemplate; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +@Slf4j +public class KafkaProducerService { + + private final KafkaTemplate kafkaTemplate; + private static final String TOPIC = "user-events"; + + public void sendUserEvent(String message) { + log.info("Отправка сообщения в Kafka: {}", message); + kafkaTemplate.send(TOPIC, message); + } +} \ 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 new file mode 100644 index 0000000..3de3493 --- /dev/null +++ b/user-service/src/main/java/ru/project/iakov/homework2/service/UserService.java @@ -0,0 +1,85 @@ +package ru.project.iakov.homework2.service; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import ru.project.iakov.homework2.dto.UserDto; +import ru.project.iakov.homework2.dto.UserEvent; +import ru.project.iakov.homework2.entity.User; +import ru.project.iakov.homework2.mapper.UserMapper; +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; + +@Service +@RequiredArgsConstructor +public class UserService { + + private final UserRepository userRepository; + private final UserMapper userMapper; + private final KafkaProducerService kafkaProducer; + private final ObjectMapper objectMapper; + + @Transactional + public UserDto createUser(UserDto userDto) { + User user = userMapper.toEntity(userDto); + User savedUser = userRepository.save(user); + + sendKafkaEvent(savedUser.getEmail(), EventType.CREATED); + + return userMapper.toDto(savedUser); + } + + public UserDto findById(Long id) { + User user = userRepository.findById(id) + .orElseThrow(IllegalArgumentException::new); + + return userMapper.toDto(user); + } + + public List findAll() { + List users = userRepository.findAll(); + + return users.stream() + .map(userMapper::toDto) + .collect(Collectors.toList()); + } + + public UserDto updateUser(Long id, UserDto userDto) { + User existingUser = userRepository.findById(id) + .orElseThrow(IllegalArgumentException::new); + + existingUser.setName(userDto.getName()); + existingUser.setEmail(userDto.getEmail()); + + User updatedUser = userRepository.save(existingUser); + + return userMapper.toDto(updatedUser); + } + + @Transactional + public void deleteUser(Long id) { + User user = userRepository.findById(id) + .orElseThrow(() -> new IllegalArgumentException("Пользователь не найден")); + + userRepository.deleteById(id); + + sendKafkaEvent(user.getEmail(), EventType.DELETED); + } + + private void sendKafkaEvent(String email, EventType eventType) { + try { + UserEvent event = new UserEvent(email, eventType); + String json = objectMapper.writeValueAsString(event); + + kafkaProducer.sendUserEvent(json); + } catch (JsonProcessingException e) { + System.err.println("Ошибка сериализации события Kafka: " + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/user-service/src/main/resources/application.yml b/user-service/src/main/resources/application.yml new file mode 100644 index 0000000..39d62b0 --- /dev/null +++ b/user-service/src/main/resources/application.yml @@ -0,0 +1,37 @@ +spring: + datasource: + url: jdbc:postgresql://localhost:5432/postgres + username: postgres + password: postgres + jpa: + hibernate: + ddl-auto: update + show-sql: true + properties: + hibernate: + format_sql: true + jackson: + serialization: + INDENT_OUTPUT: true + mail: + host: smtp.mail.ru + port: 465 + username: ${EMAIL_USERNAME} + password: ${EMAIL_PASSWORD} + protocol: smtps + properties: + mail.smtp.auth: true + mail.smtp.starttls.enable: true + mail.smtp.ssl.enable: true + kafka: + bootstrap-servers: localhost:9092 + consumer: + group-id: notification-group + auto-offset-reset: earliest + key-deserializer: org.apache.kafka.common.serialization.StringDeserializer + value-deserializer: org.apache.kafka.common.serialization.StringDeserializer + producer: + key-serializer: org.apache.kafka.common.serialization.StringSerializer + value-serializer: org.apache.kafka.common.serialization.StringSerializer +server: + port: 8080 \ No newline at end of file diff --git a/Homework/src/main/resources/hibernate.cfg.xml b/user-service/src/main/resources/hibernate.cfg.xml similarity index 93% rename from Homework/src/main/resources/hibernate.cfg.xml rename to user-service/src/main/resources/hibernate.cfg.xml index a49f2af..1515a41 100644 --- a/Homework/src/main/resources/hibernate.cfg.xml +++ b/user-service/src/main/resources/hibernate.cfg.xml @@ -20,6 +20,6 @@ true - + --> \ No newline at end of file diff --git a/Homework/src/main/resources/log4j.properties b/user-service/src/main/resources/log4j.properties similarity index 100% rename from Homework/src/main/resources/log4j.properties rename to user-service/src/main/resources/log4j.properties 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 new file mode 100644 index 0000000..ae09553 --- /dev/null +++ b/user-service/src/test/java/ru/project/iakov/homework2/controller/EmailControllerTest.java @@ -0,0 +1,39 @@ +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/Homework/src/test/java/ru/project/iakov/homework2/UserControllerTest.java b/user-service/src/test/java/ru/project/iakov/homework2/controller/UserControllerTest.java similarity index 98% rename from Homework/src/test/java/ru/project/iakov/homework2/UserControllerTest.java rename to user-service/src/test/java/ru/project/iakov/homework2/controller/UserControllerTest.java index 08b60bf..4873e5d 100644 --- a/Homework/src/test/java/ru/project/iakov/homework2/UserControllerTest.java +++ b/user-service/src/test/java/ru/project/iakov/homework2/controller/UserControllerTest.java @@ -1,4 +1,4 @@ -package ru.project.iakov.homework2; +package ru.project.iakov.homework2.controller; import com.fasterxml.jackson.databind.ObjectMapper; import org.junit.jupiter.api.DisplayName; @@ -8,6 +8,7 @@ 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; import java.time.LocalDateTime; 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 new file mode 100644 index 0000000..e2854d5 --- /dev/null +++ b/user-service/src/test/java/ru/project/iakov/homework2/kafka/KafkaConsumerTest.java @@ -0,0 +1,50 @@ +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