generated from VLADIMIR/template
212 lines
11 KiB
Go
212 lines
11 KiB
Go
package users_service
|
|
|
|
import (
|
|
"fmt"
|
|
"html"
|
|
"strings"
|
|
)
|
|
|
|
// Письма содержат текстовую и HTML-версию; sender собирает multipart/alternative.
|
|
|
|
const (
|
|
// welcomeSubject — тема письма о регистрации: конкретика без КАПСА и
|
|
// кликбейта, до 50 символов (видно на мобильных целиком).
|
|
welcomeSubject = "Твой доступ к «Вечернему детективу»"
|
|
// resetSubject — тема письма о сбросе пароля.
|
|
resetSubject = "Новый пароль для «Вечернего детектива»"
|
|
)
|
|
|
|
// welcomePlain возвращает текстовую версию письма о регистрации.
|
|
func welcomePlain(username, email, password, loginURL string) string {
|
|
var b strings.Builder
|
|
b.WriteString(greeting(username))
|
|
b.WriteString("\n\n")
|
|
b.WriteString("Твоя регистрация в «Вечернем детективе» прошла. Данные для входа:\n\n")
|
|
b.WriteString(" Логин: ")
|
|
b.WriteString(email)
|
|
b.WriteString("\n")
|
|
b.WriteString(" Пароль: ")
|
|
b.WriteString(password)
|
|
b.WriteString("\n\n")
|
|
if loginURL != "" {
|
|
b.WriteString("Войти в игру: ")
|
|
b.WriteString(loginURL)
|
|
b.WriteString("\n\n")
|
|
}
|
|
b.WriteString("Если ты не регистрировался — просто проигнорируй это письмо.\n\n")
|
|
b.WriteString("— Команда «Вечернего детектива»\n")
|
|
b.WriteString("P.S. Сохрани это письмо: пароль понадобится для входа.")
|
|
return sanitizeLine(b.String())
|
|
}
|
|
|
|
// welcomeHTML возвращает HTML-версию письма о регистрации.
|
|
func welcomeHTML(username, email, password, loginURL string) string {
|
|
var content strings.Builder
|
|
content.WriteString("<p class=\"email-text\" style=\"color:#211a14;\">Привет, <strong>")
|
|
content.WriteString(html.EscapeString(username))
|
|
content.WriteString("</strong>!</p>\n")
|
|
content.WriteString("<p class=\"email-text\" style=\"color:#211a14;\">Твоя регистрация в «Вечернем детективе» прошла. Данные для входа:</p>\n")
|
|
content.WriteString(credentialsBlockHTML(email, password))
|
|
if loginURL != "" {
|
|
content.WriteString(buttonHTML(loginURL, "Войти в игру"))
|
|
}
|
|
content.WriteString("<p class=\"email-text\" style=\"color:#211a14; font-size:13px;\">Если ты не регистрировался — просто проигнорируй это письмо.</p>")
|
|
return wrapHTML(content.String(), loginURL)
|
|
}
|
|
|
|
// resetPlain возвращает текстовую версию письма о сбросе пароля.
|
|
func resetPlain(username, email, password, loginURL string) string {
|
|
var b strings.Builder
|
|
b.WriteString(greeting(username))
|
|
b.WriteString("\n\n")
|
|
b.WriteString("Ты запросил сброс пароля в «Вечернем детективе». Новый пароль для входа:\n\n")
|
|
b.WriteString(" Логин: ")
|
|
b.WriteString(email)
|
|
b.WriteString("\n")
|
|
b.WriteString(" Пароль: ")
|
|
b.WriteString(password)
|
|
b.WriteString("\n\n")
|
|
if loginURL != "" {
|
|
b.WriteString("Войти в игру: ")
|
|
b.WriteString(loginURL)
|
|
b.WriteString("\n\n")
|
|
}
|
|
b.WriteString("Никому не сообщай пароль и не пересылай это письмо.\n")
|
|
b.WriteString("Если это был не ты — ответь на это письмо: мы поможем защитить аккаунт.\n\n")
|
|
b.WriteString("— Команда «Вечернего детектива»\n")
|
|
return sanitizeLine(b.String())
|
|
}
|
|
|
|
// resetHTML возвращает HTML-версию письма о сбросе пароля.
|
|
func resetHTML(username, email, password, loginURL string) string {
|
|
var content strings.Builder
|
|
content.WriteString("<p class=\"email-text\" style=\"color:#211a14;\">Привет, <strong>")
|
|
content.WriteString(html.EscapeString(username))
|
|
content.WriteString("</strong>!</p>\n")
|
|
content.WriteString("<p class=\"email-text\" style=\"color:#211a14;\">Ты запросил сброс пароля в «Вечернем детективе». Новый пароль для входа:</p>\n")
|
|
content.WriteString(credentialsBlockHTML(email, password))
|
|
if loginURL != "" {
|
|
content.WriteString(buttonHTML(loginURL, "Войти в игру"))
|
|
}
|
|
content.WriteString("<p class=\"email-text\" style=\"color:#211a14;\">Никому не сообщай пароль и не пересылай это письмо.</p>\n")
|
|
content.WriteString("<p class=\"email-text\" style=\"color:#211a14; font-size:13px;\">Если это был не ты — ответь на это письмо: мы поможем защитить аккаунт.</p>")
|
|
return wrapHTML(content.String(), loginURL)
|
|
}
|
|
|
|
// greeting возвращает приветствие с именем пользователя; при пустом имени —
|
|
// без обращения, чтобы не было «Привет, !».
|
|
func greeting(username string) string {
|
|
username = sanitizeLine(strings.TrimSpace(username))
|
|
if username == "" {
|
|
return "Привет!"
|
|
}
|
|
return fmt.Sprintf("Привет, %s!", username)
|
|
}
|
|
|
|
// credentialsBlockHTML — блок «Логин / Пароль» в табличной вёрстке,
|
|
// совместимой с Gmail.
|
|
func credentialsBlockHTML(email, password string) string {
|
|
return `<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="margin:24px 0; border-top:1px dashed #888888; border-bottom:1px dashed #888888; table-layout:fixed;">
|
|
<tr><td class="email-text" style="padding:16px 0 4px; font-family:Arial,Helvetica,sans-serif; font-size:12px; color:#211a14;">Логин</td></tr>
|
|
<tr><td class="email-text" style="padding:0 0 16px; font-size:16px; color:#211a14; overflow-wrap:anywhere; word-break:break-all;">` + html.EscapeString(email) + `</td></tr>
|
|
<tr><td class="email-text" style="padding:16px 0 4px; border-top:1px dashed #888888; font-family:Arial,Helvetica,sans-serif; font-size:12px; color:#211a14;">Пароль</td></tr>
|
|
<tr><td class="email-text" style="padding:0 0 16px; font-size:20px; color:#211a14; overflow-wrap:anywhere; word-break:break-all;"><strong>` + html.EscapeString(password) + `</strong></td></tr>
|
|
</table>`
|
|
}
|
|
|
|
// buttonHTML — кнопка входа с inline-стилями и табличным фоном.
|
|
func buttonHTML(link, label string) string {
|
|
return `<table role="presentation" cellpadding="0" cellspacing="0" style="margin:20px 0;">
|
|
<tr>
|
|
<td class="email-button" bgcolor="#342a22" style="border-radius:5px; background-color:#342a22;">
|
|
<a href="` + html.EscapeString(link) + `" class="email-button-label" style="display:inline-block; padding:12px 20px; color:#ffffff; font-family:'font_old_typer','Courier New',serif; font-weight:400; text-decoration:none; font-size:18px; line-height:24px; border-radius:5px;">` + html.EscapeString(label) + `</a>
|
|
</td>
|
|
</tr>
|
|
</table>`
|
|
}
|
|
|
|
// wrapHTML — светлая палитра по умолчанию, тёмная через media query.
|
|
// Клиенты без поддержки CSS темы могут перекрашивать обычные цвета сами.
|
|
func wrapHTML(content, baseURL string) string {
|
|
return `<!DOCTYPE html>
|
|
<html lang="ru">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="color-scheme" content="light dark">
|
|
<meta name="supported-color-schemes" content="light dark">
|
|
<style>
|
|
:root { color-scheme:light dark; supported-color-schemes:light dark; }
|
|
@font-face { font-family:'font_old_typer'; src:url('` + emailAssetURL(baseURL, "a_OldTyper.ttf") + `') format('truetype'); }
|
|
@media (prefers-color-scheme:dark) {
|
|
.email-page { background-color:#181818 !important; }
|
|
.email-card { background-color:#262626 !important; }
|
|
.email-text, .email-text a { color:#f2ede6 !important; }
|
|
.email-muted { color:#bdb5aa !important; }
|
|
.email-button { background-color:#dfc6a6 !important; }
|
|
.email-button-label { color:#211a14 !important; }
|
|
}
|
|
[data-ogsb] .email-page, .email-page[data-ogsb] { background-color:#181818 !important; }
|
|
[data-ogsb] .email-card, .email-card[data-ogsb] { background-color:#262626 !important; }
|
|
[data-ogsc] .email-text, .email-text[data-ogsc], [data-ogsc] .email-text a { color:#f2ede6 !important; }
|
|
[data-ogsc] .email-muted, .email-muted[data-ogsc] { color:#bdb5aa !important; }
|
|
[data-ogsb] .email-button, .email-button[data-ogsb] { background-color:#dfc6a6 !important; }
|
|
[data-ogsc] .email-button-label, .email-button-label[data-ogsc] { color:#211a14 !important; }
|
|
</style>
|
|
</head>
|
|
<body class="email-page" style="margin:0; padding:0; background-color:#f3f0eb; font-family:Arial, Helvetica, sans-serif;">
|
|
<table class="email-page" role="presentation" width="100%" cellpadding="0" cellspacing="0" bgcolor="#f3f0eb" style="background-color:#f3f0eb; padding:24px 12px;">
|
|
<tr><td align="center">
|
|
<table role="presentation" width="600" cellpadding="0" cellspacing="0" style="max-width:600px; width:100%;">
|
|
<tr>
|
|
<td class="email-text" style="padding:16px 0 24px; color:#211a14;">
|
|
<span style="font-family:'font_old_typer','Courier New',serif; font-size:22px; line-height:1.4; font-weight:normal;">ВЕЧЕРНИЙ ДЕТЕКТИВ</span>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="email-card" bgcolor="#ffffff" style="padding:24px; background-color:#ffffff; border-radius:8px; font-family:'font_old_typer','Courier New',serif; font-size:18px; line-height:1.5; font-weight:400;">
|
|
` + content + `
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td class="email-muted" style="padding:24px 0; color:#655d54; font-size:12px; line-height:1.6;">
|
|
Это автоматическое письмо сервиса «Вечерний детектив».<br>
|
|
Если ты получил его по ошибке — просто ответь на это письмо.
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td></tr>
|
|
</table>
|
|
</body>
|
|
</html>`
|
|
}
|
|
|
|
// Шрифт находится в public/email фронтенда.
|
|
func emailAssetURL(baseURL, filename string) string {
|
|
if baseURL == "" {
|
|
return ""
|
|
}
|
|
return html.EscapeString(strings.TrimRight(baseURL, "/") + "/email/" + filename)
|
|
}
|
|
|
|
// sanitizeLine убирает переносы строк из пользовательского ввода,
|
|
// вставляемого в тело письма (защита от инъекции строк в DATA-фазу SMTP).
|
|
func sanitizeLine(s string) string {
|
|
return strings.NewReplacer("\r", "", "\n", "").Replace(s)
|
|
}
|
|
|
|
// loginURL нормализует адрес фронтенда для ссылки в письме: срезает
|
|
// завершающий слэш и допускает только http(s). Пустое или некорректное
|
|
// значение — ссылка в письма не добавляется.
|
|
func loginURL(baseURL string) string {
|
|
baseURL = strings.TrimSpace(baseURL)
|
|
baseURL = strings.TrimRight(baseURL, "/")
|
|
if baseURL == "" {
|
|
return ""
|
|
}
|
|
if !strings.HasPrefix(baseURL, "http://") && !strings.HasPrefix(baseURL, "https://") {
|
|
return ""
|
|
}
|
|
return baseURL
|
|
}
|