Passed
Push — main ( b58c79...d9f5c3 )
by Acho
01:41
created

ageBulkSend   A

Complexity

Conditions 4

Size

Total Lines 43
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 29
nop 3
dl 0
loc 43
rs 9.184
c 0
b 0
f 0
1
package validators
2
3
import (
4
	"context"
5
	"fmt"
6
	"net/url"
7
	"strings"
8
9
	"github.com/NdoleStudio/httpsms/pkg/repositories"
10
	"github.com/NdoleStudio/httpsms/pkg/services"
11
	"github.com/palantir/stacktrace"
12
13
	"github.com/NdoleStudio/httpsms/pkg/entities"
14
15
	"github.com/NdoleStudio/httpsms/pkg/requests"
16
	"github.com/NdoleStudio/httpsms/pkg/telemetry"
17
	"github.com/thedevsaddam/govalidator"
18
)
19
20
// MessageHandlerValidator validates models used in handlers.MessageHandler
21
type MessageHandlerValidator struct {
22
	validator
23
	logger         telemetry.Logger
24
	tracer         telemetry.Tracer
25
	phoneService   *services.PhoneService
26
	tokenValidator *TurnstileTokenValidator
27
}
28
29
// NewMessageHandlerValidator creates a new handlers.MessageHandler validator
30
func NewMessageHandlerValidator(
31
	logger telemetry.Logger,
32
	tracer telemetry.Tracer,
33
	phoneService *services.PhoneService,
34
	tokenValidator *TurnstileTokenValidator,
35
) (v *MessageHandlerValidator) {
36
	return &MessageHandlerValidator{
37
		logger:         logger.WithService(fmt.Sprintf("%T", v)),
38
		tracer:         tracer,
39
		phoneService:   phoneService,
40
		tokenValidator: tokenValidator,
41
	}
42
}
43
44
// ValidateMessageReceive validates the requests.MessageReceive request
45
func (validator MessageHandlerValidator) ValidateMessageReceive(_ context.Context, request requests.MessageReceive) url.Values {
46
	v := govalidator.New(govalidator.Options{
47
		Data: &request,
48
		Rules: govalidator.MapData{
49
			"to": []string{
50
				"required",
51
				phoneNumberRule,
52
			},
53
			"from": []string{
54
				"required",
55
			},
56
			"content": []string{
57
				"required",
58
				"min:1",
59
				"max:2048",
60
			},
61
			"sim": []string{
62
				"required",
63
				"in:" + strings.Join([]string{
64
					string(entities.SIM1),
65
					string(entities.SIM2),
66
				}, ","),
67
			},
68
		},
69
	})
70
71
	return v.ValidateStruct()
72
}
73
74
// ValidateMessageSend validates the requests.MessageSend request
75
func (validator MessageHandlerValidator) ValidateMessageSend(ctx context.Context, userID entities.UserID, request requests.MessageSend) url.Values {
76
	ctx, span := validator.tracer.Start(ctx)
77
	defer span.End()
78
79
	ctxLogger := validator.tracer.CtxLogger(validator.logger, span)
80
81
	v := govalidator.New(govalidator.Options{
82
		Data: &request,
83
		Rules: govalidator.MapData{
84
			"to": []string{
85
				"required",
86
				contactPhoneNumberRule,
87
			},
88
			"request_id": []string{
89
				"max:255",
90
			},
91
			"from": []string{
92
				"required",
93
				phoneNumberRule,
94
			},
95
			"content": []string{
96
				"required",
97
				"min:1",
98
				"max:2048",
99
			},
100
		},
101
	})
102
103
	result := v.ValidateStruct()
104
	if len(result) != 0 {
105
		return result
106
	}
107
108
	_, err := validator.phoneService.Load(ctx, userID, request.From)
109
	if stacktrace.GetCode(err) == repositories.ErrCodeNotFound {
110
		result.Add("from", fmt.Sprintf("no phone found with with 'from' number [%s]. install the android app on your phone to start sending messages", request.From))
111
	}
112
113
	if err != nil {
114
		ctxLogger.Error(validator.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, fmt.Sprintf("could not load phone for user [%s] and phone [%s]", userID, request.From))))
115
		result.Add("from", fmt.Sprintf("could not validate 'from' number [%s], please try again later", request.From))
116
	}
117
118
	return result
119
}
120
121
// ValidateMessageBulkSend validates the requests.MessageBulkSend request
122
func (validator MessageHandlerValidator) ValidateMessageBulkSend(ctx context.Context, userID entities.UserID, request requests.MessageBulkSend) url.Values {
123
	ctx, span := validator.tracer.Start(ctx)
124
	defer span.End()
125
126
	ctxLogger := validator.tracer.CtxLogger(validator.logger, span)
127
128
	v := govalidator.New(govalidator.Options{
129
		Data: &request,
130
		Rules: govalidator.MapData{
131
			"to": []string{
132
				"required",
133
				"max:1000",
134
				"min:1",
135
				multipleContactPhoneNumberRule,
136
			},
137
			"from": []string{
138
				"required",
139
				phoneNumberRule,
140
			},
141
			"content": []string{
142
				"required",
143
				"min:1",
144
				"max:1024",
145
			},
146
		},
147
	})
148
149
	result := v.ValidateStruct()
150
	if len(result) != 0 {
151
		return result
152
	}
153
154
	_, err := validator.phoneService.Load(ctx, userID, request.From)
155
	if stacktrace.GetCode(err) == repositories.ErrCodeNotFound {
156
		result.Add("from", fmt.Sprintf("no phone found with with 'from' number [%s]. Install the android app on your phone to start sending messages", request.From))
157
	}
158
159
	if err != nil {
160
		ctxLogger.Error(validator.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, fmt.Sprintf("could not load phone for user [%s] and phone [%s]", userID, request.From))))
