Passed
Push — main ( 570b65...2ff189 )
by Acho
02:48
created

services.*MarketingService.DeleteUser   A

Complexity

Conditions 4

Size

Total Lines 24
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 17
dl 0
loc 24
rs 9.55
c 0
b 0
f 0
nop 2
1
package services
2
3
import (
4
	"context"
5
	"encoding/json"
6
	"fmt"
7
	"log"
8
	"net/http"
9
	"strings"
10
11
	"github.com/sendgrid/sendgrid-go"
12
13
	"firebase.google.com/go/auth"
14
	"github.com/NdoleStudio/httpsms/pkg/entities"
15
	"github.com/NdoleStudio/httpsms/pkg/telemetry"
16
	"github.com/davecgh/go-spew/spew"
17
	"github.com/palantir/stacktrace"
18
)
19
20
// MarketingService is handles marketing requests
21
type MarketingService struct {
22
	logger         telemetry.Logger
23
	tracer         telemetry.Tracer
24
	authClient     *auth.Client
25
	sendgridAPIKey string
26
	sendgridHost   string
27
	sendgridListID string
28
}
29
30
type sendgridContact struct {
31
	FirstName  string `json:"first_name"`
32
	LastName   string `json:"last_name"`
33
	ExternalID string `json:"external_id"`
34
	Email      string `json:"email"`
35
	ID         string `json:"id,omitempty"`
36
}
37
38
type sendgridSearchResponse struct {
39
	ContactCount int               `json:"contact_count"`
40
	Result       []sendgridContact `json:"result"`
41
}
42
43
type sendgridContactRequest struct {
44
	ListIDs  []string          `json:"list_ids"`
45
	Contacts []sendgridContact `json:"contacts"`
46
}
47
48
// NewMarketingService creates a new instance of the MarketingService
49
func NewMarketingService(
50
	logger telemetry.Logger,
51
	tracer telemetry.Tracer,
52
	authClient *auth.Client,
53
	sendgridAPIKey string,
54
	sendgridListID string,
55
) *MarketingService {
56
	return &MarketingService{
57
		logger:         logger.WithService(fmt.Sprintf("%T", &MarketingService{})),
58
		tracer:         tracer,
59
		authClient:     authClient,
60
		sendgridHost:   "https://api.sendgrid.com",
61
		sendgridAPIKey: sendgridAPIKey,
62
		sendgridListID: sendgridListID,
63
	}
64
}
65
66
// DeleteUser a user if exists in the sendgrid list
67
func (service *MarketingService) DeleteUser(ctx context.Context, userID entities.UserID) error {
68
	ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
69
	defer span.End()
70
71
	request := sendgrid.GetRequest(service.sendgridAPIKey, "/v3/marketing/contacts/search", service.sendgridHost)
72
	request.Method = http.MethodPost
73
	request.Body = []byte(fmt.Sprintf(`{"query": "external_id = '%s' AND CONTAINS(list_ids, '%s')"}`, userID, service.sendgridListID))
74
	response, err := sendgrid.API(request)
75
	if err != nil {
76
		return service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, fmt.Sprintf("cannot search for user with id [%s] in sendgrid list [%s]", userID, service.sendgridListID)))
77
	}
78
79
	data := new(sendgridSearchResponse)
80
	if err = json.Unmarshal([]byte(response.Body), data); err != nil {
81
		return service.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, fmt.Sprintf("cannot [%s] into [%T]", response.Body, data)))
82
	}
83
84
	if data.ContactCount == 0 {
85
		ctxLogger.Info(fmt.Sprintf("user with ID [%s] not found in sendgrid list [%s]", userID, service.sendgridListID))
86
		return nil
87
	}
88
89
	ctxLogger.Info(fmt.Sprintf("deleting sendgrid contact with ID [%s] for user with ID [%s]", data.Result[0].ID, userID))
90
	return service.DeleteContacts(context.Background(), []string{data.Result[0].ID})
