initial commit

This commit is contained in:
m.zare
2026-04-10 18:25:21 +03:30
commit 77ca6c34a3
263 changed files with 34470 additions and 0 deletions

View File

@@ -0,0 +1,17 @@
package azblob
import (
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/rs/zerolog"
"github.com/Azure/azure-sdk-for-go/sdk/storage/azblob"
)
func New(logger zerolog.Logger, cred *azidentity.DefaultAzureCredential) (*azblob.Client, error) {
client, err := azblob.NewClientFromConnectionString("", nil)
if err != nil {
logger.Error().Err(err).Msg("failed to create azure blob storage client")
return nil, err
}
return client, nil
}

View File

@@ -0,0 +1,27 @@
package azbus
import (
"github.com/ThreeDotsLabs/watermill"
"github.com/ThreeDotsLabs/watermill/message"
"github.com/ThreeDotsLabs/watermill/pubsub/gochannel"
"github.com/rs/zerolog"
"base/config"
"base/pkg/watermill/azsb"
)
func New(cfg *config.AppConfig, logger zerolog.Logger) (message.Subscriber, message.Publisher, error) {
if cfg.Environment == config.Local {
gch := gochannel.NewGoChannel(gochannel.Config{}, watermill.NewStdLogger(true, true))
return gch, gch, nil
}
return azsb.NewAzBus(
azsb.Config{
ConnectionString: cfg.AzureServiceBus.ConnectionString,
UseManagedIdentity: cfg.AzureServiceBus.UseManagedIdentity,
Namespace: cfg.AzureServiceBus.Namespace,
},
logger,
)
}

View File

@@ -0,0 +1,15 @@
package azureidentity
import (
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
"github.com/rs/zerolog"
)
func New(logger zerolog.Logger) (*azidentity.DefaultAzureCredential, error) {
cred, err := azidentity.NewDefaultAzureCredential(nil)
if err != nil {
logger.Error().Err(err).Msg("azure identity error")
return nil, err
}
return cred, nil
}

View File

@@ -0,0 +1,143 @@
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())
}

View File

@@ -0,0 +1,41 @@
package communication
type ApiResponse struct {
ID string `json:"id"`
Status string `json:"status"`
}
type ApiContentDto struct {
Subject string `json:"subject"`
Html string `json:"html"`
PlainText string `json:"plainText"`
}
type ApiRecipientDetailDto struct {
Address string `json:"address"`
DisplayName string `json:"displayName"`
}
type ApiRecipientDto struct {
To []ApiRecipientDetailDto `json:"to"`
CC []ApiRecipientDetailDto `json:"cc"`
BCC []ApiRecipientDetailDto `json:"bcc"`
}
type ApiRequest struct {
SenderAddress string `json:"senderAddress"`
Content ApiContentDto `json:"content"`
Recipients ApiRecipientDto `json:"recipients"`
}
type ApiErrorResponse struct {
Error struct {
AdditionalInfo []struct {
Info any `json:"info"`
Type string `json:"type"`
} `json:"additionalInfo"`
Code string `json:"code"`
Message string `json:"message"`
Target string `json:"target"`
Details any `json:"details"`
} `json:"error"`
}