386 lines
11 KiB
Go
386 lines
11 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
|
|
"eslogad-be/internal/config"
|
|
"eslogad-be/internal/contract"
|
|
|
|
"github.com/google/uuid"
|
|
novu "github.com/novuhq/go-novu/lib"
|
|
)
|
|
|
|
type NotificationService interface {
|
|
TriggerNotification(ctx context.Context, req *contract.TriggerNotificationRequest) (*contract.TriggerNotificationResponse, error)
|
|
BulkTriggerNotification(ctx context.Context, req *contract.BulkTriggerNotificationRequest) (*contract.BulkTriggerNotificationResponse, error)
|
|
GetSubscriber(ctx context.Context, userID uuid.UUID) (*contract.GetSubscriberResponse, error)
|
|
UpdateSubscriberChannel(ctx context.Context, req *contract.UpdateSubscriberChannelRequest) (*contract.UpdateSubscriberChannelResponse, error)
|
|
}
|
|
|
|
type NotificationServiceImpl struct {
|
|
client *novu.APIClient
|
|
config *config.NovuConfig
|
|
userProcessor UserProcessorForNotification
|
|
}
|
|
|
|
type UserProcessorForNotification interface {
|
|
GetUserByID(ctx context.Context, id uuid.UUID) (*contract.UserResponse, error)
|
|
}
|
|
|
|
func NewNotificationService(cfg *config.NovuConfig, userProcessor UserProcessorForNotification) *NotificationServiceImpl {
|
|
var client *novu.APIClient
|
|
if cfg.APIKey != "" {
|
|
// Create Novu config with backend URL
|
|
novuConfig := &novu.Config{}
|
|
if cfg.BaseURL != "" {
|
|
backendURL, err := url.Parse(cfg.BaseURL)
|
|
if err == nil {
|
|
novuConfig.BackendURL = backendURL
|
|
}
|
|
}
|
|
client = novu.NewAPIClient(cfg.APIKey, novuConfig)
|
|
}
|
|
|
|
return &NotificationServiceImpl{
|
|
client: client,
|
|
config: cfg,
|
|
userProcessor: userProcessor,
|
|
}
|
|
}
|
|
|
|
func (s *NotificationServiceImpl) TriggerNotification(ctx context.Context, req *contract.TriggerNotificationRequest) (*contract.TriggerNotificationResponse, error) {
|
|
if s.client == nil {
|
|
return &contract.TriggerNotificationResponse{
|
|
Success: false,
|
|
Message: "notification service not configured",
|
|
}, nil
|
|
}
|
|
|
|
subscriberID := req.UserID.String()
|
|
|
|
_, err := s.ensureSubscriberExists(ctx, req.UserID)
|
|
if err != nil {
|
|
return &contract.TriggerNotificationResponse{
|
|
Success: false,
|
|
Message: fmt.Sprintf("failed to ensure subscriber exists: %v", err),
|
|
}, nil
|
|
}
|
|
|
|
// Prepare the trigger payload
|
|
to := map[string]interface{}{
|
|
"subscriberId": subscriberID,
|
|
}
|
|
|
|
// Add additional recipient information if provided
|
|
if req.To != nil {
|
|
if req.To.Email != "" {
|
|
to["email"] = req.To.Email
|
|
}
|
|
if req.To.Phone != "" {
|
|
to["phone"] = req.To.Phone
|
|
}
|
|
}
|
|
|
|
// Prepare overrides
|
|
overrides := make(map[string]interface{})
|
|
if req.Overrides != nil {
|
|
if req.Overrides.Email != nil {
|
|
overrides["email"] = req.Overrides.Email
|
|
}
|
|
if req.Overrides.SMS != nil {
|
|
overrides["sms"] = req.Overrides.SMS
|
|
}
|
|
if req.Overrides.InApp != nil {
|
|
overrides["in_app"] = req.Overrides.InApp
|
|
}
|
|
if req.Overrides.Push != nil {
|
|
overrides["push"] = req.Overrides.Push
|
|
}
|
|
if req.Overrides.Chat != nil {
|
|
overrides["chat"] = req.Overrides.Chat
|
|
}
|
|
}
|
|
|
|
// Trigger the notification using the template ID
|
|
triggerPayload := novu.ITriggerPayloadOptions{
|
|
To: to,
|
|
Payload: req.TemplateData,
|
|
Overrides: overrides,
|
|
}
|
|
|
|
resp, err := s.client.EventApi.Trigger(ctx, req.TemplateID, triggerPayload)
|
|
if err != nil {
|
|
return &contract.TriggerNotificationResponse{
|
|
Success: false,
|
|
Message: fmt.Sprintf("failed to trigger notification: %v", err),
|
|
}, nil
|
|
}
|
|
|
|
// Extract transaction ID from response
|
|
transactionID := ""
|
|
if respData, ok := resp.Data.(map[string]interface{}); ok {
|
|
if txID, exists := respData["transactionId"]; exists {
|
|
transactionID = fmt.Sprintf("%v", txID)
|
|
}
|
|
}
|
|
|
|
return &contract.TriggerNotificationResponse{
|
|
Success: true,
|
|
TransactionID: transactionID,
|
|
Message: "notification triggered successfully",
|
|
}, nil
|
|
}
|
|
|
|
func (s *NotificationServiceImpl) BulkTriggerNotification(ctx context.Context, req *contract.BulkTriggerNotificationRequest) (*contract.BulkTriggerNotificationResponse, error) {
|
|
if s.client == nil {
|
|
return &contract.BulkTriggerNotificationResponse{
|
|
Success: false,
|
|
TotalSent: 0,
|
|
TotalFailed: len(req.UserIDs),
|
|
}, nil
|
|
}
|
|
|
|
results := make([]contract.NotificationResult, 0, len(req.UserIDs))
|
|
successCount := 0
|
|
failedCount := 0
|
|
|
|
for _, userID := range req.UserIDs {
|
|
// Create individual trigger request
|
|
triggerReq := &contract.TriggerNotificationRequest{
|
|
UserID: userID,
|
|
TemplateID: req.TemplateID,
|
|
TemplateData: req.TemplateData,
|
|
Overrides: req.Overrides,
|
|
}
|
|
|
|
resp, err := s.TriggerNotification(ctx, triggerReq)
|
|
|
|
result := contract.NotificationResult{
|
|
UserID: userID,
|
|
Success: resp.Success,
|
|
}
|
|
|
|
if resp.Success {
|
|
result.TransactionID = resp.TransactionID
|
|
successCount++
|
|
} else {
|
|
if err != nil {
|
|
result.Error = err.Error()
|
|
} else {
|
|
result.Error = resp.Message
|
|
}
|
|
failedCount++
|
|
}
|
|
|
|
results = append(results, result)
|
|
}
|
|
|
|
return &contract.BulkTriggerNotificationResponse{
|
|
Success: failedCount == 0,
|
|
TotalSent: successCount,
|
|
TotalFailed: failedCount,
|
|
Results: results,
|
|
}, nil
|
|
}
|
|
|
|
func (s *NotificationServiceImpl) GetSubscriber(ctx context.Context, userID uuid.UUID) (*contract.GetSubscriberResponse, error) {
|
|
if s.client == nil {
|
|
return nil, fmt.Errorf("notification service not configured")
|
|
}
|
|
|
|
subscriberID := userID.String()
|
|
|
|
// Try to get the subscriber
|
|
subscriber, err := s.client.SubscriberApi.Get(ctx, subscriberID)
|
|
if err != nil {
|
|
// If subscriber doesn't exist, create it
|
|
_, createErr := s.ensureSubscriberExists(ctx, userID)
|
|
if createErr != nil {
|
|
return nil, fmt.Errorf("failed to get or create subscriber: %w", createErr)
|
|
}
|
|
|
|
// Try to get again after creation
|
|
subscriber, err = s.client.SubscriberApi.Get(ctx, subscriberID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get subscriber after creation: %w", err)
|
|
}
|
|
}
|
|
|
|
// Convert Novu subscriber to our response format
|
|
response := &contract.GetSubscriberResponse{
|
|
SubscriberID: subscriberID,
|
|
}
|
|
|
|
if subData, ok := subscriber.Data.(map[string]interface{}); ok {
|
|
if email, exists := subData["email"]; exists {
|
|
response.Email = fmt.Sprintf("%v", email)
|
|
}
|
|
if firstName, exists := subData["firstName"]; exists {
|
|
response.FirstName = fmt.Sprintf("%v", firstName)
|
|
}
|
|
if lastName, exists := subData["lastName"]; exists {
|
|
response.LastName = fmt.Sprintf("%v", lastName)
|
|
}
|
|
if phone, exists := subData["phone"]; exists {
|
|
response.Phone = fmt.Sprintf("%v", phone)
|
|
}
|
|
if avatar, exists := subData["avatar"]; exists {
|
|
response.Avatar = fmt.Sprintf("%v", avatar)
|
|
}
|
|
if data, exists := subData["data"]; exists {
|
|
if dataMap, ok := data.(map[string]interface{}); ok {
|
|
response.Data = dataMap
|
|
}
|
|
}
|
|
if channels, exists := subData["channels"]; exists {
|
|
if channelList, ok := channels.([]interface{}); ok {
|
|
response.Channels = make([]contract.ChannelCredentials, 0, len(channelList))
|
|
for _, ch := range channelList {
|
|
if chMap, ok := ch.(map[string]interface{}); ok {
|
|
channelCred := contract.ChannelCredentials{}
|
|
if chType, exists := chMap["providerId"]; exists {
|
|
channelCred.Channel = fmt.Sprintf("%v", chType)
|
|
}
|
|
if creds, exists := chMap["credentials"]; exists {
|
|
if credMap, ok := creds.(map[string]interface{}); ok {
|
|
channelCred.Credentials = credMap
|
|
}
|
|
}
|
|
response.Channels = append(response.Channels, channelCred)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return response, nil
|
|
}
|
|
|
|
func (s *NotificationServiceImpl) UpdateSubscriberChannel(ctx context.Context, req *contract.UpdateSubscriberChannelRequest) (*contract.UpdateSubscriberChannelResponse, error) {
|
|
if s.client == nil {
|
|
return &contract.UpdateSubscriberChannelResponse{
|
|
Success: false,
|
|
Message: "notification service not configured",
|
|
}, nil
|
|
}
|
|
|
|
subscriberID := req.UserID.String()
|
|
|
|
// Ensure subscriber exists
|
|
_, err := s.ensureSubscriberExists(ctx, req.UserID)
|
|
if err != nil {
|
|
return &contract.UpdateSubscriberChannelResponse{
|
|
Success: false,
|
|
Message: fmt.Sprintf("failed to ensure subscriber exists: %v", err),
|
|
}, nil
|
|
}
|
|
|
|
// Since the Novu Go SDK doesn't have UpdateCredentials, we'll update the subscriber data instead
|
|
// Get user info to update subscriber
|
|
user, err := s.userProcessor.GetUserByID(ctx, req.UserID)
|
|
if err != nil {
|
|
return &contract.UpdateSubscriberChannelResponse{
|
|
Success: false,
|
|
Message: fmt.Sprintf("failed to get user data: %v", err),
|
|
}, nil
|
|
}
|
|
|
|
// Prepare subscriber data with channel credentials stored in data
|
|
data := map[string]interface{}{
|
|
"userId": user.ID.String(),
|
|
"email": user.Email,
|
|
"isActive": user.IsActive,
|
|
"updatedAt": user.UpdatedAt,
|
|
}
|
|
|
|
// Store channel credentials in the subscriber data
|
|
channelKey := fmt.Sprintf("channel_%s", req.Channel)
|
|
data[channelKey] = req.Credentials
|
|
|
|
// Update subscriber with new data
|
|
updateData := novu.SubscriberPayload{
|
|
Email: user.Email,
|
|
FirstName: user.Name,
|
|
LastName: "",
|
|
Phone: "",
|
|
Avatar: "",
|
|
Data: data,
|
|
}
|
|
|
|
_, err = s.client.SubscriberApi.Update(ctx, subscriberID, updateData)
|
|
if err != nil {
|
|
return &contract.UpdateSubscriberChannelResponse{
|
|
Success: false,
|
|
Message: fmt.Sprintf("failed to update subscriber channel: %v", err),
|
|
}, nil
|
|
}
|
|
|
|
return &contract.UpdateSubscriberChannelResponse{
|
|
Success: true,
|
|
Message: "subscriber channel updated successfully",
|
|
}, nil
|
|
}
|
|
|
|
func (s *NotificationServiceImpl) ensureSubscriberExists(ctx context.Context, userID uuid.UUID) (bool, error) {
|
|
subscriberID := userID.String()
|
|
|
|
_, err := s.client.SubscriberApi.Get(ctx, subscriberID)
|
|
if err == nil {
|
|
return false, nil
|
|
}
|
|
|
|
user, err := s.userProcessor.GetUserByID(ctx, userID)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to get user data: %w", err)
|
|
}
|
|
|
|
data := map[string]interface{}{
|
|
"userId": user.ID.String(),
|
|
"email": user.Email,
|
|
"isActive": user.IsActive,
|
|
"createdAt": user.CreatedAt,
|
|
}
|
|
|
|
if user.Roles != nil && len(user.Roles) > 0 {
|
|
roles := make([]map[string]interface{}, len(user.Roles))
|
|
for i, role := range user.Roles {
|
|
roles[i] = map[string]interface{}{
|
|
"id": role.ID.String(),
|
|
"name": role.Name,
|
|
"code": role.Code,
|
|
}
|
|
}
|
|
data["roles"] = roles
|
|
}
|
|
|
|
if user.DepartmentResponse != nil && len(user.DepartmentResponse) > 0 {
|
|
depts := make([]map[string]interface{}, len(user.DepartmentResponse))
|
|
for i, dept := range user.DepartmentResponse {
|
|
depts[i] = map[string]interface{}{
|
|
"id": dept.ID.String(),
|
|
"name": dept.Name,
|
|
"code": dept.Code,
|
|
}
|
|
}
|
|
data["departments"] = depts
|
|
}
|
|
|
|
subscriber := novu.SubscriberPayload{
|
|
Email: user.Email,
|
|
FirstName: user.Name,
|
|
LastName: "",
|
|
Phone: "",
|
|
Avatar: "",
|
|
Data: data,
|
|
}
|
|
|
|
_, err = s.client.SubscriberApi.Identify(ctx, subscriberID, subscriber)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to create subscriber: %w", err)
|
|
}
|
|
|
|
return true, nil
|
|
}
|