91
}
92
93
// AddToList adds a new user on the onboarding automation.
94
func (service *MarketingService) AddToList(ctx context.Context, user *entities.User) {
95
	ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
96
	defer span.End()
97
98
	userRecord, err := service.authClient.GetUser(ctx, string(user.ID))
99
	if err != nil {
100
		msg := fmt.Sprintf("cannot get auth user with id [%s]", user.ID)
101
		ctxLogger.Error(stacktrace.Propagate(err, msg))
102
		return
103
	}
104
105
	id, err := service.addContact(sendgridContactRequest{
106
		ListIDs:  []string{service.sendgridListID},
107
		Contacts: []sendgridContact{service.toSendgridContact(userRecord)},
108
	})
109
	if err != nil {
110
		msg := fmt.Sprintf("cannot add user with id [%s] to list [%s]", user.ID, service.sendgridListID)
111
		ctxLogger.Error(stacktrace.Propagate(err, msg))
112
		return
113
	}
114
115
	ctxLogger.Info(fmt.Sprintf("user [%s] added to list [%s] with job [%s]", user.ID, service.sendgridListID, id))
116
}
117
118
// DeleteContacts deletes contacts from sendgrid
119
func (service *MarketingService) DeleteContacts(ctx context.Context, contactIDs []string) error {
120
	ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
121
	defer span.End()
122
123
	request := sendgrid.GetRequest(service.sendgridAPIKey, "/v3/marketing/contacts", service.sendgridHost)
124
	request.Method = "DELETE"
125
	request.QueryParams = map[string]string{
126
		"ids": strings.Join(contactIDs, ","),
127
	}
128
129
	response, err := sendgrid.API(request)
130
	if err != nil {
131
		return stacktrace.Propagate(err, fmt.Sprintf("cannot delete contacts in a sendgrid list [%s]", service.sendgridListID))
132
	}
133
134
	ctxLogger.Info(fmt.Sprintf("deleted contacts [%s] from sendgrid list [%s] with sendgrid response [%s]", strings.Join(contactIDs, ","), service.sendgridListID, response.Body))
135
	return nil
136
}
137
138
func (service *MarketingService) toSendgridContact(user *auth.UserRecord) sendgridContact {
139
	name := strings.TrimSpace(user.DisplayName)
140
	if name == "" {
141
		return sendgridContact{
142
			FirstName:  "",
143
			LastName:   "",
144
			ExternalID: user.UID,
145
			Email:      user.Email,
146
		}
147
	}
148
149
	parts := strings.Split(name, " ")
150
	if len(parts) == 1 {
151
		return sendgridContact{
152
			FirstName:  name,
153
			LastName:   "",
154
			ExternalID: user.UID,
155
			Email:      user.Email,
156
		}
157
	}
158
159
	return sendgridContact{
160
		FirstName:  strings.Join(parts[0:len(parts)-1], " "),
161
		LastName:   parts[len(parts)-1],
162
		ExternalID: user.UID,
163
		Email:      user.Email,
164
	}
165
}
166
167
func (service *MarketingService) addContact(contactRequest sendgridContactRequest) (string, error) {
168
	request := sendgrid.GetRequest(service.sendgridAPIKey, "/v3/marketing/contacts", "https://api.sendgrid.com")
169
	request.Method = "PUT"
170
171
	body, err := json.Marshal(contactRequest)
172
	if err != nil {
173
		log.Fatal(stacktrace.Propagate(err, fmt.Sprintf("cannot marshal [%s]", spew.Sdump(contactRequest))))
174
	}
175
176
	request.Body = body
177
	response, err := sendgrid.API(request)
178
	if err != nil {
179
		return "", stacktrace.Propagate(err, fmt.Sprintf("cannot add contact to sendgrid list [%s]", spew.Sdump(contactRequest)))
180
	}
181
	return response.Body, nil
182
}
183