From 6c71bfc0faae856cc2905d9612709bddbce5e80f Mon Sep 17 00:00:00 2001 From: ulitsaRaskolnikova Date: Mon, 9 Feb 2026 15:21:00 +0300 Subject: [PATCH] feat(domain-and-notification): add notifications for domain activating and renewing --- .../common/notification/NotificationType.java | 1 + .../domain/client/NotificationClient.java | 105 ++++++++++++++++++ .../service/impl/UserDomainServiceImpl.java | 24 +++- .../src/main/resources/application-docker.yml | 4 + .../src/main/resources/application.yml | 4 + .../service/EmailTemplateService.java | 1 + .../service/NotificationService.java | 1 + .../templates/emails/domain-activated.html | 30 +++-- .../templates/emails/domain-renewed.html | 30 +++++ 9 files changed, 190 insertions(+), 10 deletions(-) create mode 100644 domain-service/src/main/java/ru/itmo/domain/client/NotificationClient.java create mode 100644 notification-service/src/main/resources/templates/emails/domain-renewed.html diff --git a/common/src/main/java/ru/itmo/common/notification/NotificationType.java b/common/src/main/java/ru/itmo/common/notification/NotificationType.java index 0ace550..983acd7 100644 --- a/common/src/main/java/ru/itmo/common/notification/NotificationType.java +++ b/common/src/main/java/ru/itmo/common/notification/NotificationType.java @@ -6,5 +6,6 @@ public enum NotificationType { DOMAIN_ACTIVATED, DOMAIN_EXPIRING_SOON, DOMAIN_EXPIRED, + DOMAIN_RENEWED, EMAIL_VERIFICATION } diff --git a/domain-service/src/main/java/ru/itmo/domain/client/NotificationClient.java b/domain-service/src/main/java/ru/itmo/domain/client/NotificationClient.java new file mode 100644 index 0000000..0da8cae --- /dev/null +++ b/domain-service/src/main/java/ru/itmo/domain/client/NotificationClient.java @@ -0,0 +1,105 @@ +package ru.itmo.domain.client; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; +import ru.itmo.common.notification.NotificationType; +import ru.itmo.common.notification.SendNotificationRequest; + +import jakarta.servlet.http.HttpServletRequest; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +/** + * Клиент для отправки уведомлений через notification-service. + * Получает JWT-токен из текущего HTTP-запроса и пробрасывает его. + */ +@Slf4j +@Component +public class NotificationClient { + + private final RestTemplate restTemplate; + private final HttpServletRequest httpServletRequest; + private final String notificationServiceUrl; + + public NotificationClient(RestTemplate restTemplate, + HttpServletRequest httpServletRequest, + @Value("${services.notification.url}") String notificationServiceUrl) { + this.restTemplate = restTemplate; + this.httpServletRequest = httpServletRequest; + this.notificationServiceUrl = notificationServiceUrl; + } + + public void sendDomainsActivated(UUID userId, List domains, String expiresAt) { + try { + SendNotificationRequest request = new SendNotificationRequest(); + request.setUserId(userId); + request.setType(NotificationType.DOMAIN_ACTIVATED); + request.setSubject("Активировано доменов: " + domains.size()); + request.setParameters(Map.of( + "domains", domains, + "expiresAt", expiresAt + )); + + String jwtToken = extractJwtToken(); + if (jwtToken == null) { + log.warn("No JWT token found, skipping notification for domains: {}", domains); + return; + } + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(jwtToken); + + HttpEntity entity = new HttpEntity<>(request, headers); + + String url = notificationServiceUrl + "/notifications/send"; + restTemplate.exchange(url, HttpMethod.POST, entity, Void.class); + log.info("Domain activation notification sent for {} domains", domains.size()); + } catch (Exception e) { + log.warn("Failed to send domain activation notification: {}", e.getMessage()); + } + } + + public void sendDomainsRenewed(UUID userId, Map domainsWithExpiry) { + try { + SendNotificationRequest request = new SendNotificationRequest(); + request.setUserId(userId); + request.setType(NotificationType.DOMAIN_RENEWED); + request.setSubject("Продлено доменов: " + domainsWithExpiry.size()); + request.setParameters(Map.of("domains", domainsWithExpiry)); + + String jwtToken = extractJwtToken(); + if (jwtToken == null) { + log.warn("No JWT token found, skipping renewal notification"); + return; + } + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + headers.setBearerAuth(jwtToken); + + HttpEntity entity = new HttpEntity<>(request, headers); + + String url = notificationServiceUrl + "/notifications/send"; + restTemplate.exchange(url, HttpMethod.POST, entity, Void.class); + log.info("Domain renewal notification sent for {} domains", domainsWithExpiry.size()); + } catch (Exception e) { + log.warn("Failed to send domain renewal notification: {}", e.getMessage()); + } + } + + private String extractJwtToken() { + String authHeader = httpServletRequest.getHeader("Authorization"); + if (authHeader != null && authHeader.startsWith("Bearer ")) { + return authHeader.substring(7); + } + return null; + } +} diff --git a/domain-service/src/main/java/ru/itmo/domain/service/impl/UserDomainServiceImpl.java b/domain-service/src/main/java/ru/itmo/domain/service/impl/UserDomainServiceImpl.java index 92747ba..0244205 100644 --- a/domain-service/src/main/java/ru/itmo/domain/service/impl/UserDomainServiceImpl.java +++ b/domain-service/src/main/java/ru/itmo/domain/service/impl/UserDomainServiceImpl.java @@ -3,6 +3,7 @@ package ru.itmo.domain.service.impl; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import ru.itmo.common.audit.AuditClient; +import ru.itmo.domain.client.NotificationClient; import ru.itmo.domain.entity.Domain; import ru.itmo.domain.exception.ForbiddenException; import ru.itmo.domain.exception.L2DomainNotFoundException; @@ -14,6 +15,7 @@ import ru.itmo.domain.service.UserDomainService; import ru.itmo.domain.util.SecurityUtil; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.List; import java.util.UUID; @@ -21,12 +23,16 @@ import java.util.UUID; @Service public class UserDomainServiceImpl implements UserDomainService { + private static final DateTimeFormatter DATE_FMT = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm"); + private final DomainRepository domainRepository; private final AuditClient auditClient; + private final NotificationClient notificationClient; - public UserDomainServiceImpl(DomainRepository domainRepository, AuditClient auditClient) { + public UserDomainServiceImpl(DomainRepository domainRepository, AuditClient auditClient, NotificationClient notificationClient) { this.domainRepository = domainRepository; this.auditClient = auditClient; + this.notificationClient = notificationClient; } @Override @@ -106,6 +112,12 @@ public class UserDomainServiceImpl implements UserDomainService { } auditClient.log("Created " + createdDomains.size() + " domains (period=" + period + "): " + String.join(", ", createdDomains), userId); + + // Отправляем одно email-уведомление со всеми созданными доменами + if (!createdDomains.isEmpty()) { + notificationClient.sendDomainsActivated(userId, createdDomains, finishedAt.format(DATE_FMT)); + } + return createdDomains; } @@ -119,6 +131,7 @@ public class UserDomainServiceImpl implements UserDomainService { DomainPeriod period = request.getPeriod(); List renewedDomains = new ArrayList<>(); + java.util.LinkedHashMap domainsWithExpiry = new java.util.LinkedHashMap<>(); for (String l3Domain : request.getL3Domains()) { String l3Name = l3Domain == null ? null : l3Domain.trim(); @@ -152,13 +165,20 @@ public class UserDomainServiceImpl implements UserDomainService { if (baseDate == null || baseDate.isBefore(LocalDateTime.now())) { baseDate = LocalDateTime.now(); } - l3.setFinishedAt(calculateFinishedAt(baseDate, period)); + LocalDateTime newFinishedAt = calculateFinishedAt(baseDate, period); + l3.setFinishedAt(newFinishedAt); domainRepository.save(l3); renewedDomains.add(l3Name); + domainsWithExpiry.put(l3Name, newFinishedAt.format(DATE_FMT)); } auditClient.log("Renewed " + renewedDomains.size() + " domains (period=" + period + "): " + String.join(", ", renewedDomains), userId); + + if (!domainsWithExpiry.isEmpty()) { + notificationClient.sendDomainsRenewed(userId, domainsWithExpiry); + } + return renewedDomains; } diff --git a/domain-service/src/main/resources/application-docker.yml b/domain-service/src/main/resources/application-docker.yml index 2f19cef..212adac 100644 --- a/domain-service/src/main/resources/application-docker.yml +++ b/domain-service/src/main/resources/application-docker.yml @@ -16,6 +16,10 @@ exdns: base-url: http://exdns:8000 api-token: ${EXDNS_API_TOKEN:-changeme} +services: + notification: + url: http://notification-service:8085 + audit: client: base-url: http://audit-service:8087 diff --git a/domain-service/src/main/resources/application.yml b/domain-service/src/main/resources/application.yml index 52b79d6..38fff51 100644 --- a/domain-service/src/main/resources/application.yml +++ b/domain-service/src/main/resources/application.yml @@ -47,6 +47,10 @@ exdns: jwt: secret: ${JWT_SECRET:your-256-bit-secret-key-must-be-at-least-32-characters-long-for-security} +services: + notification: + url: ${NOTIFICATION_SERVICE_URL:http://localhost:8085} + audit: client: base-url: ${AUDIT_SERVICE_URL:http://localhost:8087} diff --git a/notification-service/src/main/java/ru/itmo/notification/service/EmailTemplateService.java b/notification-service/src/main/java/ru/itmo/notification/service/EmailTemplateService.java index 19ca293..c4aa066 100644 --- a/notification-service/src/main/java/ru/itmo/notification/service/EmailTemplateService.java +++ b/notification-service/src/main/java/ru/itmo/notification/service/EmailTemplateService.java @@ -21,6 +21,7 @@ public class EmailTemplateService { case DOMAIN_ACTIVATED -> "domain-activated"; case DOMAIN_EXPIRING_SOON -> "domain-expiring-soon"; case DOMAIN_EXPIRED -> "domain-expired"; + case DOMAIN_RENEWED -> "domain-renewed"; case EMAIL_VERIFICATION -> "email-verification"; }; } diff --git a/notification-service/src/main/java/ru/itmo/notification/service/NotificationService.java b/notification-service/src/main/java/ru/itmo/notification/service/NotificationService.java index ed3fac7..9a21e6f 100644 --- a/notification-service/src/main/java/ru/itmo/notification/service/NotificationService.java +++ b/notification-service/src/main/java/ru/itmo/notification/service/NotificationService.java @@ -38,6 +38,7 @@ public class NotificationService { case DOMAIN_ACTIVATED -> "Домен активирован"; case DOMAIN_EXPIRING_SOON -> "Напоминание: срок действия домена истекает"; case DOMAIN_EXPIRED -> "Срок действия домена истек"; + case DOMAIN_RENEWED -> "Домены продлены"; case EMAIL_VERIFICATION -> "Подтверждение email адреса"; }; } diff --git a/notification-service/src/main/resources/templates/emails/domain-activated.html b/notification-service/src/main/resources/templates/emails/domain-activated.html index ba82fbf..269c670 100644 --- a/notification-service/src/main/resources/templates/emails/domain-activated.html +++ b/notification-service/src/main/resources/templates/emails/domain-activated.html @@ -2,15 +2,29 @@ - Домен активирован + Домены активированы -

Домен успешно активирован

-

Здравствуйте!

-

Ваш домен был успешно активирован и готов к использованию.

-

Домен:

-

Срок действия до:

-

Теперь вы можете управлять DNS-записями домена в личном кабинете.

-

С уважением,
Команда Domain Registrar

+
+

Домены успешно активированы

+

Здравствуйте!

+

Ваши домены были успешно активированы и готовы к использованию.

+ + + + + + + + + + + + + +
ДоменСрок действия до
+

Теперь вы можете управлять DNS-записями доменов в личном кабинете.

+

С уважением,
Команда Domain Registrar

+
diff --git a/notification-service/src/main/resources/templates/emails/domain-renewed.html b/notification-service/src/main/resources/templates/emails/domain-renewed.html new file mode 100644 index 0000000..b37f2fe --- /dev/null +++ b/notification-service/src/main/resources/templates/emails/domain-renewed.html @@ -0,0 +1,30 @@ + + + + + Домены продлены + + +
+

Домены успешно продлены

+

Здравствуйте!

+

Срок действия ваших доменов был успешно продлён.

+ + + + + + + + + + + + + +
ДоменНовый срок действия до
+

Вы можете управлять DNS-записями доменов в личном кабинете.

+

С уважением,
Команда Domain Registrar

+
+ +