feat(backend): implement notification system (logger mock)
This commit is contained in:
parent
b63242b8fd
commit
6de471ce3e
4 changed files with 108 additions and 7 deletions
37
backend/internal/notifications/service.go
Normal file
37
backend/internal/notifications/service.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package notifications
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
|
||||
"github.com/saveinmed/backend-go/internal/domain"
|
||||
)
|
||||
|
||||
// NotificationService defines the contract for sending alerts.
|
||||
type NotificationService interface {
|
||||
NotifyOrderCreated(ctx context.Context, order *domain.Order, buyer, seller *domain.User) error
|
||||
NotifyOrderStatusChanged(ctx context.Context, order *domain.Order, buyer *domain.User) error
|
||||
}
|
||||
|
||||
// LoggerNotificationService prints notifications to stdout for dev/MVP.
|
||||
type LoggerNotificationService struct{}
|
||||
|
||||
func NewLoggerNotificationService() *LoggerNotificationService {
|
||||
return &LoggerNotificationService{}
|
||||
}
|
||||
|
||||
func (s *LoggerNotificationService) NotifyOrderCreated(ctx context.Context, order *domain.Order, buyer, seller *domain.User) error {
|
||||
log.Printf("📧 [EMAIL] To: Seller <%s>\n Subject: Novo Pedido #%s recebido!\n Body: Olá %s, você recebeu um novo pedido de %s. Total: R$ %.2f",
|
||||
seller.Email, order.ID, seller.Name, buyer.Name, float64(order.TotalCents)/100)
|
||||
|
||||
log.Printf("📧 [EMAIL] To: Buyer <%s>\n Subject: Pedido #%s confirmado!\n Body: Olá %s, seu pedido foi recebido e está aguardando processamento.",
|
||||
buyer.Email, order.ID, buyer.Name)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *LoggerNotificationService) NotifyOrderStatusChanged(ctx context.Context, order *domain.Order, buyer *domain.User) error {
|
||||
log.Printf("📧 [EMAIL] To: Buyer <%s>\n Subject: Atualização do Pedido #%s\n Body: Olá %s, seu pedido mudou para status: %s.",
|
||||
buyer.Email, order.ID, buyer.Name, order.Status)
|
||||
return nil
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/saveinmed/backend-go/internal/domain"
|
||||
"github.com/saveinmed/backend-go/internal/http/handler"
|
||||
"github.com/saveinmed/backend-go/internal/http/middleware"
|
||||
"github.com/saveinmed/backend-go/internal/notifications"
|
||||
"github.com/saveinmed/backend-go/internal/payments"
|
||||
"github.com/saveinmed/backend-go/internal/repository/postgres"
|
||||
"github.com/saveinmed/backend-go/internal/usecase"
|
||||
|
|
@ -34,9 +35,11 @@ func New(cfg config.Config) (*Server, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
repo := postgres.New(db)
|
||||
gateway := payments.NewMercadoPagoGateway(cfg.MercadoPagoBaseURL, cfg.MarketplaceCommission)
|
||||
svc := usecase.NewService(repo, gateway, cfg.MarketplaceCommission, cfg.JWTSecret, cfg.JWTExpiresIn, cfg.PasswordPepper)
|
||||
repoInstance := postgres.New(db)
|
||||
paymentGateway := payments.NewMercadoPagoGateway(cfg.MercadoPagoBaseURL, cfg.MarketplaceCommission)
|
||||
// Services
|
||||
notifySvc := notifications.NewLoggerNotificationService()
|
||||
svc := usecase.NewService(repoInstance, paymentGateway, notifySvc, cfg.MarketplaceCommission, cfg.JWTSecret, cfg.JWTExpiresIn, cfg.PasswordPepper)
|
||||
h := handler.New(svc, cfg.BuyerFeeRate)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import (
|
|||
"github.com/gofrs/uuid/v5"
|
||||
|
||||
"github.com/saveinmed/backend-go/internal/domain"
|
||||
"github.com/saveinmed/backend-go/internal/notifications"
|
||||
)
|
||||
|
||||
// Repository defines DB contract for the core entities.
|
||||
|
|
@ -72,6 +73,7 @@ type PaymentGateway interface {
|
|||
type Service struct {
|
||||
repo Repository
|
||||
pay PaymentGateway
|
||||
notify notifications.NotificationService
|
||||
jwtSecret []byte
|
||||
tokenTTL time.Duration
|
||||
marketplaceCommission float64
|
||||
|
|
@ -83,10 +85,11 @@ const (
|
|||
)
|
||||
|
||||
// NewService wires use cases together.
|
||||
func NewService(repo Repository, pay PaymentGateway, commissionPct float64, jwtSecret string, tokenTTL time.Duration, passwordPepper string) *Service {
|
||||
func NewService(repo Repository, pay PaymentGateway, notify notifications.NotificationService, commissionPct float64, jwtSecret string, tokenTTL time.Duration, passwordPepper string) *Service {
|
||||
return &Service{
|
||||
repo: repo,
|
||||
pay: pay,
|
||||
notify: notify,
|
||||
jwtSecret: []byte(jwtSecret),
|
||||
tokenTTL: tokenTTL,
|
||||
marketplaceCommission: commissionPct,
|
||||
|
|
@ -313,7 +316,33 @@ func (s *Service) AdjustInventory(ctx context.Context, productID uuid.UUID, delt
|
|||
func (s *Service) CreateOrder(ctx context.Context, order *domain.Order) error {
|
||||
order.ID = uuid.Must(uuid.NewV7())
|
||||
order.Status = domain.OrderStatusPending
|
||||
return s.repo.CreateOrder(ctx, order)
|
||||
|
||||
if err := s.repo.CreateOrder(ctx, order); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Async notification (ignore errors to not block request)
|
||||
go func() {
|
||||
// Fetch buyer details
|
||||
buyer, err := s.repo.GetUser(context.Background(), order.BuyerID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// Fetch seller details (hack: get list and pick first for MVP)
|
||||
users, _, _ := s.repo.ListUsers(context.Background(), domain.UserFilter{CompanyID: &order.SellerID, Limit: 1})
|
||||
var seller *domain.User
|
||||
if len(users) > 0 {
|
||||
seller = &users[0]
|
||||
} else {
|
||||
// Fallback mock
|
||||
seller = &domain.User{Name: "Seller", Email: "seller@platform.com"}
|
||||
}
|
||||
|
||||
s.notify.NotifyOrderCreated(context.Background(), order, buyer, seller)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) ListOrders(ctx context.Context, filter domain.OrderFilter, page, pageSize int) (*domain.OrderPage, error) {
|
||||
|
|
@ -368,7 +397,28 @@ func (s *Service) UpdateOrderStatus(ctx context.Context, id uuid.UUID, status do
|
|||
return errors.New("order is in terminal state")
|
||||
}
|
||||
|
||||
return s.repo.UpdateOrderStatus(ctx, id, status)
|
||||
if err := s.repo.UpdateOrderStatus(ctx, id, status); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Async notification
|
||||
go func() {
|
||||
// Re-fetch order to get all details if necessary, but we have fields.
|
||||
// Need buyer to send email
|
||||
updatedOrder, _ := s.repo.GetOrder(context.Background(), id)
|
||||
if updatedOrder == nil {
|
||||
return
|
||||
}
|
||||
|
||||
buyer, err := s.repo.GetUser(context.Background(), updatedOrder.BuyerID)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
s.notify.NotifyOrderStatusChanged(context.Background(), updatedOrder, buyer)
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Service) DeleteOrder(ctx context.Context, id uuid.UUID) error {
|
||||
|
|
|
|||
|
|
@ -329,11 +329,22 @@ func (m *MockPaymentGateway) CreatePreference(ctx context.Context, order *domain
|
|||
}, nil
|
||||
}
|
||||
|
||||
// MockNotificationService for testing
|
||||
type MockNotificationService struct{}
|
||||
|
||||
func (m *MockNotificationService) NotifyOrderCreated(ctx context.Context, order *domain.Order, buyer, seller *domain.User) error {
|
||||
return nil
|
||||
}
|
||||
func (m *MockNotificationService) NotifyOrderStatusChanged(ctx context.Context, order *domain.Order, buyer *domain.User) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper to create a test service
|
||||
func newTestService() (*Service, *MockRepository) {
|
||||
repo := NewMockRepository()
|
||||
gateway := &MockPaymentGateway{}
|
||||
svc := NewService(repo, gateway, 2.5, "test-secret", time.Hour, "test-pepper")
|
||||
notify := &MockNotificationService{}
|
||||
svc := NewService(repo, gateway, notify, 2.5, "test-secret", time.Hour, "test-pepper")
|
||||
return svc, repo
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue