Files
base/internal/pkg/azure/communication/azcommunication.go
2026-04-10 18:25:21 +03:30

144 lines
3.7 KiB
Go

package communication
import (
"bytes"
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"html/template"
"net/http"
"net/url"
"time"
"github.com/rs/zerolog"
"base/config"
"base/pkg/email"
)
type client struct {
logger zerolog.Logger
endpoint string
accessKey string
apiVersion string
senderAddress string
templates *template.Template
}
func New(logger zerolog.Logger, config *config.AppConfig) email.Email {
return &client{
logger: logger,
endpoint: config.AzureCommunicationConfig.Endpoint,
accessKey: config.AzureCommunicationConfig.AccessKey,
apiVersion: config.AzureCommunicationConfig.ApiVersion,
senderAddress: config.AzureCommunicationConfig.SenderAddress,
}
}
func (c client) Send(ctx context.Context, params email.Request) (*email.Response, error) {
var tpl bytes.Buffer
if err := c.templates.ExecuteTemplate(&tpl, generateTemplateName(params.Template.EmailTemplateName), params.Template.Data); err != nil {
return nil, err
}
html := tpl.String()
request := &ApiRequest{
SenderAddress: c.senderAddress,
Content: ApiContentDto{
Subject: params.Subject,
Html: html,
},
Recipients: ApiRecipientDto{
To: []ApiRecipientDetailDto{
{
Address: params.RecipientAddress,
DisplayName: params.UserFullName,
},
},
CC: make([]ApiRecipientDetailDto, 0),
BCC: make([]ApiRecipientDetailDto, 0),
},
}
byteBody, err := json.Marshal(&request)
if err != nil {
return nil, errors.New("marshaling error")
}
method := "POST"
endpoint := c.endpoint
u, _ := url.Parse(endpoint)
snedPathAndQuery := fmt.Sprintf(
"/emails:send?api-version=%s",
c.apiVersion,
)
date := time.Now().In(time.FixedZone("GMT", 0)).Format("Mon, 02 Jan 2006 15:04:05 GMT")
host := u.Host
contentHash := computeContentHash(byteBody)
stringToSign := fmt.Sprintf("%s\n%s\n%s;%s;%s", method, snedPathAndQuery, date, host, contentHash)
signature, err := computeSignature(stringToSign, c.accessKey)
if err != nil {
return nil, err
}
authHeader := fmt.Sprintf("HMAC-SHA256 SignedHeaders=x-ms-date;host;x-ms-content-sha256&Signature=%s", signature)
fullURL := endpoint + snedPathAndQuery
req, _ := http.NewRequest(method, fullURL, bytes.NewReader(byteBody))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("x-ms-date", date)
req.Header.Set("x-ms-content-sha256", contentHash)
req.Header.Set("Authorization", authHeader)
req.Header.Set("Host", host)
client := &http.Client{Timeout: 15 * time.Second}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusAccepted {
response := &ApiErrorResponse{}
if err := json.NewDecoder(resp.Body).Decode(response); err != nil {
return nil, err
}
c.logger.Info().Msgf("email sending failed. %v", response)
return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode)
}
c.logger.Info().Msgf("email sending done. %v", resp.Body)
response := &email.Response{}
if err := json.NewDecoder(resp.Body).Decode(response); err != nil {
return nil, err
}
return response, nil
}
func computeContentHash(body []byte) string {
sum := sha256.Sum256(body)
return base64.StdEncoding.EncodeToString(sum[:])
}
func computeSignature(stringToSign, base64AccessKey string) (string, error) {
key, err := base64.StdEncoding.DecodeString(base64AccessKey)
if err != nil {
return "", err
}
mac := hmac.New(sha256.New, key)
_, err = mac.Write([]byte(stringToSign))
if err != nil {
return "", err
}
sig := mac.Sum(nil)
return base64.StdEncoding.EncodeToString(sig), nil
}
func generateTemplateName(emailTemplateName email.Template) string {
return fmt.Sprintf("%s.html", emailTemplateName.String())
}