feat: change templates bitforce to elcsa
This commit is contained in:
31
.env-example
Normal file
31
.env-example
Normal file
@@ -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
|
||||||
11
src/infrastructure/mail/assets.py
Normal file
11
src/infrastructure/mail/assets.py
Normal file
@@ -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}'
|
||||||
@@ -3,52 +3,43 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
<meta name="color-scheme" content="dark" />
|
<meta name="color-scheme" content="light" />
|
||||||
<title>{{ subject }}</title>
|
<title>{{ subject }}</title>
|
||||||
</head>
|
</head>
|
||||||
<body style="margin:0;padding:0;background:#0E1126;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
<body style="margin:0;padding:0;background:#F2F1E8;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%;">
|
||||||
|
|
||||||
<!--[if mso]><style>table,td{font-family:Arial,Helvetica,sans-serif !important;}</style><![endif]-->
|
<!--[if mso]><style>table,td{font-family:Arial,Helvetica,sans-serif !important;}</style><![endif]-->
|
||||||
|
|
||||||
<!-- Preheader -->
|
|
||||||
<div style="display:none;max-height:0;overflow:hidden;mso-hide:all;">
|
<div style="display:none;max-height:0;overflow:hidden;mso-hide:all;">
|
||||||
Ваш код: {{ code }}. Действует {{ ttl_minutes }} мин.  ͏
|
Ваш код: {{ code }}. Действует {{ ttl_minutes }} мин.  ͏
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%"
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%"
|
||||||
style="background:#0E1126;padding:32px 16px;">
|
style="background:#F2F1E8;padding:32px 16px;">
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center">
|
<td align="center">
|
||||||
|
|
||||||
<!-- Outer card 600px -->
|
|
||||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600"
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="600"
|
||||||
style="max-width:600px;width:100%;border-radius:20px;overflow:hidden;
|
style="max-width:600px;width:100%;border-radius:12px;overflow:hidden;
|
||||||
border:1px solid rgba(93,4,217,0.30);">
|
box-shadow:0 4px 24px rgba(14,16,61,0.08);border:1px solid rgba(14,16,61,0.08);">
|
||||||
|
|
||||||
<!-- ====== HEADER — gradient bar ====== -->
|
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:28px 32px;
|
<td style="padding:24px 32px;background:#0E103D;">
|
||||||
background:linear-gradient(135deg,#260E59 0%,#5D04D9 50%,#056CF2 100%);">
|
|
||||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td align="left" valign="middle">
|
||||||
<div style="font-family:'Segoe UI',Arial,Helvetica,sans-serif;
|
{% if logo_data_uri %}
|
||||||
color:#ffffff;font-size:22px;font-weight:800;
|
<img src="{{ logo_data_uri }}" alt="{{ brand }}" width="200" height="auto"
|
||||||
letter-spacing:0.4px;">
|
style="display:block;max-width:200px;height:auto;border:0;outline:none;text-decoration:none;" />
|
||||||
|
{% else %}
|
||||||
|
<div style="font-family:Inter,'Segoe UI',Arial,Helvetica,sans-serif;
|
||||||
|
color:#FFFFFF;font-size:22px;font-weight:800;letter-spacing:0.14em;">
|
||||||
{{ brand }}
|
{{ brand }}
|
||||||
</div>
|
</div>
|
||||||
<div style="font-family:'Segoe UI',Arial,Helvetica,sans-serif;
|
{% endif %}
|
||||||
color:rgba(255,255,255,0.80);font-size:13px;
|
<div style="font-family:Inter,'Segoe UI',Arial,Helvetica,sans-serif;
|
||||||
margin-top:6px;letter-spacing:0.2px;">
|
color:#A1A4C1;font-size:13px;margin-top:10px;">
|
||||||
Подтверждение · Безопасность
|
Подтверждение действия
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td align="right" valign="middle">
|
|
||||||
<!-- Shield icon via CSS -->
|
|
||||||
<div style="width:44px;height:44px;border-radius:12px;
|
|
||||||
background:rgba(255,255,255,0.12);
|
|
||||||
text-align:center;line-height:44px;font-size:22px;">
|
|
||||||
🔐
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -56,57 +47,42 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<!-- ====== BODY ====== -->
|
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:28px 32px;background:#0E1126;">
|
<td style="padding:28px 32px 24px;background:#EFECE0;">
|
||||||
|
|
||||||
<!-- Greeting -->
|
<div style="font-family:Inter,'Segoe UI',Arial,Helvetica,sans-serif;
|
||||||
<div style="font-family:'Segoe UI',Arial,Helvetica,sans-serif;
|
color:#0E103D;font-size:19px;font-weight:700;">
|
||||||
color:#ffffff;font-size:20px;font-weight:700;">
|
|
||||||
Ваш код подтверждения
|
Ваш код подтверждения
|
||||||
</div>
|
</div>
|
||||||
<div style="font-family:'Segoe UI',Arial,Helvetica,sans-serif;
|
<div style="font-family:Inter,'Segoe UI',Arial,Helvetica,sans-serif;
|
||||||
color:rgba(255,255,255,0.70);font-size:14px;
|
color:#0A0B2E;font-size:14px;line-height:22px;margin-top:10px;opacity:0.88;">
|
||||||
line-height:22px;margin-top:10px;">
|
Введите код в приложении {{ brand }}. Не пересылайте это письмо и не сообщайте код другим людям.
|
||||||
Введите этот код в приложении, чтобы завершить действие.
|
|
||||||
Никому не сообщайте его.
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ── Code card ── -->
|
|
||||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%"
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%"
|
||||||
style="margin-top:24px;">
|
style="margin-top:22px;">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="background:linear-gradient(160deg,#260E59 0%,#1a0a3e 100%);
|
<td style="background:#151845;border-radius:10px;padding:22px 18px;text-align:center;">
|
||||||
border:1px solid rgba(93,4,217,0.50);
|
|
||||||
border-radius:16px;padding:24px 20px;text-align:center;">
|
|
||||||
|
|
||||||
<div style="font-family:'Segoe UI',Arial,Helvetica,sans-serif;
|
<div style="font-family:Inter,'Segoe UI',Arial,Helvetica,sans-serif;
|
||||||
color:rgba(255,255,255,0.55);font-size:11px;
|
color:#A1A4C1;font-size:11px;
|
||||||
letter-spacing:2px;text-transform:uppercase;">
|
letter-spacing:0.16em;text-transform:uppercase;">
|
||||||
код подтверждения
|
код подтверждения
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="font-family:'SF Mono','Cascadia Code','Fira Code',monospace;
|
<div style="font-family:'SF Mono','Cascadia Code',Consolas,monospace;
|
||||||
color:#ffffff;font-size:38px;font-weight:800;
|
color:#FFFFFF;font-size:34px;font-weight:800;
|
||||||
letter-spacing:10px;margin-top:12px;
|
letter-spacing:0.32em;margin-top:12px;padding:8px 0;">
|
||||||
padding:12px 0;
|
|
||||||
background:linear-gradient(90deg,#05C7F2,#5D04D9,#056CF2);
|
|
||||||
-webkit-background-clip:text;
|
|
||||||
-webkit-text-fill-color:transparent;
|
|
||||||
background-clip:text;">
|
|
||||||
{{ code }}
|
{{ code }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- TTL pill -->
|
|
||||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0"
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0"
|
||||||
style="margin:14px auto 0;">
|
style="margin:16px auto 0;">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="background:rgba(5,199,242,0.12);
|
<td style="background:#4A47A3;border-radius:8px;padding:10px 22px;">
|
||||||
border:1px solid rgba(5,199,242,0.30);
|
<span style="font-family:Inter,'Segoe UI',Arial,Helvetica,sans-serif;
|
||||||
border-radius:20px;padding:6px 16px;">
|
color:#FFFFFF;font-size:13px;font-weight:600;">
|
||||||
<span style="font-family:'Segoe UI',Arial,Helvetica,sans-serif;
|
Действует {{ ttl_minutes }} мин
|
||||||
color:#05C7F2;font-size:13px;font-weight:600;">
|
|
||||||
⏱ Действует {{ ttl_minutes }} мин
|
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -116,17 +92,13 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<!-- Warning -->
|
|
||||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%"
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%"
|
||||||
style="margin-top:20px;">
|
style="margin-top:18px;">
|
||||||
<tr>
|
<tr>
|
||||||
<td style="background:rgba(93,4,217,0.08);
|
<td style="background:#FDE8E8;border-radius:8px;padding:14px 16px;">
|
||||||
border-left:3px solid #5D04D9;
|
<div style="font-family:Inter,'Segoe UI',Arial,Helvetica,sans-serif;
|
||||||
border-radius:0 10px 10px 0;padding:14px 16px;">
|
color:#991B1B;font-size:13px;line-height:20px;">
|
||||||
<div style="font-family:'Segoe UI',Arial,Helvetica,sans-serif;
|
Если вы не запрашивали код, проигнорируйте письмо. Служба поддержки {{ brand }} никогда не попросит код по телефону или в мессенджере.
|
||||||
color:rgba(255,255,255,0.70);font-size:13px;line-height:20px;">
|
|
||||||
⚠️ Если вы не запрашивали код — просто проигнорируйте
|
|
||||||
это письмо. Никогда не сообщайте код третьим лицам.
|
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -135,25 +107,15 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
<!-- ====== DIVIDER ====== -->
|
|
||||||
<tr>
|
<tr>
|
||||||
<td style="padding:0 32px;">
|
<td style="padding:20px 32px 24px;background:#0E103D;">
|
||||||
<div style="height:1px;
|
|
||||||
background:linear-gradient(90deg,transparent,rgba(5,199,242,0.25),transparent);">
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
|
|
||||||
<!-- ====== FOOTER ====== -->
|
|
||||||
<tr>
|
|
||||||
<td style="padding:20px 32px 28px;background:#0E1126;">
|
|
||||||
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" width="100%">
|
||||||
<tr>
|
<tr>
|
||||||
<td align="right" valign="bottom"
|
<td align="left" valign="top"
|
||||||
style="font-family:'Segoe UI',Arial,Helvetica,sans-serif;
|
style="font-family:Inter,'Segoe UI',Arial,Helvetica,sans-serif;
|
||||||
color:rgba(255,255,255,0.30);font-size:11px;">
|
color:#A1A4C1;font-size:11px;line-height:17px;">
|
||||||
© {{ year }} {{ brand }}<br />
|
© {{ year }} {{ brand }}<br />
|
||||||
Ref: {{ trace_id }}
|
<span style="color:rgba(161,164,193,0.75);">Ref: {{ trace_id }}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
@@ -161,7 +123,6 @@
|
|||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
<!-- /Outer card -->
|
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
{{ brand }}
|
{{ brand }}
|
||||||
Ваш код подтверждения: {{ code }}
|
Код подтверждения: {{ code }}
|
||||||
Срок действия: {{ ttl_minutes }} минут
|
Срок действия: {{ ttl_minutes }} минут
|
||||||
|
|
||||||
Если вы не запрашивали код — игнорируйте это письмо.
|
Введите код в приложении {{ brand }}. Не сообщайте код третьим лицам.
|
||||||
|
|
||||||
|
Если вы не запрашивали код — проигнорируйте это письмо.
|
||||||
|
|
||||||
|
© {{ year }} {{ brand }}
|
||||||
Ref: {{ trace_id }}
|
Ref: {{ trace_id }}
|
||||||
BIN
src/infrastructure/mail/templates/static/exa-logo.png
Normal file
BIN
src/infrastructure/mail/templates/static/exa-logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.3 KiB |
@@ -3,6 +3,7 @@ from faststream.rabbit.fastapi import RabbitRouter, RabbitMessage
|
|||||||
from src.infrastructure.config import settings
|
from src.infrastructure.config import settings
|
||||||
from src.infrastructure.logger import logger
|
from src.infrastructure.logger import logger
|
||||||
from src.infrastructure.mail import TemplateRenderer, get_email_sender, get_renderer
|
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 src.infrastructure.mail.sender import EmailSender
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Literal, Optional
|
from typing import Literal, Optional
|
||||||
@@ -52,27 +53,31 @@ async def consume_email_code(
|
|||||||
f"trace_id={trace_id}"
|
f"trace_id={trace_id}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
logo_data_uri = get_exa_logo_data_uri()
|
||||||
|
|
||||||
html = renderer.render(
|
html = renderer.render(
|
||||||
"email_code.html",
|
'email_code.html',
|
||||||
subject="Код подтверждения",
|
subject='Экса — код подтверждения',
|
||||||
code=msg_body.payload.code,
|
code=msg_body.payload.code,
|
||||||
ttl_minutes=msg_body.payload.ttl_seconds // 60,
|
ttl_minutes=msg_body.payload.ttl_seconds // 60,
|
||||||
brand="Bitforce",
|
brand='Экса',
|
||||||
|
logo_data_uri=logo_data_uri,
|
||||||
trace_id=trace_id,
|
trace_id=trace_id,
|
||||||
year=datetime.now().year,
|
year=datetime.now().year,
|
||||||
)
|
)
|
||||||
|
|
||||||
text = renderer.render(
|
text = renderer.render(
|
||||||
"email_code.txt",
|
'email_code.txt',
|
||||||
code=msg_body.payload.code,
|
code=msg_body.payload.code,
|
||||||
ttl_minutes=msg_body.payload.ttl_seconds // 60,
|
ttl_minutes=msg_body.payload.ttl_seconds // 60,
|
||||||
brand="Bitforce",
|
brand='Экса',
|
||||||
trace_id=trace_id,
|
trace_id=trace_id,
|
||||||
|
year=datetime.now().year,
|
||||||
)
|
)
|
||||||
|
|
||||||
await sender.send(
|
await sender.send(
|
||||||
to=msg_body.payload.email,
|
to=msg_body.payload.email,
|
||||||
subject="Код подтверждения",
|
subject='Экса — код подтверждения',
|
||||||
body=html,
|
body=html,
|
||||||
plain=text,
|
plain=text,
|
||||||
)
|
)
|
||||||
Reference in New Issue
Block a user