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:
@@ -65,6 +65,15 @@ type RateLimitConfig struct {
|
||||
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
|
||||
// OS environment variables take precedence over .env file values
|
||||
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