moved email-tester from gitlab to gitlab tools

This commit is contained in:
Pasha 2024-11-11 10:59:29 +03:00
parent 4a2f581a40
commit e9043bc198
14 changed files with 1376 additions and 0 deletions

1
email-tester/.gitignore vendored Normal file

@ -0,0 +1 @@
.idea/

11
email-tester/README.md Normal file

@ -0,0 +1,11 @@
# email-tester
нужен для тестирования текущей логики отправки формы, все что нужно это запустить в терминале проект, вписать тип события - toClient или reminder, а также что config.yaml находящийся в проекте в той же директории заполенен и все выходящие из него штуки, таки как шаблоны и файл json должны быть заполены
процесс работы:
- установить конфигурационный файл, пример в проекте
- поставить шаблоны, пример в проекте
- при запуске указать тип работы, toClient или reminder
- после этого произойдет логика работы с шаблоном и проведется отправка на почту данных
- далее информация в консоль выведется, если произойдет ошибка в консоли выведется
- в конце нужно прописать exit чтобы выйти из текущего сеанса и консоли, далее запуск снова и т.д.

121
email-tester/client/mail.go Normal file

@ -0,0 +1,121 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"github.com/gofiber/fiber/v2"
"mime/multipart"
"penahub.gitlab.yandexcloud.net/pena-services/email-tester/model"
)
type Deps struct {
SmtpApiUrl string
SmtpSender string
ApiKey string
FiberClient *fiber.Client
}
type SmtpClient struct {
smtpApiUrl string
smtpSender string
apiKey string
fiberClient *fiber.Client
}
func NewSmtpClient(deps Deps) *SmtpClient {
if deps.FiberClient == nil {
deps.FiberClient = fiber.AcquireClient()
}
return &SmtpClient{
smtpApiUrl: deps.SmtpApiUrl,
smtpSender: deps.SmtpSender,
apiKey: deps.ApiKey,
fiberClient: deps.FiberClient,
}
}
type Message struct {
To string
Subject string
HtmlBody string
Attachments []Attachment
}
type Attachment struct {
Name string `json:"name"`
// data в base64 это файл
Data string `json:"body"`
}
type TemplateData struct {
QuizConfig model.ResultInfo
AnswerContent model.ResultContent
AllAnswers []model.ResultAnswer
QuestionsMap map[uint64]string
AnswerTime string
}
func (m *SmtpClient) MailSender(data Message) error {
form := new(bytes.Buffer)
writer := multipart.NewWriter(form)
fields := map[string]string{
"from": m.smtpSender,
"to": data.To,
"subject": data.Subject,
"html": data.HtmlBody,
}
if data.Attachments != nil && len(data.Attachments) > 0 {
attachmentJson, err := json.Marshal(data.Attachments)
if err != nil {
return err
}
fields["files"] = string(attachmentJson)
}
for key, value := range fields {
if err := writer.WriteField(key, value); err != nil {
return err
}
}
if err := writer.Close(); err != nil {
return err
}
req := m.fiberClient.Post(m.smtpApiUrl).Body(form.Bytes()).ContentType(writer.FormDataContentType())
if m.apiKey != "" {
req.Set("Authorization", m.apiKey)
}
statusCode, body, errs := req.Bytes()
if errs != nil {
return errs[0]
}
if statusCode != fiber.StatusOK {
err := fmt.Errorf("the SMTP service returned an error: %d Response body: %s", statusCode, body)
return err
}
return nil
}
func (m *SmtpClient) SendMailWithAttachment(recipient, subject string, emailTemplate string, data TemplateData, attachments []Attachment) error {
sanitizedData := sanitizeHTMLData(data)
text, err := generateTextFromTemplate(sanitizedData, emailTemplate)
if err != nil {
return err
}
msg := Message{
To: recipient,
Subject: subject,
HtmlBody: text,
Attachments: attachments,
}
return m.MailSender(msg)
}

