mirror of
https://github.com/memohai/Memoh.git
synced 2026-04-25 07:00:48 +09:00
148 lines
3.8 KiB
Go
148 lines
3.8 KiB
Go
package email
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
|
|
"github.com/memohai/memoh/internal/db"
|
|
"github.com/memohai/memoh/internal/db/sqlc"
|
|
)
|
|
|
|
// OutboxService manages the email outbox audit log.
|
|
type OutboxService struct {
|
|
queries *sqlc.Queries
|
|
logger *slog.Logger
|
|
}
|
|
|
|
func NewOutboxService(log *slog.Logger, queries *sqlc.Queries) *OutboxService {
|
|
return &OutboxService{
|
|
queries: queries,
|
|
logger: log.With(slog.String("service", "email_outbox")),
|
|
}
|
|
}
|
|
|
|
// Create records a pending outbound email.
|
|
func (s *OutboxService) Create(ctx context.Context, providerID, botID string, msg OutboundEmail, fromAddr string) (string, error) {
|
|
pgProviderID, err := db.ParseUUID(providerID)
|
|
if err != nil {
|
|
return "", fmt.Errorf("invalid provider_id: %w", err)
|
|
}
|
|
pgBotID, err := db.ParseUUID(botID)
|
|
if err != nil {
|
|
return "", fmt.Errorf("invalid bot_id: %w", err)
|
|
}
|
|
toJSON, _ := json.Marshal(msg.To)
|
|
bodyText, bodyHTML := msg.Body, ""
|
|
if msg.HTML {
|
|
bodyHTML = msg.Body
|
|
bodyText = ""
|
|
}
|
|
|
|
row, err := s.queries.CreateEmailOutbox(ctx, sqlc.CreateEmailOutboxParams{
|
|
ProviderID: pgProviderID,
|
|
BotID: pgBotID,
|
|
FromAddress: fromAddr,
|
|
ToAddresses: toJSON,
|
|
Subject: msg.Subject,
|
|
BodyText: bodyText,
|
|
BodyHtml: bodyHTML,
|
|
Attachments: []byte("[]"),
|
|
Status: "pending",
|
|
})
|
|
if err != nil {
|
|
return "", fmt.Errorf("create outbox: %w", err)
|
|
}
|
|
return row.ID.String(), nil
|
|
}
|
|
|
|
// MarkSent updates the outbox record with a successful send.
|
|
func (s *OutboxService) MarkSent(ctx context.Context, id, messageID string) error {
|
|
pgID, err := db.ParseUUID(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.queries.UpdateEmailOutboxSent(ctx, sqlc.UpdateEmailOutboxSentParams{
|
|
ID: pgID,
|
|
MessageID: messageID,
|
|
})
|
|
}
|
|
|
|
// MarkFailed updates the outbox record with an error.
|
|
func (s *OutboxService) MarkFailed(ctx context.Context, id, errMsg string) error {
|
|
pgID, err := db.ParseUUID(id)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.queries.UpdateEmailOutboxFailed(ctx, sqlc.UpdateEmailOutboxFailedParams{
|
|
ID: pgID,
|
|
Error: errMsg,
|
|
})
|
|
}
|
|
|
|
func (s *OutboxService) Get(ctx context.Context, id string) (OutboxItemResponse, error) {
|
|
pgID, err := db.ParseUUID(id)
|
|
if err != nil {
|
|
return OutboxItemResponse{}, err
|
|
}
|
|
row, err := s.queries.GetEmailOutboxByID(ctx, pgID)
|
|
if err != nil {
|
|
return OutboxItemResponse{}, fmt.Errorf("get outbox: %w", err)
|
|
}
|
|
return s.toOutboxResponse(row), nil
|
|
}
|
|
|
|
func (s *OutboxService) ListByBot(ctx context.Context, botID string, limit, offset int32) ([]OutboxItemResponse, int64, error) {
|
|
pgBotID, err := db.ParseUUID(botID)
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
rows, err := s.queries.ListEmailOutboxByBot(ctx, sqlc.ListEmailOutboxByBotParams{
|
|
BotID: pgBotID,
|
|
Lim: limit,
|
|
Off: offset,
|
|
})
|
|
if err != nil {
|
|
return nil, 0, fmt.Errorf("list outbox: %w", err)
|
|
}
|
|
count, err := s.queries.CountEmailOutboxByBot(ctx, pgBotID)
|
|
if err != nil {
|
|
return nil, 0, fmt.Errorf("count outbox: %w", err)
|
|
}
|
|
items := make([]OutboxItemResponse, 0, len(rows))
|
|
for _, row := range rows {
|
|
items = append(items, s.toOutboxResponse(row))
|
|
}
|
|
return items, count, nil
|
|
}
|
|
|
|
func (*OutboxService) toOutboxResponse(row sqlc.EmailOutbox) OutboxItemResponse {
|
|
var to []string
|
|
_ = json.Unmarshal(row.ToAddresses, &to)
|
|
var attachments []any
|
|
_ = json.Unmarshal(row.Attachments, &attachments)
|
|
|
|
var sentAt interface{ IsZero() bool }
|
|
resp := OutboxItemResponse{
|
|
ID: row.ID.String(),
|
|
ProviderID: row.ProviderID.String(),
|
|
BotID: row.BotID.String(),
|
|
MessageID: row.MessageID,
|
|
From: row.FromAddress,
|
|
To: to,
|
|
Subject: row.Subject,
|
|
BodyText: row.BodyText,
|
|
BodyHTML: row.BodyHtml,
|
|
Attachments: attachments,
|
|
Status: row.Status,
|
|
Error: row.Error,
|
|
CreatedAt: row.CreatedAt.Time,
|
|
}
|
|
_ = sentAt
|
|
if row.SentAt.Valid {
|
|
resp.SentAt = row.SentAt.Time
|
|
}
|
|
return resp
|
|
}
|