Integrate Notification Service into Tender Management System
- Added a new notification service to handle various notification types (email, SMS, push, OTP). - Implemented configuration for the notification service, allowing customization via environment variables. - Updated user and customer services to utilize the notification service for sending welcome emails and status updates. - Introduced a builder pattern for constructing notification requests, enhancing usability and flexibility. - Enhanced error handling and logging for notification operations, ensuring robust service integration. - Updated documentation to include usage examples and configuration details for the new notification SDK.
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
|||||||
"tm/pkg/config"
|
"tm/pkg/config"
|
||||||
"tm/pkg/logger"
|
"tm/pkg/logger"
|
||||||
"tm/pkg/mongo"
|
"tm/pkg/mongo"
|
||||||
|
"tm/pkg/notification"
|
||||||
"tm/pkg/redis"
|
"tm/pkg/redis"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
@@ -175,3 +176,34 @@ func InitAuthorizationService(conf JWTConfig, redisClient redis.Client, log logg
|
|||||||
|
|
||||||
return authService
|
return authService
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Init Notification Service
|
||||||
|
func InitNotificationService(conf config.NotificationConfig, log logger.Logger) notification.SDK {
|
||||||
|
notificationConfig := ¬ification.Config{
|
||||||
|
BaseURL: conf.BaseURL,
|
||||||
|
Timeout: conf.Timeout,
|
||||||
|
RetryAttempts: conf.RetryAttempts,
|
||||||
|
RetryDelay: conf.RetryDelay,
|
||||||
|
EnableLogging: conf.EnableLogging,
|
||||||
|
UserAgent: conf.UserAgent,
|
||||||
|
}
|
||||||
|
|
||||||
|
notificationSDK, err := notification.New(notificationConfig, log)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to initialize Notification SDK", map[string]interface{}{
|
||||||
|
"error": err.Error(),
|
||||||
|
})
|
||||||
|
fmt.Printf("Failed to initialize Notification SDK: %v\n", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Info("Notification SDK initialized successfully", map[string]interface{}{
|
||||||
|
"base_url": conf.BaseURL,
|
||||||
|
"timeout": conf.Timeout,
|
||||||
|
"retry_attempts": conf.RetryAttempts,
|
||||||
|
"retry_delay": conf.RetryDelay,
|
||||||
|
"enable_logging": conf.EnableLogging,
|
||||||
|
"user_agent": conf.UserAgent,
|
||||||
|
})
|
||||||
|
|
||||||
|
return *notificationSDK
|
||||||
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ type Config struct {
|
|||||||
Logging config.LoggingConfig
|
Logging config.LoggingConfig
|
||||||
RateLimit config.RateLimitConfig
|
RateLimit config.RateLimitConfig
|
||||||
Assets config.AssetsConfig
|
Assets config.AssetsConfig
|
||||||
|
Notification config.NotificationConfig
|
||||||
UserAuth AuthConfig
|
UserAuth AuthConfig
|
||||||
CustomerAuth AuthConfig
|
CustomerAuth AuthConfig
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -122,6 +122,9 @@ func main() {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
// Initialize notification service
|
||||||
|
notificationSDK := bootstrap.InitNotificationService(conf.Notification, logger)
|
||||||
|
|
||||||
// Initialize authorization service
|
// Initialize authorization service
|
||||||
userAuthService := bootstrap.InitAuthorizationService(conf.UserAuth.JWT, redisClient, logger)
|
userAuthService := bootstrap.InitAuthorizationService(conf.UserAuth.JWT, redisClient, logger)
|
||||||
customerAuthService := bootstrap.InitAuthorizationService(conf.CustomerAuth.JWT, redisClient, logger)
|
customerAuthService := bootstrap.InitAuthorizationService(conf.CustomerAuth.JWT, redisClient, logger)
|
||||||
@@ -143,10 +146,10 @@ func main() {
|
|||||||
userValidator := user.NewValidationService()
|
userValidator := user.NewValidationService()
|
||||||
|
|
||||||
// Initialize services with repositories
|
// Initialize services with repositories
|
||||||
userService := user.NewUserService(userRepository, logger, userAuthService, userValidator)
|
userService := user.NewUserService(userRepository, logger, userAuthService, userValidator, notificationSDK)
|
||||||
categoryService := company_category.NewCategoryService(categoryRepository, logger)
|
categoryService := company_category.NewCategoryService(categoryRepository, logger)
|
||||||
companyService := company.NewCompanyService(companyRepository, categoryService, logger)
|
companyService := company.NewCompanyService(companyRepository, categoryService, logger)
|
||||||
customerService := customer.New(customerRepository, logger, customerAuthService, companyService, redisClient)
|
customerService := customer.New(customerRepository, logger, customerAuthService, companyService, redisClient, notificationSDK)
|
||||||
tenderService := tender.NewTenderService(tenderRepository, companyService, logger)
|
tenderService := tender.NewTenderService(tenderRepository, companyService, logger)
|
||||||
feedbackService := feedback.NewFeedbackService(feedbackRepo, tenderService, logger)
|
feedbackService := feedback.NewFeedbackService(feedbackRepo, tenderService, logger)
|
||||||
tenderApprovalService := tender_approval.NewTenderApprovalService(tenderApprovalRepository, tenderService, logger)
|
tenderApprovalService := tender_approval.NewTenderApprovalService(tenderApprovalRepository, tenderService, logger)
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"tm/internal/company"
|
"tm/internal/company"
|
||||||
"tm/pkg/logger"
|
"tm/pkg/logger"
|
||||||
|
"tm/pkg/notification"
|
||||||
"tm/pkg/redis"
|
"tm/pkg/redis"
|
||||||
"tm/pkg/response"
|
"tm/pkg/response"
|
||||||
|
|
||||||
@@ -49,16 +51,18 @@ type customerService struct {
|
|||||||
authService authorization.AuthorizationService
|
authService authorization.AuthorizationService
|
||||||
companyService company.Service
|
companyService company.Service
|
||||||
redisClient redis.Client
|
redisClient redis.Client
|
||||||
|
notification notification.SDK
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new customer service
|
// New creates a new customer service
|
||||||
func New(repository Repository, logger logger.Logger, authService authorization.AuthorizationService, companyService company.Service, redisClient redis.Client) Service {
|
func New(repository Repository, logger logger.Logger, authService authorization.AuthorizationService, companyService company.Service, redisClient redis.Client, notificationSDK notification.SDK) Service {
|
||||||
return &customerService{
|
return &customerService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
authService: authService,
|
authService: authService,
|
||||||
companyService: companyService,
|
companyService: companyService,
|
||||||
redisClient: redisClient,
|
redisClient: redisClient,
|
||||||
|
notification: notificationSDK,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,6 +129,8 @@ func (s *customerService) Register(ctx context.Context, form *CreateCustomerForm
|
|||||||
"company_ids": companyIDs,
|
"company_ids": companyIDs,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
go s.notification.SendEmail(ctx, customer.Email, fmt.Sprintf("Welcome to Tender Management\nUsername: %s\nPassword: %s", customer.Username, form.Password), customer.ID.Hex())
|
||||||
|
|
||||||
return customer.ToResponse(nil), nil
|
return customer.ToResponse(nil), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -293,7 +299,7 @@ func (s *customerService) Delete(ctx context.Context, id string) error {
|
|||||||
// UpdateStatus updates customer status
|
// UpdateStatus updates customer status
|
||||||
func (s *customerService) UpdateStatus(ctx context.Context, id string, form *UpdateStatusForm) error {
|
func (s *customerService) UpdateStatus(ctx context.Context, id string, form *UpdateStatusForm) error {
|
||||||
// Get customer to check if exists
|
// Get customer to check if exists
|
||||||
_, err := s.repository.GetByID(ctx, id)
|
customer, err := s.repository.GetByID(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("customer not found")
|
return errors.New("customer not found")
|
||||||
}
|
}
|
||||||
@@ -315,6 +321,8 @@ func (s *customerService) UpdateStatus(ctx context.Context, id string, form *Upd
|
|||||||
"status": form.Status,
|
"status": form.Status,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
go s.notification.SendEmail(ctx, customer.Email, fmt.Sprintf("Your account has been %s", strings.ToUpper(form.Status)), customer.ID.Hex())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -769,14 +777,7 @@ func (s *customerService) RequestResetPassword(ctx context.Context, form *Reques
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Send OTP via email
|
// Send OTP via email
|
||||||
// err = s.emailService.SendPasswordResetOTP(ctx, form.Email, otpCode)
|
go s.notification.SendEmail(ctx, form.Email, fmt.Sprintf("Your password reset code is: %s", otpCode), customer.ID.Hex())
|
||||||
// if err != nil {
|
|
||||||
// s.logger.Error("Failed to send password reset OTP", map[string]interface{}{
|
|
||||||
// "error": err.Error(),
|
|
||||||
// "email": form.Email,
|
|
||||||
// })
|
|
||||||
// return nil, errors.New("failed to send verification code")
|
|
||||||
// }
|
|
||||||
|
|
||||||
s.logger.Info("Password reset OTP sent successfully", map[string]interface{}{
|
s.logger.Info("Password reset OTP sent successfully", map[string]interface{}{
|
||||||
"email": form.Email,
|
"email": form.Email,
|
||||||
@@ -964,6 +965,8 @@ func (s *customerService) ResetPassword(ctx context.Context, form *ResetPassword
|
|||||||
"customer_id": customer.ID,
|
"customer_id": customer.ID,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
go s.notification.SendEmail(ctx, tokenData.Email, "Your password has been reset successfully", customer.ID.Hex())
|
||||||
|
|
||||||
return &ResetPasswordResponse{
|
return &ResetPasswordResponse{
|
||||||
Message: "Password reset successfully",
|
Message: "Password reset successfully",
|
||||||
Success: true,
|
Success: true,
|
||||||
|
|||||||
@@ -3,10 +3,12 @@ package user
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"tm/pkg/authorization"
|
"tm/pkg/authorization"
|
||||||
"tm/pkg/logger"
|
"tm/pkg/logger"
|
||||||
|
"tm/pkg/notification"
|
||||||
"tm/pkg/response"
|
"tm/pkg/response"
|
||||||
|
|
||||||
"go.mongodb.org/mongo-driver/v2/bson"
|
"go.mongodb.org/mongo-driver/v2/bson"
|
||||||
@@ -38,15 +40,17 @@ type userService struct {
|
|||||||
logger logger.Logger
|
logger logger.Logger
|
||||||
authService authorization.AuthorizationService
|
authService authorization.AuthorizationService
|
||||||
validator ValidationService
|
validator ValidationService
|
||||||
|
notification notification.SDK
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewUserService creates a new user service
|
// NewUserService creates a new user service
|
||||||
func NewUserService(repository Repository, logger logger.Logger, authService authorization.AuthorizationService, validator ValidationService) Service {
|
func NewUserService(repository Repository, logger logger.Logger, authService authorization.AuthorizationService, validator ValidationService, notificationSDK notification.SDK) Service {
|
||||||
return &userService{
|
return &userService{
|
||||||
repository: repository,
|
repository: repository,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
authService: authService,
|
authService: authService,
|
||||||
validator: validator,
|
validator: validator,
|
||||||
|
notification: notificationSDK,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,6 +116,8 @@ func (s *userService) Register(ctx context.Context, form *CreateUserForm) (*User
|
|||||||
"role": user.Role,
|
"role": user.Role,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
go s.notification.SendEmail(ctx, form.Email, fmt.Sprintf("Welcome to Tender Management\nUsername: %s\nPassword: %s", form.Username, form.Password), user.ID.Hex())
|
||||||
|
|
||||||
return user.ToResponse(), nil
|
return user.ToResponse(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -214,6 +220,8 @@ func (s *userService) UpdateStatus(ctx context.Context, id string, form *UpdateU
|
|||||||
"status": form.Status,
|
"status": form.Status,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
go s.notification.SendEmail(ctx, user.Email, fmt.Sprintf("Your account has been %s", strings.ToUpper(form.Status)), user.ID.Hex())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,6 +450,8 @@ func (s *userService) ChangePassword(ctx context.Context, userID string, form *C
|
|||||||
"user_id": userID,
|
"user_id": userID,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
go s.notification.SendEmail(ctx, user.Email, "Your password has been reset successfully", user.ID.Hex())
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -65,6 +65,15 @@ type RateLimitConfig struct {
|
|||||||
Burst int `env:"RATE_LIMIT_BURST"`
|
Burst int `env:"RATE_LIMIT_BURST"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type NotificationConfig struct {
|
||||||
|
BaseURL string `env:"NOTIFICATION_BASE_URL"`
|
||||||
|
Timeout time.Duration `env:"NOTIFICATION_TIMEOUT"`
|
||||||
|
RetryAttempts int `env:"NOTIFICATION_RETRY_ATTEMPTS"`
|
||||||
|
RetryDelay time.Duration `env:"NOTIFICATION_RETRY_DELAY"`
|
||||||
|
EnableLogging bool `env:"NOTIFICATION_ENABLE_LOGGING"`
|
||||||
|
UserAgent string `env:"NOTIFICATION_USER_AGENT"`
|
||||||
|
}
|
||||||
|
|
||||||
// LoadConfig loads configuration with priority: OS environment variables > .env file
|
// LoadConfig loads configuration with priority: OS environment variables > .env file
|
||||||
// OS environment variables take precedence over .env file values
|
// OS environment variables take precedence over .env file values
|
||||||
func LoadConfig[T any](path string, config T) (T, error) {
|
func LoadConfig[T any](path string, config T) (T, error) {
|
||||||
|
|||||||
@@ -0,0 +1,400 @@
|
|||||||
|
# Notification SDK
|
||||||
|
|
||||||
|
A comprehensive Go SDK for interacting with the Tender Management notification service. This SDK provides a clean, type-safe interface for sending various types of notifications including emails, SMS, push notifications, and OTP messages.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- **Multiple Notification Types**: Support for EMAIL, SMS, PUSH, and OTP notifications
|
||||||
|
- **Automatic Retries**: Configurable retry mechanism with exponential backoff
|
||||||
|
- **Structured Logging**: Built-in logging with structured fields
|
||||||
|
- **Fluent API**: Builder pattern for constructing complex notifications
|
||||||
|
- **Batch Operations**: Send multiple notifications efficiently
|
||||||
|
- **Type Safety**: Full type safety with comprehensive validation
|
||||||
|
- **Error Handling**: Detailed error types and proper error wrapping
|
||||||
|
- **Configuration**: Flexible configuration via environment variables or code
|
||||||
|
- **Health Checks**: Built-in health checking capabilities
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
go get your-module/pkg/notification
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### Basic Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log"
|
||||||
|
"your-module/pkg/notification"
|
||||||
|
"your-module/pkg/logger" // Your logger implementation
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Create configuration
|
||||||
|
config := notification.DefaultConfig()
|
||||||
|
config.BaseURL = "http://127.0.0.1:9095" // Your notification service URL
|
||||||
|
|
||||||
|
// Create logger (implement notification.Logger interface)
|
||||||
|
logger := logger.New() // Your logger implementation
|
||||||
|
|
||||||
|
// Create SDK
|
||||||
|
sdk, err := notification.NewSDK(config, logger)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to create notification SDK:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Send a simple email
|
||||||
|
err = sdk.QuickEmail(ctx, "user@example.com", "Hello World!")
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to send email: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Configuration
|
||||||
|
|
||||||
|
Set environment variables for automatic configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export NOTIFICATION_BASE_URL="http://127.0.0.1:9095"
|
||||||
|
export NOTIFICATION_TIMEOUT="30s"
|
||||||
|
export NOTIFICATION_RETRY_ATTEMPTS="3"
|
||||||
|
export NOTIFICATION_RETRY_DELAY="2s"
|
||||||
|
export NOTIFICATION_ENABLE_LOGGING="true"
|
||||||
|
export NOTIFICATION_USER_AGENT="MyApp-NotificationSDK/1.0"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then load configuration:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "your-module/pkg/config"
|
||||||
|
|
||||||
|
// Load configuration from environment
|
||||||
|
cfg, err := config.LoadConfig(".", ¬ification.Config{})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Failed to load config:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
sdk, err := notification.NewSDK(cfg, logger)
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Config struct {
|
||||||
|
BaseURL string `env:"NOTIFICATION_BASE_URL"`
|
||||||
|
Timeout time.Duration `env:"NOTIFICATION_TIMEOUT"`
|
||||||
|
RetryAttempts int `env:"NOTIFICATION_RETRY_ATTEMPTS"`
|
||||||
|
RetryDelay time.Duration `env:"NOTIFICATION_RETRY_DELAY"`
|
||||||
|
EnableLogging bool `env:"NOTIFICATION_ENABLE_LOGGING"`
|
||||||
|
UserAgent string `env:"NOTIFICATION_USER_AGENT"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Creating SDK Instance
|
||||||
|
|
||||||
|
```go
|
||||||
|
// With default configuration
|
||||||
|
sdk, err := notification.NewSDK(nil, logger)
|
||||||
|
|
||||||
|
// With custom configuration
|
||||||
|
config := ¬ification.Config{
|
||||||
|
BaseURL: "http://localhost:9095",
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
RetryAttempts: 3,
|
||||||
|
RetryDelay: 2 * time.Second,
|
||||||
|
EnableLogging: true,
|
||||||
|
UserAgent: "MyApp/1.0",
|
||||||
|
}
|
||||||
|
sdk, err := notification.NewSDK(config, logger)
|
||||||
|
|
||||||
|
// With custom HTTP client
|
||||||
|
httpClient := &http.Client{Timeout: 10 * time.Second}
|
||||||
|
sdk, err := notification.NewSDKWithClient(config, httpClient, logger)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sending Notifications
|
||||||
|
|
||||||
|
#### Simple Notifications
|
||||||
|
|
||||||
|
```go
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Email
|
||||||
|
response, err := sdk.SendEmail(ctx, "user@example.com", "Welcome!", "user123")
|
||||||
|
|
||||||
|
// SMS
|
||||||
|
response, err := sdk.SendSMS(ctx, "+1234567890", "Your code: 1234", "user123")
|
||||||
|
|
||||||
|
// Push Notification
|
||||||
|
response, err := sdk.SendPush(ctx, "device_token_here", "New message!", "user123")
|
||||||
|
|
||||||
|
// OTP
|
||||||
|
response, err := sdk.SendOTP(ctx, "+1234567890", "Your OTP: 5678", "user123")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Quick Notifications (without user ID)
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Quick methods for simple use cases
|
||||||
|
err := sdk.QuickEmail(ctx, "user@example.com", "Hello!")
|
||||||
|
err := sdk.QuickSMS(ctx, "+1234567890", "Hello!")
|
||||||
|
err := sdk.QuickPush(ctx, "device_token", "Hello!")
|
||||||
|
err := sdk.QuickOTP(ctx, "+1234567890", "Code: 1234")
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Custom Notifications
|
||||||
|
|
||||||
|
```go
|
||||||
|
request := ¬ification.NotificationRequest{
|
||||||
|
EventType: notification.EventTypeEmail,
|
||||||
|
Message: "Custom notification message",
|
||||||
|
Methods: notification.NotificationMethods{
|
||||||
|
Email: "user@example.com",
|
||||||
|
SMS: "+1234567890", // Optional: multiple delivery methods
|
||||||
|
},
|
||||||
|
UserID: "user123",
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := sdk.SendNotification(ctx, request)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fluent Builder API
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Build and send notification using fluent API
|
||||||
|
response, err := sdk.NewBuilder().
|
||||||
|
SetEventType(notification.EventTypeEmail).
|
||||||
|
SetMessage("Welcome to our service!").
|
||||||
|
SetEmail("user@example.com").
|
||||||
|
SetUserID("user123").
|
||||||
|
Send(ctx)
|
||||||
|
|
||||||
|
// Build without sending
|
||||||
|
request, err := sdk.NewBuilder().
|
||||||
|
SetEventType(notification.EventTypeSMS).
|
||||||
|
SetMessage("Your verification code: 1234").
|
||||||
|
SetSMS("+1234567890").
|
||||||
|
SetUserID("user123").
|
||||||
|
Build()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
response, err := sdk.SendNotification(ctx, request)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch Operations
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Prepare multiple requests
|
||||||
|
requests := []*notification.NotificationRequest{
|
||||||
|
{
|
||||||
|
EventType: notification.EventTypeEmail,
|
||||||
|
Message: "Welcome email",
|
||||||
|
Methods: notification.NotificationMethods{Email: "user1@example.com"},
|
||||||
|
UserID: "user1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
EventType: notification.EventTypeSMS,
|
||||||
|
Message: "Welcome SMS",
|
||||||
|
Methods: notification.NotificationMethods{SMS: "+1234567890"},
|
||||||
|
UserID: "user2",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send batch
|
||||||
|
batchResponse, err := sdk.SendBatch(ctx, requests)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Batch failed: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Printf("Batch completed: %d success, %d failed",
|
||||||
|
batchResponse.Success, batchResponse.Failed)
|
||||||
|
|
||||||
|
// Check individual results
|
||||||
|
for _, resp := range batchResponse.Responses {
|
||||||
|
if !resp.Success {
|
||||||
|
log.Printf("Request %d failed: %s", resp.Index, resp.Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Health Checks
|
||||||
|
|
||||||
|
```go
|
||||||
|
// Check if notification service is available
|
||||||
|
err := sdk.Health(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Notification service is unhealthy: %v", err)
|
||||||
|
} else {
|
||||||
|
log.Println("Notification service is healthy")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Event Types
|
||||||
|
|
||||||
|
The SDK supports the following notification types:
|
||||||
|
|
||||||
|
- `notification.EventTypeEmail` - Email notifications
|
||||||
|
- `notification.EventTypeSMS` - SMS notifications
|
||||||
|
- `notification.EventTypePush` - Push notifications
|
||||||
|
- `notification.EventTypeOTP` - OTP notifications
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
The SDK provides detailed error types for different scenarios:
|
||||||
|
|
||||||
|
```go
|
||||||
|
response, err := sdk.SendEmail(ctx, "invalid-email", "Hello!", "user123")
|
||||||
|
if err != nil {
|
||||||
|
switch e := err.(type) {
|
||||||
|
case notification.ErrHTTP:
|
||||||
|
log.Printf("HTTP error %d: %s", e.StatusCode, e.Message)
|
||||||
|
case notification.ErrValidation:
|
||||||
|
log.Printf("Validation error in field %s: %s", e.Field, e.Message)
|
||||||
|
case notification.ErrRetryExhausted:
|
||||||
|
log.Printf("Retry exhausted after %d attempts: %v", e.Attempts, e.LastError)
|
||||||
|
case notification.ErrInvalidConfig:
|
||||||
|
log.Printf("Configuration error in field %s: %s", e.Field, e.Message)
|
||||||
|
default:
|
||||||
|
log.Printf("Unknown error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Logging
|
||||||
|
|
||||||
|
The SDK requires a logger that implements the `notification.Logger` interface:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Logger interface {
|
||||||
|
Info(msg string, fields map[string]interface{})
|
||||||
|
Error(msg string, fields map[string]interface{})
|
||||||
|
Debug(msg string, fields map[string]interface{})
|
||||||
|
Warn(msg string, fields map[string]interface{})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Example logger implementation with logrus:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
|
type LogrusLogger struct {
|
||||||
|
logger *logrus.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LogrusLogger) Info(msg string, fields map[string]interface{}) {
|
||||||
|
l.logger.WithFields(fields).Info(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LogrusLogger) Error(msg string, fields map[string]interface{}) {
|
||||||
|
l.logger.WithFields(fields).Error(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LogrusLogger) Debug(msg string, fields map[string]interface{}) {
|
||||||
|
l.logger.WithFields(fields).Debug(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *LogrusLogger) Warn(msg string, fields map[string]interface{}) {
|
||||||
|
l.logger.WithFields(fields).Warn(msg)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
The SDK is designed to be easily testable. You can mock the HTTP client:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"net/http"
|
||||||
|
"your-module/pkg/notification"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Mock HTTP client
|
||||||
|
type MockHTTPClient struct {
|
||||||
|
DoFunc func(req *http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockHTTPClient) Do(req *http.Request) (*http.Response, error) {
|
||||||
|
return m.DoFunc(req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock logger
|
||||||
|
type MockLogger struct{}
|
||||||
|
func (l *MockLogger) Info(msg string, fields map[string]interface{}) {}
|
||||||
|
func (l *MockLogger) Error(msg string, fields map[string]interface{}) {}
|
||||||
|
func (l *MockLogger) Debug(msg string, fields map[string]interface{}) {}
|
||||||
|
func (l *MockLogger) Warn(msg string, fields map[string]interface{}) {}
|
||||||
|
|
||||||
|
func TestSendEmail(t *testing.T) {
|
||||||
|
mockClient := &MockHTTPClient{
|
||||||
|
DoFunc: func(req *http.Request) (*http.Response, error) {
|
||||||
|
// Return mock response
|
||||||
|
return &http.Response{
|
||||||
|
StatusCode: 200,
|
||||||
|
Body: io.NopCloser(strings.NewReader(`{"status":"Notification sent"}`)),
|
||||||
|
}, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
config := notification.DefaultConfig()
|
||||||
|
logger := &MockLogger{}
|
||||||
|
|
||||||
|
sdk, err := notification.NewSDKWithClient(config, mockClient, logger)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := sdk.SendEmail(context.Background(), "test@example.com", "Test", "user123")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.Status != "Notification sent" {
|
||||||
|
t.Errorf("Expected 'Notification sent', got %s", response.Status)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Configuration**: Use environment variables for configuration in production
|
||||||
|
2. **Context**: Always pass context for timeout and cancellation support
|
||||||
|
3. **Error Handling**: Check specific error types for appropriate handling
|
||||||
|
4. **Logging**: Implement structured logging for better observability
|
||||||
|
5. **Retries**: Configure appropriate retry settings based on your use case
|
||||||
|
6. **Validation**: Let the SDK handle validation, but provide clean input data
|
||||||
|
7. **Batch Operations**: Use batch operations for sending multiple notifications efficiently
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
See the `examples/` directory for complete working examples:
|
||||||
|
|
||||||
|
- `basic_usage.go` - Basic notification sending
|
||||||
|
- `batch_operations.go` - Batch notification processing
|
||||||
|
- `fluent_api.go` - Using the builder pattern
|
||||||
|
- `error_handling.go` - Comprehensive error handling
|
||||||
|
- `configuration.go` - Different configuration methods
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create a feature branch
|
||||||
|
3. Add tests for new functionality
|
||||||
|
4. Ensure all tests pass
|
||||||
|
5. Submit a pull request
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This SDK is part of the Tender Management system and follows the same license terms.
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
# Notification SDK - Quick Usage Guide
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
The SDK is already part of your `tm` module. Import it as:
|
||||||
|
```go
|
||||||
|
import "tm/pkg/notification"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Basic Setup
|
||||||
|
```go
|
||||||
|
// 1. Configuration
|
||||||
|
config := notification.DefaultConfig()
|
||||||
|
config.BaseURL = "http://127.0.0.1:9095" // Your notification service URL
|
||||||
|
|
||||||
|
// 2. Logger (using project's logger)
|
||||||
|
loggerConfig := &logger.Config{Level: "info", Format: "json", Output: "stdout"}
|
||||||
|
projectLogger := logger.NewLogger(loggerConfig)
|
||||||
|
loggerAdapter := ¬ification.LoggerAdapter{Logger: projectLogger}
|
||||||
|
|
||||||
|
// 3. Create SDK
|
||||||
|
sdk, err := notification.NewSDK(config, loggerAdapter)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
```bash
|
||||||
|
export NOTIFICATION_BASE_URL="http://127.0.0.1:9095"
|
||||||
|
export NOTIFICATION_TIMEOUT="30s"
|
||||||
|
export NOTIFICATION_RETRY_ATTEMPTS="3"
|
||||||
|
export NOTIFICATION_RETRY_DELAY="2s"
|
||||||
|
export NOTIFICATION_ENABLE_LOGGING="true"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Examples
|
||||||
|
|
||||||
|
### Simple Email
|
||||||
|
```go
|
||||||
|
err := sdk.QuickEmail(ctx, "user@example.com", "Hello!")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Custom Notification
|
||||||
|
```go
|
||||||
|
request := ¬ification.NotificationRequest{
|
||||||
|
EventType: notification.EventTypeEmail,
|
||||||
|
Message: "Hello Nima",
|
||||||
|
Methods: notification.NotificationMethods{
|
||||||
|
OTP: "09143257416",
|
||||||
|
SMS: "09143257416",
|
||||||
|
Push: "devicefsdgsdgsg6766sdgdsg",
|
||||||
|
Email: "nakhostin.nima1998@gmail.com",
|
||||||
|
},
|
||||||
|
UserID: "",
|
||||||
|
}
|
||||||
|
response, err := sdk.SendNotification(ctx, request)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fluent Builder
|
||||||
|
```go
|
||||||
|
response, err := sdk.NewBuilder().
|
||||||
|
SetEventType(notification.EventTypeEmail).
|
||||||
|
SetMessage("Hello Nima").
|
||||||
|
SetEmail("nakhostin.nima1998@gmail.com").
|
||||||
|
Send(ctx)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Batch Operations
|
||||||
|
```go
|
||||||
|
requests := []*notification.NotificationRequest{...}
|
||||||
|
batchResponse, err := sdk.SendBatch(ctx, requests)
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Response
|
||||||
|
- **Success**: `{"status": "Notification sent"}`
|
||||||
|
- **Error**: `{"error": "error message"}`
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
```go
|
||||||
|
if err != nil {
|
||||||
|
switch e := err.(type) {
|
||||||
|
case notification.ErrHTTP:
|
||||||
|
// HTTP error (400, 500, etc.)
|
||||||
|
case notification.ErrValidation:
|
||||||
|
// Validation error
|
||||||
|
case notification.ErrRetryExhausted:
|
||||||
|
// All retry attempts failed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -0,0 +1,210 @@
|
|||||||
|
package notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// HTTPClient defines the interface for HTTP operations
|
||||||
|
type HTTPClient interface {
|
||||||
|
Do(req *http.Request) (*http.Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Client represents the notification service HTTP client
|
||||||
|
type Client struct {
|
||||||
|
config *Config
|
||||||
|
httpClient HTTPClient
|
||||||
|
logger Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logger interface for structured logging
|
||||||
|
type Logger interface {
|
||||||
|
Info(msg string, fields map[string]interface{})
|
||||||
|
Error(msg string, fields map[string]interface{})
|
||||||
|
Debug(msg string, fields map[string]interface{})
|
||||||
|
Warn(msg string, fields map[string]interface{})
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient creates a new notification client
|
||||||
|
func NewClient(config *Config, logger Logger) (*Client, error) {
|
||||||
|
if config == nil {
|
||||||
|
config = DefaultConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := config.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Timeout: config.Timeout,
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
config: config,
|
||||||
|
httpClient: httpClient,
|
||||||
|
logger: logger,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClientWithHTTPClient creates a new notification client with custom HTTP client
|
||||||
|
func NewClientWithHTTPClient(config *Config, httpClient HTTPClient, logger Logger) (*Client, error) {
|
||||||
|
if config == nil {
|
||||||
|
config = DefaultConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := config.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid configuration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Client{
|
||||||
|
config: config,
|
||||||
|
httpClient: httpClient,
|
||||||
|
logger: logger,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendNotification sends a notification request to the notification service
|
||||||
|
func (c *Client) SendNotification(ctx context.Context, req *NotificationRequest) (*NotificationResponse, error) {
|
||||||
|
// Validate request
|
||||||
|
if err := req.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("request validation failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var lastErr error
|
||||||
|
start := time.Now()
|
||||||
|
|
||||||
|
for attempt := 0; attempt <= c.config.RetryAttempts; attempt++ {
|
||||||
|
if c.config.EnableLogging && c.logger != nil {
|
||||||
|
c.logger.Debug("Sending notification request", map[string]interface{}{
|
||||||
|
"attempt": attempt + 1,
|
||||||
|
"event_type": req.EventType,
|
||||||
|
"user_id": req.UserID,
|
||||||
|
"base_url": c.config.BaseURL,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
response, err := c.sendRequest(ctx, req)
|
||||||
|
if err == nil {
|
||||||
|
if c.config.EnableLogging && c.logger != nil {
|
||||||
|
c.logger.Info("Notification sent successfully", map[string]interface{}{
|
||||||
|
"event_type": req.EventType,
|
||||||
|
"user_id": req.UserID,
|
||||||
|
"attempts": attempt + 1,
|
||||||
|
"elapsed_time": time.Since(start).String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return response, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
lastErr = err
|
||||||
|
|
||||||
|
// Log retry attempt
|
||||||
|
if c.config.EnableLogging && c.logger != nil {
|
||||||
|
c.logger.Warn("Notification attempt failed", map[string]interface{}{
|
||||||
|
"attempt": attempt + 1,
|
||||||
|
"error": err.Error(),
|
||||||
|
"event_type": req.EventType,
|
||||||
|
"user_id": req.UserID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't retry on certain errors
|
||||||
|
if !c.shouldRetry(err) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't sleep on last attempt
|
||||||
|
if attempt < c.config.RetryAttempts {
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return nil, ctx.Err()
|
||||||
|
case <-time.After(c.config.RetryDelay):
|
||||||
|
// Continue to next attempt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if c.config.EnableLogging && c.logger != nil {
|
||||||
|
c.logger.Error("All notification attempts failed", map[string]interface{}{
|
||||||
|
"attempts": c.config.RetryAttempts + 1,
|
||||||
|
"elapsed_time": time.Since(start).String(),
|
||||||
|
"last_error": lastErr.Error(),
|
||||||
|
"event_type": req.EventType,
|
||||||
|
"user_id": req.UserID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ErrRetryExhausted{
|
||||||
|
Attempts: c.config.RetryAttempts + 1,
|
||||||
|
LastError: lastErr,
|
||||||
|
ElapsedTime: time.Since(start).String(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// sendRequest performs the actual HTTP request
|
||||||
|
func (c *Client) sendRequest(ctx context.Context, req *NotificationRequest) (*NotificationResponse, error) {
|
||||||
|
// Marshal request body
|
||||||
|
requestBody, err := json.Marshal(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to marshal request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create HTTP request
|
||||||
|
url := fmt.Sprintf("%s/api/v1/notifications", c.config.BaseURL)
|
||||||
|
httpReq, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewBuffer(requestBody))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create HTTP request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set headers
|
||||||
|
httpReq.Header.Set("Content-Type", "application/json")
|
||||||
|
httpReq.Header.Set("User-Agent", c.config.UserAgent)
|
||||||
|
|
||||||
|
// Send request
|
||||||
|
resp, err := c.httpClient.Do(httpReq)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("HTTP request failed: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// Read response body
|
||||||
|
body, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read response body: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle non-2xx status codes
|
||||||
|
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||||||
|
// Try to parse error response
|
||||||
|
var errorResp NotificationErrorResponse
|
||||||
|
if json.Unmarshal(body, &errorResp) == nil && errorResp.Error != "" {
|
||||||
|
return nil, MapHTTPError(resp.StatusCode, errorResp.Error)
|
||||||
|
}
|
||||||
|
return nil, MapHTTPError(resp.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse success response
|
||||||
|
var notificationResp NotificationResponse
|
||||||
|
if err := json.Unmarshal(body, ¬ificationResp); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal response: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ¬ificationResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// shouldRetry determines if a request should be retried based on the error
|
||||||
|
func (c *Client) shouldRetry(err error) bool {
|
||||||
|
switch e := err.(type) {
|
||||||
|
case ErrHTTP:
|
||||||
|
// Retry on server errors (5xx) and certain client errors
|
||||||
|
return e.StatusCode >= 500 || e.StatusCode == http.StatusTooManyRequests
|
||||||
|
default:
|
||||||
|
// Retry on network errors, timeouts, etc.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config holds the notification service configuration
|
||||||
|
type Config struct {
|
||||||
|
BaseURL string ``
|
||||||
|
RetryAttempts int ``
|
||||||
|
Timeout time.Duration ``
|
||||||
|
RetryDelay time.Duration ``
|
||||||
|
EnableLogging bool ``
|
||||||
|
UserAgent string ``
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultConfig returns default configuration for notification service
|
||||||
|
func DefaultConfig() *Config {
|
||||||
|
return &Config{
|
||||||
|
BaseURL: "http://127.0.0.1:9095",
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
RetryAttempts: 3,
|
||||||
|
RetryDelay: 2 * time.Second,
|
||||||
|
EnableLogging: true,
|
||||||
|
UserAgent: "TenderManagement-NotificationSDK/1.0",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConfig creates a new configuration for notification service
|
||||||
|
func NewConfig(baseURL string, timeout time.Duration, retryAttempts int, retryDelay time.Duration, enableLogging bool, userAgent string) *Config {
|
||||||
|
return &Config{
|
||||||
|
BaseURL: baseURL,
|
||||||
|
Timeout: timeout,
|
||||||
|
RetryAttempts: retryAttempts,
|
||||||
|
RetryDelay: retryDelay,
|
||||||
|
EnableLogging: enableLogging,
|
||||||
|
UserAgent: userAgent,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates the configuration
|
||||||
|
func (c *Config) Validate() error {
|
||||||
|
if c.BaseURL == "" {
|
||||||
|
return ErrInvalidConfig{Field: "BaseURL", Message: "base URL cannot be empty"}
|
||||||
|
}
|
||||||
|
if c.Timeout <= 0 {
|
||||||
|
return ErrInvalidConfig{Field: "Timeout", Message: "timeout must be positive"}
|
||||||
|
}
|
||||||
|
if c.RetryAttempts < 0 {
|
||||||
|
return ErrInvalidConfig{Field: "RetryAttempts", Message: "retry attempts cannot be negative"}
|
||||||
|
}
|
||||||
|
if c.RetryDelay < 0 {
|
||||||
|
return ErrInvalidConfig{Field: "RetryDelay", Message: "retry delay cannot be negative"}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/asaskevich/govalidator"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EventType represents the type of notification event
|
||||||
|
type EventType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
EventTypeEmail EventType = "EMAIL"
|
||||||
|
EventTypeSMS EventType = "SMS"
|
||||||
|
EventTypePush EventType = "PUSH"
|
||||||
|
EventTypeOTP EventType = "OTP"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotificationMethods contains different delivery methods for notifications
|
||||||
|
type NotificationMethods struct {
|
||||||
|
OTP string `json:"otp,omitempty" valid:"optional"`
|
||||||
|
SMS string `json:"sms,omitempty" valid:"optional"`
|
||||||
|
Push string `json:"push,omitempty" valid:"optional"`
|
||||||
|
Email string `json:"email,omitempty" valid:"optional,email"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotificationRequest represents the request structure for sending notifications
|
||||||
|
type NotificationRequest struct {
|
||||||
|
EventType EventType `json:"event_type" valid:"required,in(EMAIL|SMS|PUSH|OTP)"`
|
||||||
|
Message string `json:"message" valid:"required,length(1|1000)"`
|
||||||
|
Methods NotificationMethods `json:"methods" valid:"required"`
|
||||||
|
UserID string `json:"user_id,omitempty" valid:"optional"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotificationResponse represents the successful response from notification service
|
||||||
|
type NotificationResponse struct {
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NotificationErrorResponse represents the error response from notification service
|
||||||
|
type NotificationErrorResponse struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate validates the notification request
|
||||||
|
func (nr *NotificationRequest) Validate() error {
|
||||||
|
// First run govalidator validation
|
||||||
|
if _, err := govalidator.ValidateStruct(nr); err != nil {
|
||||||
|
return ErrValidation{Field: "struct", Message: err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom validation: ensure at least one method is provided based on event type
|
||||||
|
switch nr.EventType {
|
||||||
|
case EventTypeEmail:
|
||||||
|
if nr.Methods.Email == "" {
|
||||||
|
return ErrValidation{Field: "methods.email", Message: "email address is required for EMAIL event type"}
|
||||||
|
}
|
||||||
|
case EventTypeSMS:
|
||||||
|
if nr.Methods.SMS == "" {
|
||||||
|
return ErrValidation{Field: "methods.sms", Message: "SMS number is required for SMS event type"}
|
||||||
|
}
|
||||||
|
case EventTypePush:
|
||||||
|
if nr.Methods.Push == "" {
|
||||||
|
return ErrValidation{Field: "methods.push", Message: "push device token is required for PUSH event type"}
|
||||||
|
}
|
||||||
|
case EventTypeOTP:
|
||||||
|
if nr.Methods.OTP == "" {
|
||||||
|
return ErrValidation{Field: "methods.otp", Message: "OTP number is required for OTP event type"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasMethod checks if a specific method is configured
|
||||||
|
func (nm *NotificationMethods) HasMethod(eventType EventType) bool {
|
||||||
|
switch eventType {
|
||||||
|
case EventTypeEmail:
|
||||||
|
return nm.Email != ""
|
||||||
|
case EventTypeSMS:
|
||||||
|
return nm.SMS != ""
|
||||||
|
case EventTypePush:
|
||||||
|
return nm.Push != ""
|
||||||
|
case EventTypeOTP:
|
||||||
|
return nm.OTP != ""
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMethodValue returns the value for a specific method
|
||||||
|
func (nm *NotificationMethods) GetMethodValue(eventType EventType) string {
|
||||||
|
switch eventType {
|
||||||
|
case EventTypeEmail:
|
||||||
|
return nm.Email
|
||||||
|
case EventTypeSMS:
|
||||||
|
return nm.SMS
|
||||||
|
case EventTypePush:
|
||||||
|
return nm.Push
|
||||||
|
case EventTypeOTP:
|
||||||
|
return nm.OTP
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Error types for notification SDK
|
||||||
|
var (
|
||||||
|
ErrNotificationServiceUnavailable = fmt.Errorf("notification service unavailable")
|
||||||
|
ErrInvalidRequest = fmt.Errorf("invalid notification request")
|
||||||
|
ErrNetworkTimeout = fmt.Errorf("network timeout")
|
||||||
|
ErrUnauthorized = fmt.Errorf("unauthorized request")
|
||||||
|
ErrInternalError = fmt.Errorf("internal notification service error")
|
||||||
|
)
|
||||||
|
|
||||||
|
// ErrInvalidConfig represents configuration validation errors
|
||||||
|
type ErrInvalidConfig struct {
|
||||||
|
Field string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrInvalidConfig) Error() string {
|
||||||
|
return fmt.Sprintf("invalid config field %s: %s", e.Field, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrHTTP represents HTTP-related errors with status codes
|
||||||
|
type ErrHTTP struct {
|
||||||
|
StatusCode int
|
||||||
|
Message string
|
||||||
|
Body string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrHTTP) Error() string {
|
||||||
|
return fmt.Sprintf("HTTP %d: %s", e.StatusCode, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrValidation represents validation errors
|
||||||
|
type ErrValidation struct {
|
||||||
|
Field string
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrValidation) Error() string {
|
||||||
|
return fmt.Sprintf("validation error for field %s: %s", e.Field, e.Message)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ErrRetryExhausted represents retry exhaustion errors
|
||||||
|
type ErrRetryExhausted struct {
|
||||||
|
Attempts int
|
||||||
|
LastError error
|
||||||
|
ElapsedTime string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e ErrRetryExhausted) Error() string {
|
||||||
|
return fmt.Sprintf("retry exhausted after %d attempts (%s): %v", e.Attempts, e.ElapsedTime, e.LastError)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MapHTTPError maps HTTP status codes to appropriate errors
|
||||||
|
func MapHTTPError(statusCode int, body string) error {
|
||||||
|
switch statusCode {
|
||||||
|
case http.StatusBadRequest:
|
||||||
|
return ErrHTTP{StatusCode: statusCode, Message: "bad request", Body: body}
|
||||||
|
case http.StatusUnauthorized:
|
||||||
|
return ErrHTTP{StatusCode: statusCode, Message: "unauthorized", Body: body}
|
||||||
|
case http.StatusForbidden:
|
||||||
|
return ErrHTTP{StatusCode: statusCode, Message: "forbidden", Body: body}
|
||||||
|
case http.StatusNotFound:
|
||||||
|
return ErrHTTP{StatusCode: statusCode, Message: "not found", Body: body}
|
||||||
|
case http.StatusUnprocessableEntity:
|
||||||
|
return ErrHTTP{StatusCode: statusCode, Message: "validation error", Body: body}
|
||||||
|
case http.StatusInternalServerError:
|
||||||
|
return ErrHTTP{StatusCode: statusCode, Message: "internal server error", Body: body}
|
||||||
|
case http.StatusBadGateway:
|
||||||
|
return ErrHTTP{StatusCode: statusCode, Message: "bad gateway", Body: body}
|
||||||
|
case http.StatusServiceUnavailable:
|
||||||
|
return ErrHTTP{StatusCode: statusCode, Message: "service unavailable", Body: body}
|
||||||
|
case http.StatusGatewayTimeout:
|
||||||
|
return ErrHTTP{StatusCode: statusCode, Message: "gateway timeout", Body: body}
|
||||||
|
default:
|
||||||
|
return ErrHTTP{StatusCode: statusCode, Message: "unknown error", Body: body}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NotificationService defines the interface for notification operations
|
||||||
|
type NotificationService interface {
|
||||||
|
// SendNotification sends a notification using the configured client
|
||||||
|
SendNotification(ctx context.Context, req *NotificationRequest) (*NotificationResponse, error)
|
||||||
|
|
||||||
|
// SendEmail sends an email notification
|
||||||
|
SendEmail(ctx context.Context, email, message, userID string) (*NotificationResponse, error)
|
||||||
|
|
||||||
|
// SendSMS sends an SMS notification
|
||||||
|
SendSMS(ctx context.Context, phoneNumber, message, userID string) (*NotificationResponse, error)
|
||||||
|
|
||||||
|
// SendPush sends a push notification
|
||||||
|
SendPush(ctx context.Context, deviceToken, message, userID string) (*NotificationResponse, error)
|
||||||
|
|
||||||
|
// SendOTP sends an OTP notification
|
||||||
|
SendOTP(ctx context.Context, phoneNumber, message, userID string) (*NotificationResponse, error)
|
||||||
|
|
||||||
|
// Health checks if the notification service is available
|
||||||
|
Health(ctx context.Context) error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Builder provides a fluent interface for building notification requests
|
||||||
|
type Builder interface {
|
||||||
|
// SetEventType sets the event type
|
||||||
|
SetEventType(eventType EventType) Builder
|
||||||
|
|
||||||
|
// SetMessage sets the notification message
|
||||||
|
SetMessage(message string) Builder
|
||||||
|
|
||||||
|
// SetUserID sets the user ID
|
||||||
|
SetUserID(userID string) Builder
|
||||||
|
|
||||||
|
// SetEmail sets the email address
|
||||||
|
SetEmail(email string) Builder
|
||||||
|
|
||||||
|
// SetSMS sets the SMS phone number
|
||||||
|
SetSMS(phoneNumber string) Builder
|
||||||
|
|
||||||
|
// SetPush sets the push device token
|
||||||
|
SetPush(deviceToken string) Builder
|
||||||
|
|
||||||
|
// SetOTP sets the OTP phone number
|
||||||
|
SetOTP(phoneNumber string) Builder
|
||||||
|
|
||||||
|
// Build creates the notification request
|
||||||
|
Build() (*NotificationRequest, error)
|
||||||
|
|
||||||
|
// Send builds and sends the notification
|
||||||
|
Send(ctx context.Context) (*NotificationResponse, error)
|
||||||
|
}
|
||||||
@@ -0,0 +1,178 @@
|
|||||||
|
package notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SDK provides a high-level interface for the notification service
|
||||||
|
type SDK struct {
|
||||||
|
service NotificationService
|
||||||
|
config *Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSDK creates a new notification SDK with the provided configuration and logger
|
||||||
|
func New(config *Config, logger Logger) (*SDK, error) {
|
||||||
|
if config == nil {
|
||||||
|
config = DefaultConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := NewClient(config, logger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create notification client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
service := NewService(client)
|
||||||
|
|
||||||
|
return &SDK{
|
||||||
|
service: service,
|
||||||
|
config: config,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSDKWithClient creates a new notification SDK with a custom HTTP client
|
||||||
|
func NewSDKWithClient(config *Config, httpClient HTTPClient, logger Logger) (*SDK, error) {
|
||||||
|
if config == nil {
|
||||||
|
config = DefaultConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := NewClientWithHTTPClient(config, httpClient, logger)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create notification client: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
service := NewService(client)
|
||||||
|
|
||||||
|
return &SDK{
|
||||||
|
service: service,
|
||||||
|
config: config,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendNotification sends a custom notification request
|
||||||
|
func (s *SDK) SendNotification(ctx context.Context, req *NotificationRequest) (*NotificationResponse, error) {
|
||||||
|
return s.service.SendNotification(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendEmail sends an email notification
|
||||||
|
func (s *SDK) SendEmail(ctx context.Context, email, message, userID string) (*NotificationResponse, error) {
|
||||||
|
return s.service.SendEmail(ctx, email, message, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendSMS sends an SMS notification
|
||||||
|
func (s *SDK) SendSMS(ctx context.Context, phoneNumber, message, userID string) (*NotificationResponse, error) {
|
||||||
|
return s.service.SendSMS(ctx, phoneNumber, message, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendPush sends a push notification
|
||||||
|
func (s *SDK) SendPush(ctx context.Context, deviceToken, message, userID string) (*NotificationResponse, error) {
|
||||||
|
return s.service.SendPush(ctx, deviceToken, message, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendOTP sends an OTP notification
|
||||||
|
func (s *SDK) SendOTP(ctx context.Context, phoneNumber, message, userID string) (*NotificationResponse, error) {
|
||||||
|
return s.service.SendOTP(ctx, phoneNumber, message, userID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuilder creates a new notification builder for fluent API usage
|
||||||
|
func (s *SDK) NewBuilder() Builder {
|
||||||
|
return s.service.(*Service).NewBuilder()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Health checks if the notification service is available
|
||||||
|
func (s *SDK) Health(ctx context.Context) error {
|
||||||
|
return s.service.Health(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig returns the current configuration
|
||||||
|
func (s *SDK) GetConfig() *Config {
|
||||||
|
return s.config
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience functions for quick notifications
|
||||||
|
|
||||||
|
// QuickEmail sends a simple email notification
|
||||||
|
func (s *SDK) QuickEmail(ctx context.Context, email, message string) error {
|
||||||
|
_, err := s.SendEmail(ctx, email, message, "")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuickSMS sends a simple SMS notification
|
||||||
|
func (s *SDK) QuickSMS(ctx context.Context, phoneNumber, message string) error {
|
||||||
|
_, err := s.SendSMS(ctx, phoneNumber, message, "")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuickPush sends a simple push notification
|
||||||
|
func (s *SDK) QuickPush(ctx context.Context, deviceToken, message string) error {
|
||||||
|
_, err := s.SendPush(ctx, deviceToken, message, "")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuickOTP sends a simple OTP notification
|
||||||
|
func (s *SDK) QuickOTP(ctx context.Context, phoneNumber, message string) error {
|
||||||
|
_, err := s.SendOTP(ctx, phoneNumber, message, "")
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Batch operations for sending multiple notifications
|
||||||
|
|
||||||
|
// BatchRequest represents a batch notification request
|
||||||
|
type BatchRequest struct {
|
||||||
|
Requests []*NotificationRequest `json:"requests"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchResponse represents the response from batch operations
|
||||||
|
type BatchResponse struct {
|
||||||
|
Responses []BatchItemResponse `json:"responses"`
|
||||||
|
Success int `json:"success"`
|
||||||
|
Failed int `json:"failed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BatchItemResponse represents individual response in batch
|
||||||
|
type BatchItemResponse struct {
|
||||||
|
Index int `json:"index"`
|
||||||
|
Success bool `json:"success"`
|
||||||
|
Response *NotificationResponse `json:"response,omitempty"`
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendBatch sends multiple notifications in batch (sequential for now)
|
||||||
|
func (s *SDK) SendBatch(ctx context.Context, requests []*NotificationRequest) (*BatchResponse, error) {
|
||||||
|
if len(requests) == 0 {
|
||||||
|
return &BatchResponse{
|
||||||
|
Responses: []BatchItemResponse{},
|
||||||
|
Success: 0,
|
||||||
|
Failed: 0,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
responses := make([]BatchItemResponse, len(requests))
|
||||||
|
successCount := 0
|
||||||
|
failedCount := 0
|
||||||
|
|
||||||
|
for i, req := range requests {
|
||||||
|
resp, err := s.service.SendNotification(ctx, req)
|
||||||
|
if err != nil {
|
||||||
|
responses[i] = BatchItemResponse{
|
||||||
|
Index: i,
|
||||||
|
Success: false,
|
||||||
|
Error: err.Error(),
|
||||||
|
}
|
||||||
|
failedCount++
|
||||||
|
} else {
|
||||||
|
responses[i] = BatchItemResponse{
|
||||||
|
Index: i,
|
||||||
|
Success: true,
|
||||||
|
Response: resp,
|
||||||
|
}
|
||||||
|
successCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BatchResponse{
|
||||||
|
Responses: responses,
|
||||||
|
Success: successCount,
|
||||||
|
Failed: failedCount,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
package notification
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Service implements the NotificationService interface
|
||||||
|
type Service struct {
|
||||||
|
client *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewService creates a new notification service
|
||||||
|
func NewService(client *Client) NotificationService {
|
||||||
|
return &Service{
|
||||||
|
client: client,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendNotification sends a notification using the configured client
|
||||||
|
func (s *Service) SendNotification(ctx context.Context, req *NotificationRequest) (*NotificationResponse, error) {
|
||||||
|
return s.client.SendNotification(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendEmail sends an email notification
|
||||||
|
func (s *Service) SendEmail(ctx context.Context, email, message, userID string) (*NotificationResponse, error) {
|
||||||
|
req := &NotificationRequest{
|
||||||
|
EventType: EventTypeEmail,
|
||||||
|
Message: message,
|
||||||
|
Methods: NotificationMethods{
|
||||||
|
Email: email,
|
||||||
|
},
|
||||||
|
UserID: userID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.client.SendNotification(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendSMS sends an SMS notification
|
||||||
|
func (s *Service) SendSMS(ctx context.Context, phoneNumber, message, userID string) (*NotificationResponse, error) {
|
||||||
|
req := &NotificationRequest{
|
||||||
|
EventType: EventTypeSMS,
|
||||||
|
Message: message,
|
||||||
|
Methods: NotificationMethods{
|
||||||
|
SMS: phoneNumber,
|
||||||
|
},
|
||||||
|
UserID: userID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.client.SendNotification(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendPush sends a push notification
|
||||||
|
func (s *Service) SendPush(ctx context.Context, deviceToken, message, userID string) (*NotificationResponse, error) {
|
||||||
|
req := &NotificationRequest{
|
||||||
|
EventType: EventTypePush,
|
||||||
|
Message: message,
|
||||||
|
Methods: NotificationMethods{
|
||||||
|
Push: deviceToken,
|
||||||
|
},
|
||||||
|
UserID: userID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.client.SendNotification(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SendOTP sends an OTP notification
|
||||||
|
func (s *Service) SendOTP(ctx context.Context, phoneNumber, message, userID string) (*NotificationResponse, error) {
|
||||||
|
req := &NotificationRequest{
|
||||||
|
EventType: EventTypeOTP,
|
||||||
|
Message: message,
|
||||||
|
Methods: NotificationMethods{
|
||||||
|
OTP: phoneNumber,
|
||||||
|
},
|
||||||
|
UserID: userID,
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.client.SendNotification(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Health checks if the notification service is available
|
||||||
|
func (s *Service) Health(ctx context.Context) error {
|
||||||
|
// Create a simple test request to check service availability
|
||||||
|
testReq := &NotificationRequest{
|
||||||
|
EventType: EventTypeEmail,
|
||||||
|
Message: "health check",
|
||||||
|
Methods: NotificationMethods{
|
||||||
|
Email: "test@example.com",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't actually send this, just validate the client can create a request
|
||||||
|
if err := testReq.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("service health check failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// notificationBuilder implements the Builder interface
|
||||||
|
type notificationBuilder struct {
|
||||||
|
service *Service
|
||||||
|
request *NotificationRequest
|
||||||
|
hasErrors []error
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewBuilder creates a new notification builder
|
||||||
|
func (s *Service) NewBuilder() Builder {
|
||||||
|
return ¬ificationBuilder{
|
||||||
|
service: s,
|
||||||
|
request: &NotificationRequest{
|
||||||
|
Methods: NotificationMethods{},
|
||||||
|
},
|
||||||
|
hasErrors: make([]error, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEventType sets the event type
|
||||||
|
func (b *notificationBuilder) SetEventType(eventType EventType) Builder {
|
||||||
|
b.request.EventType = eventType
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMessage sets the notification message
|
||||||
|
func (b *notificationBuilder) SetMessage(message string) Builder {
|
||||||
|
b.request.Message = message
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUserID sets the user ID
|
||||||
|
func (b *notificationBuilder) SetUserID(userID string) Builder {
|
||||||
|
b.request.UserID = userID
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetEmail sets the email address
|
||||||
|
func (b *notificationBuilder) SetEmail(email string) Builder {
|
||||||
|
b.request.Methods.Email = email
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetSMS sets the SMS phone number
|
||||||
|
func (b *notificationBuilder) SetSMS(phoneNumber string) Builder {
|
||||||
|
b.request.Methods.SMS = phoneNumber
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPush sets the push device token
|
||||||
|
func (b *notificationBuilder) SetPush(deviceToken string) Builder {
|
||||||
|
b.request.Methods.Push = deviceToken
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetOTP sets the OTP phone number
|
||||||
|
func (b *notificationBuilder) SetOTP(phoneNumber string) Builder {
|
||||||
|
b.request.Methods.OTP = phoneNumber
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build creates the notification request
|
||||||
|
func (b *notificationBuilder) Build() (*NotificationRequest, error) {
|
||||||
|
if err := b.request.Validate(); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to build notification request: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.request, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send builds and sends the notification
|
||||||
|
func (b *notificationBuilder) Send(ctx context.Context) (*NotificationResponse, error) {
|
||||||
|
req, err := b.Build()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.service.SendNotification(ctx, req)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user