From 323e309c7e19b9a05dc0e1019d4605c5782075be Mon Sep 17 00:00:00 2001 From: Noloquideus Date: Sat, 18 Apr 2026 12:39:02 +0300 Subject: [PATCH] feat: change templates bitforce to elcsa --- .env-example | 31 ++++ src/infrastructure/mail/assets.py | 11 ++ .../mail/templates/email_code.html | 135 +++++++----------- .../mail/templates/email_code.txt | 9 +- .../mail/templates/static/exa-logo.png | Bin 0 -> 9519 bytes src/presentation/messaging/code.py | 17 ++- 6 files changed, 107 insertions(+), 96 deletions(-) create mode 100644 .env-example create mode 100644 src/infrastructure/mail/assets.py create mode 100644 src/infrastructure/mail/templates/static/exa-logo.png diff --git a/.env-example b/.env-example new file mode 100644 index 0000000..f772cea --- /dev/null +++ b/.env-example @@ -0,0 +1,31 @@ +APP_MODULE=src.main:app +APP_HOST=0.0.0.0 +APP_PORT=8000 +APP_WORKERS=3 + +VAULT_ADDR=http://localhost:8200 +VAULT_ROLE_ID=replace-me +VAULT_SECRET_ID=replace-me +VAULT_AUTH_MOUNT=approle +VAULT_MOUNT_POINT=secrets + +DOCS_USERNAME=admin +DOCS_PASSWORD=admin + +RABBIT_HOST=localhost +RABBIT_PORT=5672 +RABBIT_USER=guest +RABBIT_PASSWORD=guest +RABBIT_VHOST=/ +RABBIT_PUBLISH_PERSIST=true +RABBIT_CONNECT_TIMEOUT=5 +RABBIT_EMAIL_CODE_QUEUE=email.verification_code + +SMTP_FROM= +SMTP_HOST=localhost +SMTP_PORT=587 +SMTP_USER= +SMTP_PASSWORD= + +LOG_LEVEL=INFO +LOG_FORMAT=TEXT diff --git a/src/infrastructure/mail/assets.py b/src/infrastructure/mail/assets.py new file mode 100644 index 0000000..59c8853 --- /dev/null +++ b/src/infrastructure/mail/assets.py @@ -0,0 +1,11 @@ +import base64 +from pathlib import Path + +_LOGO_PATH = Path(__file__).resolve().parent / 'templates' / 'static' / 'exa-logo.png' + + +def get_exa_logo_data_uri() -> str | None: + if not _LOGO_PATH.is_file(): + return None + data = base64.b64encode(_LOGO_PATH.read_bytes()).decode('ascii') + return f'data:image/png;base64,{data}' diff --git a/src/infrastructure/mail/templates/email_code.html b/src/infrastructure/mail/templates/email_code.html index 6747230..e4e48e6 100644 --- a/src/infrastructure/mail/templates/email_code.html +++ b/src/infrastructure/mail/templates/email_code.html @@ -3,52 +3,43 @@ - + {{ subject }} - + -
Ваш код: {{ code }}. Действует {{ ttl_minutes }} мин.  ͏
+ style="background:#F2F1E8;padding:32px 16px;">
- + style="max-width:600px;width:100%;border-radius:12px;overflow:hidden; + box-shadow:0 4px 24px rgba(14,16,61,0.08);border:1px solid rgba(14,16,61,0.08);"> - -
+ - - @@ -56,57 +47,42 @@ - -
-
+
+ {% if logo_data_uri %} + {{ brand }} + {% else %} +
{{ brand }}
-
- Подтверждение · Безопасность -
-
- -
- 🔐 + {% endif %} +
+ Подтверждение действия
+ - -
+
Ваш код подтверждения
-
- Введите этот код в приложении, чтобы завершить действие. - Никому не сообщайте его. +
+ Введите код в приложении {{ brand }}. Не пересылайте это письмо и не сообщайте код другим людям.
- + style="margin-top:22px;"> -
+ -
+
код подтверждения
-
+
{{ code }}
- + style="margin:16px auto 0;"> - @@ -116,17 +92,13 @@
- - ⏱ Действует {{ ttl_minutes }} мин + + + Действует {{ ttl_minutes }} мин
- + style="margin-top:18px;"> - @@ -135,25 +107,15 @@ - - - - - - -
-
- ⚠️ Если вы не запрашивали код — просто проигнорируйте - это письмо. Никогда не сообщайте код третьим лицам. +
+
+ Если вы не запрашивали код, проигнорируйте письмо. Служба поддержки {{ brand }} никогда не попросит код по телефону или в мессенджере.
-
-
-
+ -
- © {{ year }} {{ brand }}
- Ref: {{ trace_id }} +
+ © {{ year }} {{ brand }}
+ Ref: {{ trace_id }}
@@ -161,11 +123,10 @@
-
- \ No newline at end of file + diff --git a/src/infrastructure/mail/templates/email_code.txt b/src/infrastructure/mail/templates/email_code.txt index 46cfdfb..94be5ee 100644 --- a/src/infrastructure/mail/templates/email_code.txt +++ b/src/infrastructure/mail/templates/email_code.txt @@ -1,7 +1,10 @@ {{ brand }} -Ваш код подтверждения: {{ code }} +Код подтверждения: {{ code }} Срок действия: {{ ttl_minutes }} минут -Если вы не запрашивали код — игнорируйте это письмо. +Введите код в приложении {{ brand }}. Не сообщайте код третьим лицам. -Ref: {{ trace_id }} \ No newline at end of file +Если вы не запрашивали код — проигнорируйте это письмо. + +© {{ year }} {{ brand }} +Ref: {{ trace_id }} diff --git a/src/infrastructure/mail/templates/static/exa-logo.png b/src/infrastructure/mail/templates/static/exa-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..f59a52c31ad8855657c8602aa7f4b89ded4fbbbf GIT binary patch literal 9519 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 aeNx5|q|HCsOuv@%|H%KpXMk?A^S=NnVj}GT literal 0 HcmV?d00001 diff --git a/src/presentation/messaging/code.py b/src/presentation/messaging/code.py index 8cef13c..a9c4495 100644 --- a/src/presentation/messaging/code.py +++ b/src/presentation/messaging/code.py @@ -3,6 +3,7 @@ from faststream.rabbit.fastapi import RabbitRouter, RabbitMessage from src.infrastructure.config import settings from src.infrastructure.logger import logger from src.infrastructure.mail import TemplateRenderer, get_email_sender, get_renderer +from src.infrastructure.mail.assets import get_exa_logo_data_uri from src.infrastructure.mail.sender import EmailSender from datetime import datetime from typing import Literal, Optional @@ -52,27 +53,31 @@ async def consume_email_code( f"trace_id={trace_id}" ) + logo_data_uri = get_exa_logo_data_uri() + html = renderer.render( - "email_code.html", - subject="Код подтверждения", + 'email_code.html', + subject='Экса — код подтверждения', code=msg_body.payload.code, ttl_minutes=msg_body.payload.ttl_seconds // 60, - brand="Bitforce", + brand='Экса', + logo_data_uri=logo_data_uri, trace_id=trace_id, year=datetime.now().year, ) text = renderer.render( - "email_code.txt", + 'email_code.txt', code=msg_body.payload.code, ttl_minutes=msg_body.payload.ttl_seconds // 60, - brand="Bitforce", + brand='Экса', trace_id=trace_id, + year=datetime.now().year, ) await sender.send( to=msg_body.payload.email, - subject="Код подтверждения", + subject='Экса — код подтверждения', body=html, plain=text, ) \ No newline at end of file