init push

This commit is contained in:
2026-07-16 22:16:45 +07:00
commit 8b068bdb10
1021 changed files with 332816 additions and 0 deletions

View File

@@ -0,0 +1,141 @@
package service
import (
"context"
"encoding/json"
"errors"
"os"
"path/filepath"
"strings"
"time"
"wucher/internal/shared/pkg/uuidv7"
"wucher/internal/queue"
)
type MockEmailSender struct {
dir string
}
type MockEmailRecord struct {
StoredAt time.Time `json:"stored_at"`
Provider string `json:"provider"`
JobID string `json:"job_id"`
MessageType string `json:"message_type"`
To string `json:"to"`
CC []string `json:"cc,omitempty"`
BCC []string `json:"bcc,omitempty"`
ReplyTo []string `json:"reply_to,omitempty"`
Subject string `json:"subject"`
TextBody string `json:"text_body,omitempty"`
HTMLBody string `json:"html_body,omitempty"`
Tags map[string]string `json:"tags,omitempty"`
Metadata map[string]string `json:"metadata,omitempty"`
TraceID string `json:"trace_id,omitempty"`
RequestID string `json:"request_id,omitempty"`
}
func NewMockEmailSender(dir string) *MockEmailSender {
return &MockEmailSender{dir: strings.TrimSpace(dir)}
}
func (s *MockEmailSender) Configured() bool {
return s != nil && strings.TrimSpace(s.dir) != ""
}
func (s *MockEmailSender) Send(ctx context.Context, job queue.EmailJob) (EmailDeliveryResult, error) {
if !s.Configured() {
return EmailDeliveryResult{}, errors.New("mock email sender is not configured")
}
job = job.Canonical()
if err := job.Validate(); err != nil {
return EmailDeliveryResult{}, err
}
select {
case <-ctx.Done():
return EmailDeliveryResult{}, ctx.Err()
default:
}
if err := os.MkdirAll(s.dir, 0o755); err != nil {
return EmailDeliveryResult{}, err
}
jobID := strings.TrimSpace(job.JobID)
if jobID == "" {
var err error
jobID, err = uuidv7.String(uuidv7.MustNew())
if err != nil {
return EmailDeliveryResult{}, err
}
}
filename := time.Now().UTC().Format("20060102T150405.000000000Z") + "_" + sanitizeFileName(job.NormalizedType()) + "_" + sanitizeFileName(jobID) + ".json"
path := filepath.Join(s.dir, filename)
record := MockEmailRecord{
StoredAt: time.Now().UTC(),
Provider: "mock",
JobID: jobID,
MessageType: job.NormalizedType(),
To: job.To,
CC: append([]string(nil), job.CC...),
BCC: append([]string(nil), job.BCC...),
ReplyTo: append([]string(nil), job.ReplyTo...),
Subject: job.Subject,
TextBody: job.TextBodyValue(),
HTMLBody: job.HTMLBody,
Tags: cloneStringMap(job.Tags),
Metadata: cloneStringMap(job.Metadata),
TraceID: job.TraceID,
RequestID: job.RequestID,
}
payload, err := json.MarshalIndent(record, "", " ")
if err != nil {
return EmailDeliveryResult{}, err
}
payload = append(payload, '\n')
if err := os.WriteFile(path, payload, 0o600); err != nil {
return EmailDeliveryResult{}, err
}
return EmailDeliveryResult{
Provider: "mock",
ProviderMessageID: filename,
RequestID: path,
}, nil
}
func sanitizeFileName(value string) string {
value = strings.TrimSpace(value)
if value == "" {
return "email"
}
var b strings.Builder
b.Grow(len(value))
for _, r := range value {
switch {
case r >= 'a' && r <= 'z':
b.WriteRune(r)
case r >= 'A' && r <= 'Z':
b.WriteRune(r)
case r >= '0' && r <= '9':
b.WriteRune(r)
case r == '-', r == '_':
b.WriteRune(r)
default:
b.WriteRune('_')
}
}
return b.String()
}
func cloneStringMap(values map[string]string) map[string]string {
if len(values) == 0 {
return nil
}
out := make(map[string]string, len(values))
for key, value := range values {
out[key] = value
}
return out
}