Files
enkly-ai/services/notification/internal/application/service.go
enkly 79ce8dc4c0
Some checks failed
Go CI Pipeline / build_and_test (push) Failing after 22m0s
Init Project
2026-10-30 18:06:55 +01:00

97 lines
3.4 KiB
Go

// Package application is the notification service's entire job: listen
// for the events iam publishes when it needs an email sent, and send
// it. Replaces the parts of internal/handlers/auth.go that called
// mail.Mailer.Send inline (sendVerificationEmail, sendResetEmail),
// decoupled onto RabbitMQ per the task's requirement that asynchronous,
// no-reply-needed work use the message bus rather than a synchronous
// call — sending an email has no result the caller
// (iam's SignUp/ResendVerification/ForgotPassword RPCs) needs to wait
// for.
package application
import (
"context"
"encoding/json"
"fmt"
"log/slog"
"enkly/pkg/eventbus"
"enkly/pkg/eventbus/events"
"enkly/pkg/mailkit"
)
type Service struct {
Mailer *mailkit.Mailer
Logger *slog.Logger
}
// HandleEvent is the single eventbus.Handler this service registers —
// see cmd/server/main.go's Consume call. It switches on the envelope's
// routing key the same way the monolith's handlers switched on which
// function (sendVerificationEmail vs. sendResetEmail) was calling
// mail.Send.
func (s *Service) HandleEvent(ctx context.Context, env eventbus.Envelope) error {
switch env.RoutingKey {
case eventbus.RoutingKeyVerificationCodeIssued:
var ev events.VerificationCodeIssued
if err := decode(env, &ev); err != nil {
return err
}
return s.sendVerificationEmail(ctx, ev)
case eventbus.RoutingKeyPasswordResetRequested:
var ev events.PasswordResetRequested
if err := decode(env, &ev); err != nil {
return err
}
return s.sendResetEmail(ctx, ev)
default:
// Bound to this queue by mistake (topology drift) — log and ack
// rather than requeue-looping forever on a message we'll never
// know how to handle.
s.Logger.Warn("notification: unhandled routing key", "routing_key", env.RoutingKey)
return nil
}
}
func decode(env eventbus.Envelope, v any) error {
if err := json.Unmarshal(env.Payload, v); err != nil {
return fmt.Errorf("decode %s payload: %w", env.RoutingKey, err)
}
return nil
}
// sendVerificationEmail replaces Handlers.sendVerificationEmail.
func (s *Service) sendVerificationEmail(_ context.Context, ev events.VerificationCodeIssued) error {
subject := "Verify your enkly account"
body := fmt.Sprintf(
"Hi,\n\nYour enkly verification code is: %s\n\nThis code expires in 15 minutes. If you didn't request this, you can ignore this email.\n",
ev.Code)
if !s.Mailer.Configured() {
s.Logger.Info("mail not configured — verification code logged instead", "email", ev.Email, "code", ev.Code)
return nil
}
if err := s.Mailer.Send(ev.Email, subject, body); err != nil {
return fmt.Errorf("send verification email to %s: %w", ev.Email, err)
}
return nil
}
// sendResetEmail replaces Handlers.sendResetEmail.
func (s *Service) sendResetEmail(_ context.Context, ev events.PasswordResetRequested) error {
subject := "Reset your enkly password"
body := fmt.Sprintf(
"Hi,\n\nSomeone (hopefully you) requested a password reset for your enkly account.\n\n"+
"Reset it here: %s\n\nThis link expires in an hour. If you didn't request this, you can ignore this email.\n",
ev.ResetLink)
if !s.Mailer.Configured() {
s.Logger.Info("mail not configured — reset link logged instead", "email", ev.Email, "link", ev.ResetLink)
return nil
}
if err := s.Mailer.Send(ev.Email, subject, body); err != nil {
return fmt.Errorf("send reset email to %s: %w", ev.Email, err)
}
return nil
}