@ -0,0 +1,144 @@
package client
import (
"bytes"
"encoding/json"
"fmt"
"golang.org/x/net/html"
"html/template"
"penahub.gitlab.yandexcloud.net/pena-services/email-tester/model"
"strings"
)
func generateTextFromTemplate(data TemplateData, tpl string) (string, error) {
t, err := template.New("email").Funcs(tmplFuncs).Parse(tpl)
if err != nil {
return "", fmt.Errorf("error parsing template: %w", err)
}
var text bytes.Buffer
if err := t.Execute(&text, TemplateData{
QuizConfig: data.QuizConfig,
AnswerContent: data.AnswerContent,
AllAnswers: data.AllAnswers,
QuestionsMap: data.QuestionsMap,
AnswerTime: data.AnswerTime,
}); err != nil {
return "", fmt.Errorf("error executing template: %w", err)
}
return text.String(), nil
}
var tmplFuncs = template.FuncMap{
"renderImage": RenderImage,
}
func RenderImage(content string) template.HTML {
var res model.ImageContent
err := json.Unmarshal([]byte(content), &res)
if err != nil {
return SplitContent(content)
}
tpl := template.HTML(fmt.Sprintf("<td>%s<br><img class=\"image\" style=\"width:100%%; max-width:250px; max-height:250px\" src=\"%s\"/></td>", res.Description, res.Image))
return tpl
}
func SplitContent(content string) template.HTML {
parts := strings.Split(content, "|")
if len(parts) == 2 {
url := strings.TrimSpace(parts[0])
filename := strings.TrimSpace(parts[1])
return template.HTML(fmt.Sprintf(`<a href="%s" download>%s</a>`, url, filename))
}
return template.HTML(content)
}
func sanitizeHTMLData(data TemplateData) TemplateData {
sanitized := TemplateData{
QuizConfig: stripHTMLResultInfo(data.QuizConfig),
AnswerContent: stripHTMLResultContent(data.AnswerContent),
AllAnswers: stripHTMLResultAnswers(data.AllAnswers),
QuestionsMap: stripHTMLResultMap(data.QuestionsMap),
AnswerTime: StripHTML(data.AnswerTime),
}
return sanitized
}
func stripHTMLResultInfo(input model.ResultInfo) model.ResultInfo {
return model.ResultInfo{
When: StripHTML(input.When),
Theme: StripHTML(input.Theme),
Reply: StripHTML(input.Reply),
ReplName: StripHTML(input.ReplName),
}
}
func stripHTMLResultContent(input model.ResultContent) model.ResultContent {
return model.ResultContent{
Text: StripHTML(input.Text),
Name: StripHTML(input.Name),
Email: StripHTML(input.Email),
Phone: StripHTML(input.Phone),
Address: StripHTML(input.Address),
Telegram: StripHTML(input.Telegram),
Wechat: StripHTML(input.Wechat),
Viber: StripHTML(input.Viber),
Vk: StripHTML(input.Vk),
Skype: StripHTML(input.Skype),
Whatsup: StripHTML(input.Whatsup),
Messenger: StripHTML(input.Messenger),
Custom: stripHTMLCustom(input.Custom),
}
}
func stripHTMLResultAnswers(answers []model.ResultAnswer) []model.ResultAnswer {
sanitized := make([]model.ResultAnswer, len(answers))
for i, j := range answers {
sanitized[i] = model.ResultAnswer{
Content: StripHTML(j.Content),
CreatedAt: j.CreatedAt,
QuestionID: j.QuestionID,
AnswerID: j.AnswerID,
}
}
return sanitized
}
func stripHTMLResultMap(questionsMap map[uint64]string) map[uint64]string {
sanitized := make(map[uint64]string)
for i, j := range questionsMap {
sanitized[i] = StripHTML(j)
}
return sanitized
}
func stripHTMLCustom(custom map[string]string) map[string]string {
sanitized := make(map[string]string)
for i, j := range custom {
sanitized[i] = StripHTML(j)
}
return sanitized
}
func StripHTML(htmlString string) string {
tokenizer := html.NewTokenizer(bytes.NewBufferString(htmlString))
var result bytes.Buffer
for {
tokenType := tokenizer.Next()
switch tokenType {
case html.ErrorToken:
return strings.TrimSpace(result.String())
case html.TextToken:
result.WriteString(tokenizer.Token().Data)
result.WriteString("\n")
case html.StartTagToken, html.EndTagToken:
tagName, _ := tokenizer.TagName()
if string(tagName) == "a" {
_, attrVal, _ := tokenizer.TagAttr()
result.WriteString(string(attrVal))
result.WriteString("\n")
}
}
}
}

