362 lines
9.4 KiB
Go
362 lines
9.4 KiB
Go
package processor
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/url"
|
|
|
|
"eslogad-be/internal/config"
|
|
"eslogad-be/internal/contract"
|
|
"eslogad-be/internal/entities"
|
|
|
|
"github.com/google/uuid"
|
|
novu "github.com/novuhq/go-novu/lib"
|
|
)
|
|
|
|
type NotificationProcessor interface {
|
|
// User management
|
|
CreateSubscriber(ctx context.Context, user *entities.User) error
|
|
UpdateSubscriber(ctx context.Context, user *entities.User) error
|
|
DeleteSubscriber(ctx context.Context, userID uuid.UUID) error
|
|
CreateSubscriberFromContract(ctx context.Context, user *contract.UserResponse) error
|
|
BulkCreateSubscribers(ctx context.Context, users []*entities.User) error
|
|
|
|
// Letter notifications
|
|
SendIncomingLetterNotification(ctx context.Context, letterID uuid.UUID, recipientUserID uuid.UUID, subject string, body string) error
|
|
}
|
|
|
|
type NotificationProcessorImpl struct {
|
|
provider NotificationProvider
|
|
workflowID string
|
|
}
|
|
|
|
func NewNotificationProcessor(provider NotificationProvider, workflowID string) *NotificationProcessorImpl {
|
|
return &NotificationProcessorImpl{
|
|
provider: provider,
|
|
workflowID: workflowID,
|
|
}
|
|
}
|
|
|
|
func (p *NotificationProcessorImpl) CreateSubscriber(ctx context.Context, user *entities.User) error {
|
|
return p.provider.CreateSubscriber(ctx, user)
|
|
}
|
|
|
|
func (p *NotificationProcessorImpl) UpdateSubscriber(ctx context.Context, user *entities.User) error {
|
|
return p.provider.UpdateSubscriber(ctx, user)
|
|
}
|
|
|
|
func (p *NotificationProcessorImpl) DeleteSubscriber(ctx context.Context, userID uuid.UUID) error {
|
|
return p.provider.DeleteSubscriber(ctx, userID)
|
|
}
|
|
|
|
func (p *NotificationProcessorImpl) CreateSubscriberFromContract(ctx context.Context, user *contract.UserResponse) error {
|
|
return p.provider.CreateSubscriberFromContract(ctx, user)
|
|
}
|
|
|
|
func (p *NotificationProcessorImpl) BulkCreateSubscribers(ctx context.Context, users []*entities.User) error {
|
|
return p.provider.BulkCreateSubscribers(ctx, users)
|
|
}
|
|
|
|
func (p *NotificationProcessorImpl) SendIncomingLetterNotification(ctx context.Context, letterID uuid.UUID, recipientUserID uuid.UUID, subject string, body string) error {
|
|
// Ensure subscriber exists
|
|
if err := p.provider.EnsureSubscriberExists(ctx, recipientUserID); err != nil {
|
|
return fmt.Errorf("failed to ensure subscriber exists: %w", err)
|
|
}
|
|
|
|
// Build notification URL
|
|
url := fmt.Sprintf("/en/apps/surat-menyurat/masuk-detail/%s", letterID.String())
|
|
|
|
// Use workflow ID from config (defaults to "notification-dashbpard")
|
|
workflowID := p.workflowID
|
|
if workflowID == "" {
|
|
workflowID = "notification-dashbpard"
|
|
}
|
|
|
|
// Send notification
|
|
return p.provider.SendNotification(ctx, NotificationPayload{
|
|
RecipientID: recipientUserID,
|
|
EventName: workflowID,
|
|
Data: map[string]interface{}{
|
|
"subject": subject,
|
|
"body": body,
|
|
"url": url,
|
|
},
|
|
})
|
|
}
|
|
|
|
// NotificationProvider interface for different notification services
|
|
type NotificationProvider interface {
|
|
// User management
|
|
CreateSubscriber(ctx context.Context, user *entities.User) error
|
|
UpdateSubscriber(ctx context.Context, user *entities.User) error
|
|
DeleteSubscriber(ctx context.Context, userID uuid.UUID) error
|
|
CreateSubscriberFromContract(ctx context.Context, user *contract.UserResponse) error
|
|
BulkCreateSubscribers(ctx context.Context, users []*entities.User) error
|
|
|
|
// Core notification methods
|
|
EnsureSubscriberExists(ctx context.Context, userID uuid.UUID) error
|
|
SendNotification(ctx context.Context, payload NotificationPayload) error
|
|
}
|
|
|
|
type NotificationPayload struct {
|
|
RecipientID uuid.UUID
|
|
EventName string
|
|
Data map[string]interface{}
|
|
}
|
|
|
|
// NovuProvider implements NotificationProvider using Novu
|
|
type NovuProvider struct {
|
|
client *novu.APIClient
|
|
config *config.NovuConfig
|
|
}
|
|
|
|
func NewNovuProvider(cfg *config.NovuConfig) *NovuProvider {
|
|
if cfg.APIKey == "" {
|
|
return &NovuProvider{
|
|
client: nil,
|
|
config: cfg,
|
|
}
|
|
}
|
|
|
|
// 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 &NovuProvider{
|
|
client: client,
|
|
config: cfg,
|
|
}
|
|
}
|
|
|
|
func (p *NovuProvider) CreateSubscriber(ctx context.Context, user *entities.User) error {
|
|
if p.client == nil {
|
|
return fmt.Errorf("novu client not initialized")
|
|
}
|
|
|
|
subscriberID := user.ID.String()
|
|
|
|
data := map[string]interface{}{
|
|
"userId": user.ID.String(),
|
|
"email": user.Email,
|
|
"isActive": user.IsActive,
|
|
"createdAt": user.CreatedAt,
|
|
}
|
|
|
|
if user.Departments != nil && len(user.Departments) > 0 {
|
|
depts := make([]map[string]interface{}, len(user.Departments))
|
|
for i, dept := range user.Departments {
|
|
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 := p.client.SubscriberApi.Identify(ctx, subscriberID, subscriber)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create subscriber: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *NovuProvider) UpdateSubscriber(ctx context.Context, user *entities.User) error {
|
|
if p.client == nil {
|
|
return fmt.Errorf("novu client not initialized")
|
|
}
|
|
|
|
subscriberID := user.ID.String()
|
|
|
|
data := map[string]interface{}{
|
|
"userId": user.ID.String(),
|
|
"email": user.Email,
|
|
"isActive": user.IsActive,
|
|
"updatedAt": user.UpdatedAt,
|
|
}
|
|
|
|
if user.Departments != nil && len(user.Departments) > 0 {
|
|
depts := make([]map[string]interface{}, len(user.Departments))
|
|
for i, dept := range user.Departments {
|
|
depts[i] = map[string]interface{}{
|
|
"id": dept.ID.String(),
|
|
"name": dept.Name,
|
|
"code": dept.Code,
|
|
}
|
|
}
|
|
data["departments"] = depts
|
|
}
|
|
|
|
updateData := novu.SubscriberPayload{
|
|
Email: user.Email,
|
|
FirstName: user.Name,
|
|
LastName: "",
|
|
Phone: "",
|
|
Avatar: "",
|
|
Data: data,
|
|
}
|
|
|
|
_, err := p.client.SubscriberApi.Update(ctx, subscriberID, updateData)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to update subscriber: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *NovuProvider) DeleteSubscriber(ctx context.Context, userID uuid.UUID) error {
|
|
if p.client == nil {
|
|
return fmt.Errorf("novu client not initialized")
|
|
}
|
|
|
|
subscriberID := userID.String()
|
|
|
|
_, err := p.client.SubscriberApi.Delete(ctx, subscriberID)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete subscriber: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *NovuProvider) CreateSubscriberFromContract(ctx context.Context, user *contract.UserResponse) error {
|
|
if p.client == nil {
|
|
return fmt.Errorf("novu client not initialized")
|
|
}
|
|
|
|
subscriberID := user.ID.String()
|
|
|
|
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 := p.client.SubscriberApi.Identify(ctx, subscriberID, subscriber)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create subscriber from contract: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *NovuProvider) BulkCreateSubscribers(ctx context.Context, users []*entities.User) error {
|
|
if p.client == nil {
|
|
return fmt.Errorf("novu client not initialized")
|
|
}
|
|
|
|
var lastErr error
|
|
successCount := 0
|
|
|
|
for _, user := range users {
|
|
err := p.CreateSubscriber(ctx, user)
|
|
if err != nil {
|
|
lastErr = err
|
|
continue
|
|
}
|
|
successCount++
|
|
}
|
|
|
|
if lastErr != nil && successCount == 0 {
|
|
return fmt.Errorf("failed to create any subscribers, last error: %w", lastErr)
|
|
}
|
|
|
|
if lastErr != nil {
|
|
return fmt.Errorf("created %d out of %d subscribers, last error: %w", successCount, len(users), lastErr)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *NovuProvider) EnsureSubscriberExists(ctx context.Context, userID uuid.UUID) error {
|
|
if p.client == nil {
|
|
return fmt.Errorf("novu client not initialized")
|
|
}
|
|
|
|
subscriberID := userID.String()
|
|
|
|
// Check if subscriber exists
|
|
_, err := p.client.SubscriberApi.Get(ctx, subscriberID)
|
|
if err != nil {
|
|
// Subscriber doesn't exist, create a basic one
|
|
subscriber := novu.SubscriberPayload{
|
|
Email: fmt.Sprintf("%s@placeholder.com", subscriberID),
|
|
}
|
|
_, err = p.client.SubscriberApi.Identify(ctx, subscriberID, subscriber)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to ensure subscriber exists: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (p *NovuProvider) SendNotification(ctx context.Context, payload NotificationPayload) error {
|
|
if p.client == nil {
|
|
return fmt.Errorf("novu client not initialized")
|
|
}
|
|
|
|
triggerPayload := novu.ITriggerPayloadOptions{
|
|
To: payload.RecipientID.String(),
|
|
Payload: payload.Data,
|
|
}
|
|
|
|
_, err := p.client.EventApi.Trigger(ctx, payload.EventName, triggerPayload)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to send notification: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|