Passed
Push — main ( 3fad1b...5bf569 )
by Acho
01:38
created

ageSend   B

Complexity

Conditions 6

Size

Total Lines 48
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

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