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 (
|
require (
|
||||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
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/google/uuid v1.5.0 // indirect
|
||||||
|
github.com/joho/godotenv v1.5.1 // indirect
|
||||||
github.com/klauspost/compress v1.17.0 // indirect
|
github.com/klauspost/compress v1.17.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // 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 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs=
|
||||||
github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig=
|
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 h1:tWoP1MJQjGEe4GB5TUGOi7P2E0ZMMRx5ZTG4rT+yGMo=
|
||||||
github.com/gofiber/fiber/v2 v2.52.5/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ=
|
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 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
|
||||||
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
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 h1:Rnbp4K9EjcDuVuHtd0dgA4qNuv9yKDYKK1ulpJwgrqM=
|
||||||
github.com/klauspost/compress v1.17.0/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE=
|
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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
@ -1,16 +1,12 @@
|
|||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"gitea.pena/PenaDevops/smtpbiz-exporter/internal/models"
|
||||||
"github.com/gofiber/fiber/v2"
|
"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 {
|
type SMTPClient struct {
|
||||||
apiURL string
|
apiURL string
|
||||||
client *fiber.Client
|
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)
|
url := fmt.Sprintf("%s/%s", c.apiURL, endpoint)
|
||||||
var req *fiber.Agent
|
if len(params) > 0 {
|
||||||
|
query := urlPkg.Values{}
|
||||||
switch strings.ToUpper(method) {
|
for key, value := range params {
|
||||||
case fiber.MethodGet:
|
query.Add(key, fmt.Sprintf("%v", value))
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
req = c.client.Post(url)
|
url = fmt.Sprintf("%s?%s", url, query.Encode())
|
||||||
req.Set("Content-Type", "application/json").Body(request)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("unsupported HTTP method: %s", method)
|
|
||||||
}
|
}
|
||||||
|
req := c.client.Get(url)
|
||||||
req.Set("Authorization", c.apiKey)
|
req.Set("Authorization", c.apiKey)
|
||||||
|
|
||||||
statusCode, respBody, errs := req.Bytes()
|
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