10
email-tester/config.yaml Normal file

@ -0,0 +1,10 @@
apiUrl: "https://api.smtp.bz/v1/smtp/send"
apiKey: "P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev"
subject: "test"
sender: "noreply@mailing.pena.digital"
to: "pashamullin2001@gmail.com"
pathToTemplateData: "template/templateData.json"
pathToReminderTemplate: "template/reminder_base.tmpl"
pathToToClientTemplate: "template/to_client_base.tmpl"

23
email-tester/go.mod Normal file

@ -0,0 +1,23 @@
module penahub.gitlab.yandexcloud.net/pena-services/email-tester
go 1.22.4
require (
github.com/gofiber/fiber/v2 v2.52.5
golang.org/x/net v0.17.0
gopkg.in/yaml.v2 v2.4.0
)
require (
github.com/andybalholm/brotli v1.0.5 // indirect
github.com/google/uuid v1.5.0 // indirect
github.com/klauspost/compress v1.17.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/rivo/uniseg v0.2.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
github.com/valyala/fasthttp v1.51.0 // indirect
github.com/valyala/tcplisten v1.0.0 // indirect
golang.org/x/sys v0.15.0 // indirect
)

33
email-tester/go.sum Normal file

@ -0,0 +1,33 @@
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
github.com/gofiber/fiber/v2 v2.52.5 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.17.0 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U=
github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=

81
email-tester/main.go Normal file

@ -0,0 +1,81 @@
package main
import (
"fmt"
"gopkg.in/yaml.v2"
"io/ioutil"
"os"
"penahub.gitlab.yandexcloud.net/pena-services/email-tester/model"
"penahub.gitlab.yandexcloud.net/pena-services/email-tester/senders"
)
type event string
var toClient event = "toClient"
var reminder event = "reminder"
func main() {
fmt.Println("Need event type 'toClient' or 'reminder':")
var eventFlag string
fmt.Scanln(&eventFlag)
defer func() {
fmt.Println("if you want exit write 'exit'")
var exit string
fmt.Scanln(&exit)
if exit == "exit" {
return
}
}()
config, err := loadConfig("config.yaml")
if err != nil {
fmt.Println(err)
return
}
err = config.Validate()
if err != nil {
fmt.Println("failed validate config:", err)
return
}
switch eventFlag {
case string(toClient):
fmt.Println("Processing 'toClient' event")
err = senders.ToClientTesting(config)
if err != nil {
fmt.Println("error process task for toClient test:", err)
return
}
fmt.Println("OK check mailbox")
case string(reminder):
fmt.Println("Processing 'reminder' event")
err = senders.ReminderTesting(config)
if err != nil {
fmt.Println("error process task for reminder test:", err)
return
}
fmt.Println("OK check mailbox")
default:
fmt.Println("No event type or unknown event type specified need 'toClient' or 'reminder'")
}
}
func loadConfig(filename string) (model.Config, error) {
var config model.Config
file, err := os.Open(filename)
if err != nil {
return config, err
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
return config, err
}
err = yaml.Unmarshal(data, &config)
if err != nil {
return config, err
}
return config, nil
}

