added main logic smtp client and response bodies
This commit is contained in:
parent
7592367b1e
commit
199549fc7f
2
.env
Normal file
2
.env
Normal file
@ -0,0 +1,2 @@
|
||||
SMTP_API_URL=https://api.smtp.bz/v1
|
||||
SMTP_API_KEY=P0YsjUB137upXrr1NiJefHmXVKW1hmBWlpev
|
2
go.mod
2
go.mod
@ -6,7 +6,9 @@ require github.com/gofiber/fiber/v2 v2.52.5
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/caarlos0/env/v8 v8.0.0 // indirect
|
||||
github.com/google/uuid v1.5.0 // indirect
|
||||
github.com/joho/godotenv v1.5.1 // 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
|
||||
|
4
go.sum
4
go.sum
@ -1,9 +1,13 @@
|
||||
github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
||||
github.com/caarlos0/env/v8 v8.0.0 h1:POhxHhSpuxrLMIdvTGARuZqR4Jjm8AYmoi/JKlcScs0=
|
||||
github.com/caarlos0/env/v8 v8.0.0/go.mod h1:7K4wMY9bH0esiXSSHlfHLX5xKGQMnkH5Fk4TDSSSzfo=
|
||||
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/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
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=
|
||||
|
@ -1,16 +1,12 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.pena/PenaDevops/smtpbiz-exporter/internal/models"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"strings"
|
||||
urlPkg "net/url"
|
||||
)
|
||||
|
||||
type Client interface {
|
||||
UseMethod(method string, endpoint string, params map[string]interface{}) ([]byte, error)
|
||||
}
|
||||
|
||||
type SMTPClient struct {
|
||||
apiURL string
|
||||
client *fiber.Client
|
||||
@ -25,25 +21,16 @@ func NewSMTPClient(apiURL, apiKey string) *SMTPClient {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SMTPClient) UseMethod(method string, endpoint string, params map[string]interface{}) ([]byte, error) {
|
||||
func (c *SMTPClient) UseGetMethod(endpoint models.EndpointsSMTP, params map[string]interface{}) ([]byte, error) {
|
||||
url := fmt.Sprintf("%s/%s", c.apiURL, endpoint)
|
||||
var req *fiber.Agent
|
||||
|
||||
switch strings.ToUpper(method) {
|
||||
case fiber.MethodGet:
|
||||
// todo get params
|
||||
req = c.client.Get(url)
|
||||
case fiber.MethodPost:
|
||||
request, err := json.Marshal(params)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed marshal params: %w", err)
|
||||
if len(params) > 0 {
|
||||
query := urlPkg.Values{}
|
||||
for key, value := range params {
|
||||
query.Add(key, fmt.Sprintf("%v", value))
|
||||
}
|
||||
req = c.client.Post(url)
|
||||
req.Set("Content-Type", "application/json").Body(request)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported HTTP method: %s", method)
|
||||
url = fmt.Sprintf("%s?%s", url, query.Encode())
|
||||
}
|
||||
|
||||
req := c.client.Get(url)
|
||||
req.Set("Authorization", c.apiKey)
|
||||
|
||||
statusCode, respBody, errs := req.Bytes()
|
||||
|
23
internal/initialize/config.go
Normal file
23
internal/initialize/config.go
Normal file
@ -0,0 +1,23 @@
|
||||
package initialize
|
||||
|
||||
import (
|
||||
"github.com/caarlos0/env/v8"
|
||||
"github.com/joho/godotenv"
|
||||
"log"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
SmtpApiUrl string `env:"SMTP_API_URL"`
|
||||
SmtpApiKey string `env:"SMTP_API_KEY"`
|
||||
}
|
||||
|
||||
func LoadConfig() (*Config, error) {
|
||||
if err := godotenv.Load(); err != nil {
|
||||
log.Print("No .env file found")
|
||||
}
|
||||
var config Config
|
||||
if err := env.Parse(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &config, nil
|
||||
}
|
14
internal/models/endpoints.go
Normal file
14
internal/models/endpoints.go
Normal file
@ -0,0 +1,14 @@
|
||||
package models
|
||||
|
||||
type EndpointsSMTP string
|
||||
|
||||
const (
|
||||
UserDataEndpoint EndpointsSMTP = "user" // данные по подьзователю
|
||||
UserStatsEndpoint EndpointsSMTP = "user/stats" // статистика по рассылкам
|
||||
UserDomainsEndpoint EndpointsSMTP = "user/domain" // домены отправителя
|
||||
UserIPsEndpoint EndpointsSMTP = "user/ip" // выделенные ip-адреса отправителя
|
||||
|
||||
LogMsgEndpoint EndpointsSMTP = "log/message" // получение отправленных писем с гет параметрами
|
||||
|
||||
UnsubscribeEndpoint EndpointsSMTP = "unsubscribe" // получить список отписчиков
|
||||
)
|
88
internal/models/smtp.go
Normal file
88
internal/models/smtp.go
Normal file
@ -0,0 +1,88 @@
|
||||
package models
|
||||
|
||||
type UserDataResponse struct {
|
||||
HSent int64 `json:"hsent"` // Количество писем, отправленных за последний час
|
||||
HLimit int64 `json:"hlimit"` // Лимит на количество писем, которые можно отправить за час
|
||||
DSent int64 `json:"dsent"` // Количество писем, отправленных за текущий день
|
||||
DLimit int64 `json:"dlimit"` // Лимит на количество писем, которые можно отправить за день
|
||||
Quota int64 `json:"quota"` // Оставшееся количество писем, которые можно отправить в рамках текущего тарифа
|
||||
Validate int64 `json:"validate"` // Лимит на количество проверок наверное емейл адресов
|
||||
Tariff string `json:"tarif"` // Название текущего тарифа
|
||||
ExpiresQuota string `json:"expires_quota"` // Дата окончания квоты
|
||||
TariffQuota int64 `json:"tarif_quota"` // Общая квота на отправку писем, предоставляемая по тарифу
|
||||
Balance float64 `json:"balance"` // Баланс bucks
|
||||
TariffPrice float64 `json:"tarif_price"` // Тарифный план, в месяц
|
||||
}
|
||||
|
||||
type UserStatsResponse struct {
|
||||
Sent int64 `json:"sent"` // Общее количество отправленных писем
|
||||
Open int64 `json:"open"` // Количество открытых писем
|
||||
Spam int64 `json:"spam"` // Количество писем, попавших в спам
|
||||
Bounce int64 `json:"bounce"` // Количество писем, которые вернулись как недоставленные
|
||||
Unsub int64 `json:"unsub"` // Количество отписавшихся пользователей
|
||||
Tracking TrackingStats `json:"tracking"`
|
||||
}
|
||||
|
||||
type TrackingStats struct {
|
||||
Device DeviceStats `json:"device"` // Статистика по устройствам
|
||||
Activity ActivityStats `json:"activity"` // Активность пользователей в разное время суток
|
||||
Country map[string]int64 `json:"country"` // Статистика по странам (код страны и количество взаимодействий)
|
||||
}
|
||||
|
||||
type DeviceStats struct {
|
||||
Computer int64 `json:"computer"` // Количество взаимодействий с компьютеров
|
||||
Tablet int64 `json:"tablet"` // Количество взаимодействий с планшетов
|
||||
Mobile int64 `json:"mobile"` // Количество взаимодействий с мобильных устройств
|
||||
}
|
||||
|
||||
type ActivityStats struct {
|
||||
Morning int64 `json:"morning"` // Взаимодействия утром
|
||||
Day int64 `json:"day"` // Взаимодействия днем
|
||||
Evening int64 `json:"evening"` // Взаимодействия вечером
|
||||
Night int64 `json:"night"` // Взаимодействия ночью
|
||||
}
|
||||
|
||||
type DomainsResponse []DomainInfo
|
||||
|
||||
type DomainInfo struct {
|
||||
Domain string `json:"domain"` // Имя домена
|
||||
SPF bool `json:"spf"` // SPF запись
|
||||
DKIM bool `json:"dkim"` // DKIM запись
|
||||
CNAME bool `json:"cname"` // CNAME запись
|
||||
IsModeration bool `json:"isModeration"` // Статус модерации
|
||||
IsActive bool `json:"isActive"` // Статус активности домена
|
||||
}
|
||||
|
||||
// один из возможных ответов - {"result":false}
|
||||
// стоит чекать на длину массив
|
||||
type IpsResponse []IPInfo
|
||||
|
||||
type IPInfo struct {
|
||||
Sent int64 `json:"sent"` // Количество отправленных писем
|
||||
Bounce int64 `json:"bounce"` // Количество возвратов
|
||||
IP string `json:"ip"` // IP адрес
|
||||
IsInstall bool `json:"isInstall"` // Установлен ли IP
|
||||
IsActive bool `json:"isActive"` // Активен ли IP
|
||||
ExpiresDate string `json:"expiresDate"` // Дата окончания
|
||||
CreateDate string `json:"createDate"` // Дата создания
|
||||
}
|
||||
|
||||
type LogMsgResponse struct {
|
||||
Data []LogMsgData `json:"data"`
|
||||
TotalPages int64 `json:"totalPages"` // Всего страниц
|
||||
TotalCount int64 `json:"totalCount"` // Всего записей
|
||||
PerPagesCount int64 `json:"perPagesCount"` // Количество записей на странице
|
||||
}
|
||||
|
||||
type LogMsgData struct {
|
||||
MailFrom string `json:"mailfrom"` // От кого отправлено письмо
|
||||
MailTo string `json:"mailto"` // Кому отправлено письмо
|
||||
Status string `json:"status"` // Статус письма
|
||||
IsOpen bool `json:"is_open"` // Было ли письмо открыто
|
||||
Subject string `json:"subject"` // Тема письма
|
||||
IsUnsubscribe bool `json:"is_unsubscribe"` // Является ли письмо отпиской
|
||||
Tag string `json:"tag"` // Тег сообщения
|
||||
Response string `json:"response"` // Ответ от сервера
|
||||
MessageID string `json:"messageid"` // Уникальный идентификатор сообщения
|
||||
Date string `json:"date"` // Дата и время отправки сообщения
|
||||
}
|
0
openapi.yaml
Normal file
0
openapi.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
Суть проблемы, которая будет решаться этим сервисом - периодически так бывает, что заканчивается пакет писем в рассыльщике. Когда это происходит, нам важно быстро узнать об этом и оплатить новый пакет.
|
||||
Дополнительная информация от этого экспортера - было бы неплохо узнавать, сколько писем оказались в спаме, не были отправлены, оказались невалидны или сколько почтовых ящиков оказалось несуществующими
|
98
tests/integration/client_test.go
Normal file
98
tests/integration/client_test.go
Normal file
@ -0,0 +1,98 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.pena/PenaDevops/smtpbiz-exporter/internal/client"
|
||||
"gitea.pena/PenaDevops/smtpbiz-exporter/internal/models"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const apiUrl = "https://api.smtp.bz/v1"
|
||||
const apiKey = "8tv2xcsfCMBX3TCQxzgeeEwAEYyQrPUp0ggw"
|
||||
|
||||
func TestUserData(t *testing.T) {
|
||||
smtp := client.NewSMTPClient(apiUrl, apiKey)
|
||||
|
||||
resp, err := smtp.UseGetMethod(models.UserDataEndpoint, map[string]interface{}{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(string(resp))
|
||||
}
|
||||
|
||||
func TestUserStats(t *testing.T) {
|
||||
smtp := client.NewSMTPClient(apiUrl, apiKey)
|
||||
|
||||
resp, err := smtp.UseGetMethod(models.UserStatsEndpoint, map[string]interface{}{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(string(resp))
|
||||
}
|
||||
|
||||
func TestUserDomains(t *testing.T) {
|
||||
smtp := client.NewSMTPClient(apiUrl, apiKey)
|
||||
|
||||
resp, err := smtp.UseGetMethod(models.UserDomainsEndpoint, map[string]interface{}{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(string(resp))
|
||||
}
|
||||
|
||||
func TestUserIPs(t *testing.T) {
|
||||
smtp := client.NewSMTPClient(apiUrl, apiKey)
|
||||
|
||||
resp, err := smtp.UseGetMethod(models.UserIPsEndpoint, map[string]interface{}{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(string(resp))
|
||||
}
|
||||
|
||||
// todo рейтлимитер нужен я думаю
|
||||
func TestLogMsg(t *testing.T) {
|
||||
smtp := client.NewSMTPClient(apiUrl, apiKey)
|
||||
|
||||
limit := 50
|
||||
offset := 0
|
||||
var maxCount int64
|
||||
|
||||
for {
|
||||
resp, err := smtp.UseGetMethod(models.LogMsgEndpoint, map[string]interface{}{
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var result models.LogMsgResponse
|
||||
err = json.Unmarshal(resp, &result)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(result)
|
||||
|
||||
if maxCount == 0 {
|
||||
maxCount = result.TotalCount
|
||||
}
|
||||
|
||||
if int64(offset+limit) >= maxCount {
|
||||
break
|
||||
}
|
||||
offset += limit
|
||||
fmt.Println(offset)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnsubscribe(t *testing.T) {
|
||||
smtp := client.NewSMTPClient(apiUrl, apiKey)
|
||||
|
||||
resp, err := smtp.UseGetMethod(models.UnsubscribeEndpoint, map[string]interface{}{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fmt.Println(string(resp))
|
||||
}
|
Loading…
Reference in New Issue
Block a user