161
		result.Add("from", fmt.Sprintf("could not validate 'from' number [%s], please try again later", request.From))
162
	}
163
164
	return result
165
}
166
167
// ValidateMessageOutstanding validates the requests.MessageOutstanding request
168
func (validator MessageHandlerValidator) ValidateMessageOutstanding(_ context.Context, request requests.MessageOutstanding) url.Values {
169
	v := govalidator.New(govalidator.Options{
170
		Data: &request,
171
		Rules: govalidator.MapData{
172
			"message_id": []string{
173
				"required",
174
				"uuid",
175
			},
176
		},
177
	})
178
	return v.ValidateStruct()
179
}
180
181
// ValidateMessageIndex validates the requests.MessageIndex request
182
func (validator MessageHandlerValidator) ValidateMessageIndex(_ context.Context, request requests.MessageIndex) url.Values {
183
	v := govalidator.New(govalidator.Options{
184
		Data: &request,
185
		Rules: govalidator.MapData{
186
			"limit": []string{
187
				"required",
188
				"numeric",
189
				"min:1",
190
				"max:20",
191
			},
192
			"skip": []string{
193
				"required",
194
				"numeric",
195
				"min:0",
196
			},
197
			"contact": []string{
198
				"required",
199
				"min:1",
200
			},
201
			"query": []string{
202
				"max:100",
203
			},
204
			"owner": []string{
205
				"required",
206
				phoneNumberRule,
207
			},
208
		},
209
	})
210
	return v.ValidateStruct()
211
}
212
213
// ValidateMessageSearch validates the requests.MessageSearch request
214
func (validator MessageHandlerValidator) ValidateMessageSearch(ctx context.Context, request requests.MessageSearch) url.Values {
215
	v := govalidator.New(govalidator.Options{
216
		Data: &request,
217
		Rules: govalidator.MapData{
218
			"owners": []string{
219
				multipleContactPhoneNumberRule,
220
			},
221
			"types": []string{
222
				multipleInRule + ":" + strings.Join([]string{
223
					entities.MessageTypeCallMissed,
224
					entities.MessageTypeMobileOriginated,
225
					entities.MessageTypeMobileTerminated,
226
				}, ","),
227
			},
228
			"statuses": []string{
229
				multipleInRule + ":" + strings.Join([]string{
230
					entities.MessageStatusPending,
231
					entities.MessageStatusSent,
232
					entities.MessageStatusDelivered,
233
					entities.MessageStatusFailed,
234
					entities.MessageStatusExpired,
235
					entities.MessageStatusReceived,
236
				}, ","),
237
			},
238
			"sort_by": []string{
239
				"in:" + strings.Join([]string{
240
					"created_at",
241
					"owner",
242
					"contact",
243
					"type",
244
					"status",
245
				}, ","),
246
			},
247
			"limit": []string{
248
				"required",
249
				"numeric",
250
				"min:1",
251
				"max:200",
252
			},
253
			"skip": []string{
254
				"required",
255
				"numeric",
256
				"min:0",
257
			},
258
			"query": []string{
259
				"max:20",
260
			},
261
			"token": []string{
262
				"required",
263
			},
264
		},
265
	})
266
267
	errors := v.ValidateStruct()
268
	if len(errors) > 0 {
269
		return errors
270
	}
271
272
	if !validator.tokenValidator.ValidateToken(ctx, request.IPAddress, request.Token) {
273
		errors.Add("token", "The captcha token from turnstile is invalid")
274
	}
275
276
	return errors
277
}
278
279
// ValidateMessageEvent validates the requests.MessageEvent request
280
func (validator MessageHandlerValidator) ValidateMessageEvent(_ context.Context, request requests.MessageEvent) url.Values {
281
	v := govalidator.New(govalidator.Options{
282
		Data: &request,
283
		Rules: govalidator.MapData{
284
			"event_name": []string{
285
				"required",
286
				"in:" + strings.Join([]string{
287
					string(entities.MessageEventNameSent),
288
					string(entities.MessageEventNameFailed),
289
					string(entities.MessageEventNameDelivered),
290
				}, ","),
291
			},
292
			"messageID": []string{
293
				"required",
294
				"uuid",
295
			},
296
		},
297
	})
298
	return v.ValidateStruct()
299
}
300
301
// ValidateCallMissed validates the requests.MessageCallMissed request
302
func (validator MessageHandlerValidator) ValidateCallMissed(_ context.Context, request requests.MessageCallMissed) url.Values {
303
	v := govalidator.New(govalidator.Options{
304
		Data: &request,
305
		Rules: govalidator.MapData{
306
			"to": []string{
307
				"required",
308
				phoneNumberRule,
309
			},
310
			"from": []string{
311
				"required",
312
			},
313
			"sim": []string{
314
				"required",
315
				"in:" + strings.Join([]string{
316
					string(entities.SIM1),
317
					string(entities.SIM2),
318
				}, ","),
319
			},
320
		},
321
	})
322
323
	return v.ValidateStruct()
324
}
325