@ -0,0 +1,81 @@
package model
import (
"errors"
"time"
)
type Config struct {
ApiURL string `yaml:"apiUrl"`
ApiKey string `yaml:"apiKey"`
Subject string `yaml:"subject"`
Sender string `yaml:"sender"`
PathToTemplateData string `yaml:"pathToTemplateData"`
PathToReminderTemplate string `yaml:"pathToReminderTemplate"`
PathToToClientTemplate string `yaml:"pathToToClientTemplate"`
To string `yaml:"to"`
}
func (c *Config) Validate() error {
if c.ApiURL == "" {
return errors.New("ApiURL dont be nil")
}
if c.ApiKey == "" {
return errors.New("ApiKey dont be nil")
}
if c.Subject == "" {
return errors.New("Subject dont be nil")
}
if c.Sender == "" {
return errors.New("Sender dont be nil")
}
if c.PathToTemplateData == "" {
return errors.New("PathToTemplateData dont be nil")
}
if c.PathToReminderTemplate == "" {
return errors.New("PathToReminderTemplate dont be nil")
}
if c.PathToToClientTemplate == "" {
return errors.New("PathToToClientTemplate dont be nil")
}
if c.To == "" {
return errors.New("To dont be nil")
}
return nil
}
type ResultInfo struct {
When string `json:"when"` // before|after|email
Theme string `json:"theme"` // тема письма
Reply string `json:"reply"` // email для ответов, указывается в создании письма
ReplName string `json:"repl_name"` // имя отправителя
}
type ResultContent struct {
Text string `json:"text"`
Name string `json:"name"`
Email string `json:"email"`
Phone string `json:"phone"`
Address string `json:"address"`
Telegram string `json:"telegram"`
Wechat string `json:"wechat"`
Viber string `json:"viber"`
Vk string `json:"vk"`
Skype string `json:"skype"`
Whatsup string `json:"whatsup"`
Messenger string `json:"messenger"`
Custom map[string]string `json:"customs"`
Start bool `json:"start"`
}
type ResultAnswer struct {
Content string
CreatedAt time.Time
QuestionID uint64
AnswerID uint64
}
type ImageContent struct {
Description string
Image string
}

@ -0,0 +1,30 @@
package senders
import (
"penahub.gitlab.yandexcloud.net/pena-services/email-tester/client"
"penahub.gitlab.yandexcloud.net/pena-services/email-tester/model"
)
func ReminderTesting(config model.Config) error {
smtpClient := client.NewSmtpClient(client.Deps{
SmtpApiUrl: config.ApiURL,
SmtpSender: config.Sender,
ApiKey: config.ApiKey,
})
templateData, err := parseTemplateData(config.PathToTemplateData)
if err != nil {
return err
}
body, err := loadTemplate(config.PathToReminderTemplate)
if err != nil {
return err
}
err = smtpClient.SendMailWithAttachment(config.To, config.Subject, body, templateData, nil)
if err != nil {
return err
}
return nil
}

@ -0,0 +1,56 @@
package senders
import (
"encoding/json"
"io/ioutil"
"penahub.gitlab.yandexcloud.net/pena-services/email-tester/client"
"penahub.gitlab.yandexcloud.net/pena-services/email-tester/model"
)
func ToClientTesting(config model.Config) error {
smtpClient := client.NewSmtpClient(client.Deps{
SmtpApiUrl: config.ApiURL,
SmtpSender: config.Sender,
ApiKey: config.ApiKey,
})
templateData, err := parseTemplateData(config.PathToTemplateData)
if err != nil {
return err
}
body, err := loadTemplate(config.PathToToClientTemplate)
if err != nil {
return err
}
err = smtpClient.SendMailWithAttachment(config.To, config.Subject, body, templateData, nil)
if err != nil {
return err
}
return nil
}
func parseTemplateData(filePath string) (client.TemplateData, error) {
var data client.TemplateData
file, err := ioutil.ReadFile(filePath)
if err != nil {
return data, err
}
err = json.Unmarshal(file, &data)
if err != nil {
return data, err
}
return data, nil
}
func loadTemplate(filePath string) (string, error) {
fileContent, err := ioutil.ReadFile(filePath)
if err != nil {
return "", err
}
return string(fileContent), nil
}

@ -0,0 +1,191 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
/* Сброс стилей */
body,
h1,
h2,
h3,
p,
div,
img,
button,
table,
th,
td {
margin: 0;
padding: 0;
border: 0;
font: inherit;
vertical-align: baseline;
}
body {
background-color: #f2f2f7;
font-family: Arial, sans-serif;
}
@media (max-width: 400px) {
h1 {
font-size: 25px !important;
}
h4 {
font-size: 20px !important;
}
.balance {
font-size: 15px !important;
}
.image {
max-width: 223px;
height: 208px;
}
}
</style>
</head>
<body style="background-color: #f2f2f7; font-family: Arial, sans-serif">
<table style="width: 100%; padding: 16px">
<tr>
<td>
<img class="image" style="width: 103px; height: 40px" src="https://storage.yandexcloud.net/squizimages/logo-email-squiz.png" />
</td>
<td>
<p style="text-align: end; color: #9a9aaf; font-size: 14px">Квиз для вашего бизнеса</p>
</td>
</tr>
<tr>
<td colspan="2" style="height: 100%">
<h1
style="
font-size: 30px;
font-weight: 600;
margin-bottom: 13px;
width: 100%;
margin: 0;
margin-bottom: 16px;
margin-top: 50px;
"
>
Поступила новая заявка с квиза “{{ .QuizConfig.Theme }}”!
</h1>
</td>
</tr>
<tr>
<td colspan="2" style="height: 100%">
<p class="balance" style="color: #4d4d4d; font-size: 20px; margin-bottom: 30px">
Но у вас закончились средства на балансе :(
</p>
</td>
</tr>
<tr>
<td colspan="2" style="text-align: center; padding: 0">
<img class="image" style="width: 100%; max-width: 440px; height: 280px; margin-bottom: 40px" src="https://storage.yandexcloud.net/squizimages/img_wallet.png" />
</td>
</tr>
<tr>
<td colspan="2" style="height: 100%">
<h1
style="font-size: 25px; font-weight: 600; margin-bottom: 15px; width: 100%; margin: 0; margin-bottom: 13px"
>
Аккаунт
</h1>
</td>
</tr>
<tr>
<td colspan="2" style="padding: 0">
<table
style="
background-color: #fff;
border-radius: 8px;
text-align: left;
max-width: 480px;
width: 100%;
padding: 16px;
margin-bottom: 40px;
"
>
<tr>
<th
style="
text-align: start;
color: #4d4d4d;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
Email
</th>
<td style="word-break: break-word">
<p
style="
text-align: start;
color: #7e2aea;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
margin-bottom: 15px;
"
>
{{ .QuizConfig.Reply }}
</p>
</td>
</tr>
</table>
</td>
</tr>
<tr>
<td colspan="2" style="height: 100%">
<p class="balance" style="color: #9a9aaf; font-size: 20px; margin-bottom: 30px; text-align: center">
Пополните баланс и посмотрите заявку в личном кабинете:
</p>
</td>
</tr>
<tr>
<td colspan="2" style="height: 100%; text-align: center">
<a
style="
max-width: 312px;
color: #f2f3f7;
text-align: center;
font-size: 18px;
font-style: normal;
font-weight: 400;
line-height: 24px;
border-radius: 8px;
border: 1px solid #7e2aea;
background: #7e2aea;
padding: 10px 43px;
"
>
Посмотреть в личном кабинете
</a>
</td>
</tr>
<tr>
<td colspan="2" style="text-align: center; padding: 30px 0 0 0">
<hr style="border-top: 2px solid rgba(126, 42, 234, 0.2); margin: 0 0 10px" />
<a style="color: #7e2aea; font-size: 20px; font-style: normal; font-weight: 400; line-height: normal">
quiz.pena.digital
</a>
</td>
</tr>
</table>
</body>
</html>

@ -0,0 +1,57 @@
{
"QuizConfig": {
"when": "test@example.com",
"theme": "Taemplste Quiz",
"reply": "test@example.com",
"repl_name": "test@example.com"
},
"AnswerContent": {
"text": "",
"name": "test",
"email": "test@example.com",
"phone": "+723456789",
"address": "",
"telegram": "@test",
"wechat": "test_wechat",
"viber": "+723456789",
"vk": "test_vk",
"skype": "test_skype",
"whatsup": "test_whatsup",
"messenger": "test_messenger",
"customs": {},
"start": false
},
"AllAnswers": [
{
"Content": "https://www.google.com/search?q=ku,n",
"CreatedAt": "2024-07-05T18:54:37Z",
"QuestionID": 1,
"AnswerID": 1
},
{
"Content": "From a friend",
"CreatedAt": "2024-07-05T18:54:37Z",
"QuestionID": 2,
"AnswerID": 2
},
{
"Content": "From a friend",
"CreatedAt": "2024-07-05T18:54:37Z",
"QuestionID": 3,
"AnswerID": 3
},
{
"Content": "{\"Image\": \"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTZvq8kZl7QhYC_7f0jMlepfnLkr8Y94tJY1g&s\", \"Description\": \"Gekon\"}",
"CreatedAt": "2024-07-05T18:54:37Z",
"QuestionID": 4,
"AnswerID": 4
}
],
"QuestionsMap": {
"1": "?",
"2": "How did you hear about us?",
"3": "que 3",
"4": "que 4"
},
"AnswerTime": "2024-07-05T18:54:37Z"
}

@ -0,0 +1,537 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
/* Сброс стилей */
body,
h1,
h2,
h3,
p,
div,
img,
button,
table,
th,
td {
margin: 0;
padding: 0;
border: 0;
font: inherit;
vertical-align: baseline;
}
body {
background-color: #f2f2f7;
font-family: Arial, sans-serif;
}
@media (max-width: 600px) {
h1 {
font-size: 25px !important;
}
h4 {
font-size: 20px !important;
}
}
</style>
</head>
<body style="background-color: #f2f2f7; font-family: Arial, sans-serif">
<table style="width: 100%; padding: 16px">
<tr>
<td>
<img class="image" style="width: 103px; height: 40px" src="https://storage.yandexcloud.net/squizimages/logo-email-squiz.png" />
</td>
<td>
<p style="text-align: end; color: #9a9aaf; font-size: 14px">Квиз для вашего бизнеса</p>
</td>
</tr>
<tr>
<td colspan="2" style="height: 100%">
<h1
style="
font-size: 30px;
font-weight: 600;
margin-bottom: 13px;
width: 100%;
margin: 0;
margin-bottom: 13px;
margin-top: 50px;
"
>
Поступила новая заявка с квиза “{{.QuizConfig.Theme}}”!
</h1>
</td>
</tr>
<tr>
<td colspan="2" style="height: 100%">
<p style="color: #9a9aaf; font-size: 20px; margin-bottom: 50px">
Время заявки: {{ .AnswerTime }}
</p>
</td>
</tr>
<tr>
<td colspan="2" style="height: 100%">
<a
style="
display: flex;
justify-content: center;
color: #f2f3f7;
text-align: center;
font-size: 18px;
font-style: normal;
font-weight: 400;
line-height: 24px;
border-radius: 8px;
border: 1px solid #7e2aea;
background: #7e2aea;
padding: 10px 43px;
max-height: 63px;
margin-bottom: 50px;
"
>
Посмотреть в личном кабинете
</a>
</td>
</tr>
<tr>
<td colspan="2" style="height: 100%">
<h1
style="font-size: 25px; font-weight: 600; margin-bottom: 15px; width: 100%; margin: 0; margin-bottom: 13px"
>
Контакты
</h1>
</td>
</tr>
<tr>
<td colspan="2" style="padding: 0">
<table
style="
background-color: #fff;
border-radius: 8px;
text-align: left;
max-width: 480px;
width: 100%;
padding: 16px;
margin-bottom: 30px;
"
>
<tr>
<th
style="
text-align: start;
color: #9a9aaf;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
Имя
</th>
<td>
<p
style="
text-align: start;
color: #4d4d4d;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
margin-bottom: 15px;
"
>
{{ .AnswerContent.Name}}
</p>
</td>
</tr>
{{ if .AnswerContent.Email }}
<tr>
<th
style="
text-align: start;
color: #9a9aaf;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
Email
</th>
<td style="word-break: break-word">
<p
style="
text-align: start;
color: #7e2aea;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
margin-bottom: 15px;
"
>
{{ .AnswerContent.Email }}
</p>
</td>
</tr>
{{ end }}
{{ if .AnswerContent.Phone }}
<tr>
<th
style="
text-align: start;
color: #9a9aaf;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
Телефон
</th>
<td
style="
text-align: start;
color: #4d4d4d;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
{{ .AnswerContent.Phone }}
</td>
</tr>
{{ end }}
{{ if .AnswerContent.Telegram }}
<tr>
<th
style="
text-align: start;
color: #9a9aaf;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
Telegram
</th>
<td
style="
text-align: start;
color: #4d4d4d;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
{{ .AnswerContent.Telegram }}
</td>
</tr>
{{ end }}
{{ if .AnswerContent.Wechat }}
<tr>
<th
style="
text-align: start;
color: #9a9aaf;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
Wechat
</th>
<td
style="
text-align: start;
color: #4d4d4d;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
{{ .AnswerContent.Wechat }}
</td>
</tr>
{{ end }}
{{ if .AnswerContent.Viber }}
<tr>
<th
style="
text-align: start;
color: #9a9aaf;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
Viber
</th>
<td
style="
text-align: start;
color: #4d4d4d;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
{{ .AnswerContent.Viber }}
</td>
</tr>
{{ end }}
{{ if .AnswerContent.Vk }}
<tr>
<th
style="
text-align: start;
color: #9a9aaf;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
Vk
</th>
<td
style="
text-align: start;
color: #4d4d4d;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
{{ .AnswerContent.Vk }}
</td>
</tr>
{{ end }}
{{ if .AnswerContent.Skype }}
<tr>
<th
style="
text-align: start;
color: #9a9aaf;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
Skype
</th>
<td
style="
text-align: start;
color: #4d4d4d;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
{{ .AnswerContent.Skype }}
</td>
</tr>
{{ end }}
{{ if .AnswerContent.Whatsup }}
<tr>
<th
style="
text-align: start;
color: #9a9aaf;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
Whatsup
</th>
<td
style="
text-align: start;
color: #4d4d4d;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
{{ .AnswerContent.Whatsup }}
</td>
</tr>
{{ end }}
{{ if .AnswerContent.Messenger }}
<tr>
<th
style="
text-align: start;
color: #9a9aaf;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
Messenger
</th>
<td
style="
text-align: start;
color: #4d4d4d;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
{{ .AnswerContent.Messenger }}
</td>
</tr>
{{ end }}
{{ if .AnswerContent.Address }}
<tr>
<th
style="
text-align: start;
color: #9a9aaf;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
Адрес
</th>
<td
style="
text-align: start;
color: #4d4d4d;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
{{ .AnswerContent.Address }}
</td>
</tr>
{{ end }}
{{ range $key, $value := .AnswerContent.Custom }}
<tr>
<th
style="
text-align: start;
color: #9a9aaf;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
{{ $key }}
</th>
<td
style="
text-align: start;
color: #4d4d4d;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
"
>
{{ $value }}
</td>
</tr>
{{ end }}
</table>
</td>
</tr>
<tr>
<td colspan="2" style="height: 100%">
<h1
style="font-size: 25px; font-weight: 600; margin-bottom: 15px; width: 100%; margin: 0; margin-bottom: 13px"
>
Ответы
</h1>
</td>
</tr>
{{ range .AllAnswers }}
{{ if index $.QuestionsMap .AnswerID }}
<tr>
<td colspan="2" style="padding: 0">
<table
style="
background-color: #fff;
border-radius: 8px;
text-align: left;
max-width: 480px;
width: 100%;
padding: 16px;
margin-bottom: 15px;
"
>
<tr>
<th colspan="2">
<p
style="
text-align: start;
color: #4d4d4d;
font-size: 20px;
font-style: normal;
font-weight: 400;
line-height: normal;
margin-bottom: 10px;
"
>
{{ index $.QuestionsMap .AnswerID }}
</p>
</th>
</tr>
<tr>
<td style="color: #9a9aaf; font-size: 20px; font-style: normal; font-weight: 400; line-height: normal">
{{ renderImage .Content }}
</td>
</tr>
</table>
</td>
</tr>
{{ end }}
{{end}}
<tr>
<td colspan="2" style="text-align: center; padding: 0">
<a style="color: #7e2aea; font-size: 20px; font-style: normal; font-weight: 400; line-height: normal">
quiz.pena.digital
</a>
</td>
</tr>
</table>
</body>
</html>