handlers.*MessageHandler.BulkSend   B
last analyzed

Complexity

Conditions 8

Size

Total Lines 51
Code Lines 37

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 37
dl 0
loc 51
rs 7.1253
c 0
b 0
f 0
nop 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
package handlers
2
3
import (
4
	"fmt"
5
	"strings"
6
	"sync"
7
	"sync/atomic"
8
	"time"
9
10
	"github.com/NdoleStudio/httpsms/pkg/entities"
11
12
	"github.com/NdoleStudio/httpsms/pkg/repositories"
13
	"github.com/google/uuid"
14
15
	"github.com/NdoleStudio/httpsms/pkg/requests"
16
	"github.com/NdoleStudio/httpsms/pkg/services"
17
	"github.com/NdoleStudio/httpsms/pkg/telemetry"
18
	"github.com/NdoleStudio/httpsms/pkg/validators"
19
	"github.com/davecgh/go-spew/spew"
20
	"github.com/gofiber/fiber/v2"
21
	"github.com/palantir/stacktrace"
22
)
23
24
// MessageHandler handles message http requests.
25
type MessageHandler struct {
26
	handler
27
	logger         telemetry.Logger
28
	tracer         telemetry.Tracer
29
	billingService *services.BillingService
30
	validator      *validators.MessageHandlerValidator
31
	service        *services.MessageService
32
}
33
34
// NewMessageHandler creates a new MessageHandler
35
func NewMessageHandler(
36
	logger telemetry.Logger,
37
	tracer telemetry.Tracer,
38
	validator *validators.MessageHandlerValidator,
39
	billingService *services.BillingService,
40
	service *services.MessageService,
41
) (h *MessageHandler) {
42
	return &MessageHandler{
43
		logger:         logger.WithService(fmt.Sprintf("%T", h)),
44
		tracer:         tracer,
45
		validator:      validator,
46
		billingService: billingService,
47
		service:        service,
48
	}
49
}
50
51
// RegisterRoutes registers the routes for the MessageHandler
52
func (h *MessageHandler) RegisterRoutes(router fiber.Router, middlewares ...fiber.Handler) {
53
	router.Post("/v1/messages/send", h.computeRoute(middlewares, h.PostSend)...)
54
	router.Post("/v1/messages/bulk-send", h.computeRoute(middlewares, h.BulkSend)...)
55
	router.Get("/v1/messages", h.computeRoute(middlewares, h.Index)...)
56
	router.Get("/v1/messages/search", h.computeRoute(middlewares, h.Search)...)
57
	router.Delete("/v1/messages/:messageID", h.computeRoute(middlewares, h.Delete)...)
58
}
59
60
// RegisterPhoneAPIKeyRoutes registers the routes for the MessageHandler
61
func (h *MessageHandler) RegisterPhoneAPIKeyRoutes(router fiber.Router, middlewares ...fiber.Handler) {
62
	router.Post("/v1/messages/:messageID/events", h.computeRoute(middlewares, h.PostEvent)...)
63
	router.Post("/v1/messages/receive", h.computeRoute(middlewares, h.PostReceive)...)
64
	router.Post("/v1/messages/calls/missed", h.computeRoute(middlewares, h.PostCallMissed)...)
65
	router.Get("/v1/messages/outstanding", h.computeRoute(middlewares, h.GetOutstanding)...)
66
}
67
68
// PostSend a new entities.Message
69
// @Summary      Send a new SMS message
70
// @Description  Add a new SMS message to be sent by the android phone
71
// @Security	 ApiKeyAuth
72
// @Tags         Messages
73
// @Accept       json
74
// @Produce      json
75
// @Param        payload   body requests.MessageSend  true  "PostSend message request payload"
76
// @Success      200  {object}  responses.MessageResponse
77
// @Failure      400  {object}  responses.BadRequest
78
// @Failure 	 401  {object}	responses.Unauthorized
79
// @Failure      422  {object}  responses.UnprocessableEntity
80
// @Failure      500  {object}  responses.InternalServerError
81
// @Router       /messages/send [post]
82
func (h *MessageHandler) PostSend(c *fiber.Ctx) error {
83
	ctx, span := h.tracer.StartFromFiberCtx(c)
84
	defer span.End()
85
86
	ctxLogger := h.tracer.CtxLogger(h.logger, span)
87
88
	var request requests.MessageSend
89
	if err := c.BodyParser(&request); err != nil {
90
		msg := fmt.Sprintf("cannot marshall [%s] into %T", c.Body(), request)
91
		ctxLogger.Warn(stacktrace.Propagate(err, msg))
92
		return h.responseBadRequest(c, err)
93
	}
94
95
	if errors := h.validator.ValidateMessageSend(ctx, h.userIDFomContext(c), request.Sanitize()); len(errors) != 0 {
96
		msg := fmt.Sprintf("validation errors [%s], while sending payload [%s]", spew.Sdump(errors), c.Body())
97
		ctxLogger.Warn(stacktrace.NewError(msg))
98
		return h.responseUnprocessableEntity(c, errors, "validation errors while sending message")
99
	}
100
101
	if msg := h.billingService.IsEntitled(ctx, h.userIDFomContext(c)); msg != nil {
102
		ctxLogger.Warn(stacktrace.NewError(fmt.Sprintf("user with ID [%s] can't send a message", h.userIDFomContext(c))))
103
		return h.responsePaymentRequired(c, *msg)
104
	}
105
106
	message, err := h.service.SendMessage(ctx, request.ToMessageSendParams(h.userIDFomContext(c), c.OriginalURL()))
107
	if err != nil {
108
		msg := fmt.Sprintf("cannot send message with paylod [%s]", c.Body())
109
		ctxLogger.Error(stacktrace.Propagate(err, msg))
110
		return h.responseInternalServerError(c)
111
	}
112
113
	return h.responseOK(c, "message added to queue", message)
114
}
115
116
// BulkSend a bulk entities.Message
117
// @Summary      Send bulk SMS messages
118
// @Description  Add bulk SMS messages to be sent by the android phone
119
// @Security	 ApiKeyAuth
120
// @Tags         Messages
121
// @Accept       json
122
// @Produce      json
123
// @Param        payload   body requests.MessageBulkSend  true  "Bulk send message request payload"
124
// @Success      200  {object}  []responses.MessagesResponse
125
// @Failure      400  {object}  responses.BadRequest
126
// @Failure 	 401  {object}	responses.Unauthorized
127
// @Failure      422  {object}  responses.UnprocessableEntity
128
// @Failure      500  {object}  responses.InternalServerError
129
// @Router       /messages/bulk-send [post]
130
func (h *MessageHandler) BulkSend(c *fiber.Ctx) error {
131
	ctx, span := h.tracer.StartFromFiberCtx(c)
132
	defer span.End()
133
134
	ctxLogger := h.tracer.CtxLogger(h.logger, span)
135
136
	var request requests.MessageBulkSend
137
	if err := c.BodyParser(&request); err != nil {
138
		msg := fmt.Sprintf("cannot marshall [%s] into %T", c.Body(), request)
139
		ctxLogger.Warn(stacktrace.Propagate(err, msg))
140
		return h.responseBadRequest(c, err)
141
	}
142
143
	if errors := h.validator.ValidateMessageBulkSend(ctx, h.userIDFomContext(c), request.Sanitize()); len(errors) != 0 {
144
		msg := fmt.Sprintf("validation errors [%s], while sending payload [%s]", spew.Sdump(errors), c.Body())
145
		ctxLogger.Warn(stacktrace.NewError(msg))
146
		return h.responseUnprocessableEntity(c, errors, "validation errors while sending messages")
147
	}
148
149
	if msg := h.billingService.IsEntitledWithCount(ctx, h.userIDFomContext(c), uint(len(request.To))); msg != nil {
150
		ctxLogger.Warn(stacktrace.NewError(fmt.Sprintf("user with ID [%s] is not entitled to send [%d] messages", h.userIDFomContext(c), len(request.To))))
151
		return h.responsePaymentRequired(c, *msg)
152
	}
153
154
	wg := sync.WaitGroup{}
155
	params := request.ToMessageSendParams(h.userIDFomContext(c), c.OriginalURL())
156
	responses := make([]*entities.Message, len(params))
157
	count := atomic.Int64{}
158
159
	for index, message := range params {
160
		wg.Add(1)
161
		go func(message services.MessageSendParams, index int) {
162
			count.Add(1)
163
			if message.SendAt == nil {
164
				sentAt := time.Now().UTC().Add(time.Duration(index) * time.Second)
165
				message.SendAt = &sentAt
166
			}
167
168
			response, err := h.service.SendMessage(ctx, message)
169
			if err != nil {
170
				count.Add(-1)
171
				msg := fmt.Sprintf("cannot send message with paylod [%s] at index [%d]", spew.Sdump(message), index)
172
				ctxLogger.Error(stacktrace.Propagate(err, msg))
173
			}
174
			responses[index] = response
175
			wg.Done()
176
		}(message, index)
177
	}
178
179
	wg.Wait()
180
	return h.responseOK(c, fmt.Sprintf("%d out of %d messages processed successfully", count.Load(), len(responses)), responses)
181
}
182
183
// GetOutstanding returns an entities.Message which is still to be sent by the mobile phone
184
// @Summary      Get an outstanding message
185
// @Description  Get an outstanding message to be sent by an android phone
186
// @Security	 ApiKeyAuth
187
// @Tags         Messages
188
// @Accept       json
189
// @Produce      json
190
// @Param        message_id	query  		string  						true "The ID of the message" default(32343a19-da5e-4b1b-a767-3298a73703cb)
191
// @Success      200 		{object}	responses.MessageResponse
192
// @Failure      400		{object}	responses.BadRequest
193
// @Failure 	 401    	{object}	responses.Unauthorized
194
// @Failure      422		{object}	responses.UnprocessableEntity
195
// @Failure      500		{object}	responses.InternalServerError
196
// @Router       /messages/outstanding [get]
197
func (h *MessageHandler) GetOutstanding(c *fiber.Ctx) error {
198
	ctx, span := h.tracer.StartFromFiberCtx(c)
199
	defer span.End()
200
201
	timestamp := time.Now().UTC()
202
	ctxLogger := h.tracer.CtxLogger(h.logger, span)
203
204
	var request requests.MessageOutstanding
205
	if err := c.QueryParser(&request); err != nil {
206
		msg := fmt.Sprintf("cannot marshall params [%s] into %T", c.OriginalURL(), request)
207
		ctxLogger.Warn(stacktrace.Propagate(err, msg))
208
		return h.responseBadRequest(c, err)
209
	}
210
211
	if errors := h.validator.ValidateMessageOutstanding(ctx, request.Sanitize()); len(errors) != 0 {
212
		msg := fmt.Sprintf("validation errors [%s], while fetching outstanding messages [%s]", spew.Sdump(errors), c.OriginalURL())
213
		ctxLogger.Warn(stacktrace.NewError(msg))
214
		return h.responseUnprocessableEntity(c, errors, "validation errors while fetching outstanding messages")
215
	}
216
217
	message, err := h.service.GetOutstanding(ctx, request.ToGetOutstandingParams(c.Path(), h.userFromContext(c), timestamp))
218
	if stacktrace.GetCode(err) == repositories.ErrCodeNotFound {
219
		msg := fmt.Sprintf("Cannot find outstanding message with ID [%s]", request.MessageID)
220
		ctxLogger.Warn(stacktrace.Propagate(err, msg))
221
		return h.responseNotFound(c, msg)
222
	}
223
224
	if err != nil {
225
		msg := fmt.Sprintf("cannot get outstanding messgage with ID [%s]", request.MessageID)
226
		ctxLogger.Error(stacktrace.Propagate(err, msg))
227
		return h.responseInternalServerError(c)
228
	}
229
230
	return h.responseOK(c, "outstanding message fetched successfully", message)
231
}
232
233
// Index returns messages sent between 2 phone numbers
234
// @Summary      Get messages which are sent between 2 phone numbers
235
// @Description  Get list of messages which are sent between 2 phone numbers. It will be sorted by timestamp in descending order.
236
// @Security	 ApiKeyAuth
237
// @Tags         Messages
238
// @Accept       json
239
// @Produce      json
240
// @Param        owner		query  string  	true 	"the owner's phone number" 			default(+18005550199)
241
// @Param        contact	query  string  	true 	"the contact's phone number" 		default(+18005550100)
242
// @Param        skip		query  int  	false	"number of messages to skip"		minimum(0)
243
// @Param        query		query  string  	false 	"filter messages containing query"
244
// @Param        limit		query  int  	false	"number of messages to return"		minimum(1)	maximum(20)
245
// @Success      200 		{object}	responses.MessagesResponse
246
// @Failure      400		{object}	responses.BadRequest
247
// @Failure 	 401    	{object}	responses.Unauthorized
248
// @Failure      422		{object}	responses.UnprocessableEntity
249
// @Failure      500		{object}	responses.InternalServerError
250
// @Router       /messages [get]
251
func (h *MessageHandler) Index(c *fiber.Ctx) error {
252
	ctx, span := h.tracer.StartFromFiberCtx(c)
253
	defer span.End()
254
255
	ctxLogger := h.tracer.CtxLogger(h.logger, span)
256
257
	var request requests.MessageIndex
258
	if err := c.QueryParser(&request); err != nil {
259
		msg := fmt.Sprintf("cannot marshall params [%s] into %T", c.OriginalURL(), request)
260
		ctxLogger.Warn(stacktrace.Propagate(err, msg))
261
		return h.responseBadRequest(c, err)
262
	}
263
264
	if errors := h.validator.ValidateMessageIndex(ctx, request.Sanitize()); len(errors) != 0 {
265
		msg := fmt.Sprintf("validation errors [%s], while fetching messages [%+#v]", spew.Sdump(errors), request)
266
		ctxLogger.Warn(stacktrace.NewError(msg))
267
		return h.responseUnprocessableEntity(c, errors, "validation errors while fetching messages")
268
	}
269
270
	messages, err := h.service.GetMessages(ctx, request.ToGetParams(h.userIDFomContext(c)))
271
	if err != nil {
272
		msg := fmt.Sprintf("cannot get messgaes with params [%+#v]", request)
273
		ctxLogger.Error(stacktrace.Propagate(err, msg))
274
		return h.responseInternalServerError(c)
275
	}
276
277
	return h.responseOK(c, fmt.Sprintf("fetched %d %s", len(*messages), h.pluralize("message", len(*messages))), messages)
278
}
279
280
// PostEvent registers an event on a message
281
// @Summary      Upsert an event for a message on the mobile phone
282
// @Description  Use this endpoint to send events for a message when it is failed, sent or delivered by the mobile phone.
283
// @Security	 ApiKeyAuth
284
// @Tags         Messages
285
// @Accept       json
286
// @Produce      json
287
// @Param 		 messageID 	path		string 							true 	"ID of the message" 			default(32343a19-da5e-4b1b-a767-3298a73703ca)
288
// @Param        payload   	body 		requests.MessageEvent  			true 	"Payload of the event emitted."
289
// @Success      200  		{object} 	responses.MessageResponse
290
// @Failure      400  		{object}  	responses.BadRequest
291
// @Failure 	 401    	{object}	responses.Unauthorized
292
// @Failure 	 404		{object}	responses.NotFound
293
// @Failure      422  		{object} 	responses.UnprocessableEntity
294
// @Failure      500  		{object}  	responses.InternalServerError
295
// @Router       /messages/{messageID}/events [post]
296
func (h *MessageHandler) PostEvent(c *fiber.Ctx) error {
297
	ctx, span := h.tracer.StartFromFiberCtx(c)
298
	defer span.End()
299
300
	ctxLogger := h.tracer.CtxLogger(h.logger, span)
301
302
	var request requests.MessageEvent
303
	if err := c.BodyParser(&request); err != nil {
304
		msg := fmt.Sprintf("cannot marshall [%s] into %T", c.Body(), request)
305
		ctxLogger.Warn(stacktrace.Propagate(err, msg))
306
		return h.responseBadRequest(c, err)
307
	}
308
309
	request.MessageID = c.Params("messageID")
310
	if strings.Contains(request.MessageID, ".") {
311
		return h.responseNoContent(c, "duplicate send event received.")
312
	}
313
314
	if errors := h.validator.ValidateMessageEvent(ctx, request.Sanitize()); len(errors) != 0 {
315
		msg := fmt.Sprintf("validation errors [%s], while storing event [%s] for message [%s]", spew.Sdump(errors), c.Body(), request.MessageID)
316
		ctxLogger.Warn(stacktrace.NewError(msg))
317
		return h.responseUnprocessableEntity(c, errors, "validation errors while storing event")
318
	}
319
320
	message, err := h.service.GetMessage(ctx, h.userIDFomContext(c), uuid.MustParse(request.MessageID))
321
	if err != nil && stacktrace.GetCode(err) == repositories.ErrCodeNotFound {
322
		return h.responseNotFound(c, fmt.Sprintf("cannot find message with ID [%s]", request.MessageID))
323
	}
324
325
	if err != nil {
326
		msg := fmt.Sprintf("cannot find message with id [%s]", request.MessageID)
327
		ctxLogger.Error(h.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg)))
328
		return h.responseInternalServerError(c)
329
	}
330
331
	if !h.authorizePhoneAPIKey(c, message.Owner) {
332
		ctxLogger.Warn(stacktrace.NewError(fmt.Sprintf("user with ID [%s] is not authorized to send event for message with ID [%s]", h.userIDFomContext(c), request.MessageID)))
333
		return h.responsePhoneAPIKeyUnauthorized(c, message.Owner, h.userFromContext(c))
334
	}
335
336
	message, err = h.service.StoreEvent(ctx, message, request.ToMessageStoreEventParams(c.OriginalURL()))
337
	if err != nil {
338
		msg := fmt.Sprintf("cannot store event for message [%s] with paylod [%s]", request.MessageID, c.Body())
339
		ctxLogger.Error(h.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg)))
340
		return h.responseInternalServerError(c)
341
	}
342
343
	return h.responseOK(c, "message event stored successfully", message)
344
}
345
346
// PostReceive receives a new entities.Message
347
// @Summary      Receive a new SMS message from a mobile phone
348
// @Description  Add a new message received from a mobile phone
349
// @Security	 ApiKeyAuth
350
// @Tags         Messages
351
// @Accept       json
352
// @Produce      json
353
// @Param        payload   body requests.MessageReceive  true  "Received message request payload"
354
// @Success      200  {object}  responses.MessageResponse
355
// @Failure      400  {object}  responses.BadRequest
356
// @Failure      422  {object}  responses.UnprocessableEntity
357
// @Failure      500  {object}  responses.InternalServerError
358
// @Router       /messages/receive [post]
359
func (h *MessageHandler) PostReceive(c *fiber.Ctx) error {
360
	ctx, span := h.tracer.StartFromFiberCtx(c)
361
	defer span.End()
362
363
	ctxLogger := h.tracer.CtxLogger(h.logger, span)
364
365
	var request requests.MessageReceive
366
	if err := c.BodyParser(&request); err != nil {
367
		msg := fmt.Sprintf("cannot marshall [%s] into %T", c.Body(), request)
368
		ctxLogger.Warn(stacktrace.Propagate(err, msg))
369
		return h.responseBadRequest(c, err)
370
	}
371
372
	if errors := h.validator.ValidateMessageReceive(ctx, request.Sanitize()); len(errors) != 0 {
373
		msg := fmt.Sprintf("validation errors [%s], while sending payload [%s]", spew.Sdump(errors), c.Body())
374
		ctxLogger.Warn(stacktrace.NewError(msg))
375
		return h.responseUnprocessableEntity(c, errors, "validation errors while receiving message")
376
	}
377
378
	if msg := h.billingService.IsEntitled(ctx, h.userIDFomContext(c)); msg != nil {
379
		ctxLogger.Warn(stacktrace.NewError(fmt.Sprintf("user with ID [%s] can't receive a message becasuse they have exceeded the limit", h.userIDFomContext(c))))
380
		return h.responsePaymentRequired(c, *msg)
381
	}
382
383
	if !h.authorizePhoneAPIKey(c, request.To) {
384
		ctxLogger.Warn(stacktrace.NewError(fmt.Sprintf("user with ID [%s] is not authorized to receive message to phone number [%s]", h.userIDFomContext(c), request.To)))
385
		return h.responsePhoneAPIKeyUnauthorized(c, request.To, h.userFromContext(c))
386
	}
387
388
	message, err := h.service.ReceiveMessage(ctx, request.ToMessageReceiveParams(h.userIDFomContext(c), c.OriginalURL()))
389
	if err != nil {
390
		msg := fmt.Sprintf("cannot receive message with paylod [%s]", c.Body())
391
		ctxLogger.Error(stacktrace.Propagate(err, msg))
392
		return h.responseInternalServerError(c)
393
	}
394
395
	return h.responseOK(c, "message received successfully", message)
396
}
397
398
// Delete a message
399
// @Summary      Delete a message from the database.
400
// @Description  Delete a message from the database and removes the message content from the list of threads.
401
// @Security	 ApiKeyAuth
402
// @Tags         Messages
403
// @Accept       json
404
// @Produce      json
405
// @Param 		 messageID 	path		string 							true 	"ID of the message" 			default(32343a19-da5e-4b1b-a767-3298a73703ca)
406
// @Success      204  		{object} 	responses.NoContent
407
// @Failure      400  		{object}  	responses.BadRequest
408
// @Failure 	 401    	{object}	responses.Unauthorized
409
// @Failure 	 404		{object}	responses.NotFound
410
// @Failure      422  		{object} 	responses.UnprocessableEntity
411
// @Failure      500  		{object}  	responses.InternalServerError
412
// @Router       /messages/{messageID} [delete]
413
func (h *MessageHandler) Delete(c *fiber.Ctx) error {
414
	ctx, span := h.tracer.StartFromFiberCtx(c)
415
	defer span.End()
416
417
	ctxLogger := h.tracer.CtxLogger(h.logger, span)
418
419
	messageID := c.Params("messageID")
420
	if errors := h.validator.ValidateUUID(messageID, "messageID"); len(errors) != 0 {
421
		msg := fmt.Sprintf("validation errors [%s], while deleting a message with ID [%s]", spew.Sdump(errors), messageID)
422
		ctxLogger.Warn(stacktrace.NewError(msg))
423
		return h.responseUnprocessableEntity(c, errors, "validation errors while storing event")
424
	}
425
426
	message, err := h.service.GetMessage(ctx, h.userIDFomContext(c), uuid.MustParse(messageID))
427
	if stacktrace.GetCode(err) == repositories.ErrCodeNotFound {
428
		return h.responseNotFound(c, fmt.Sprintf("cannot find message with ID [%s]", messageID))
429
	}
430
431
	if err != nil {
432
		msg := fmt.Sprintf("cannot find message with id [%s]", messageID)
433
		ctxLogger.Error(h.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg)))
434
		return h.responseInternalServerError(c)
435
	}
436
437
	if err = h.service.DeleteMessage(ctx, c.OriginalURL(), message); err != nil {
438
		msg := fmt.Sprintf("cannot delete message with ID [%s] for user with ID [%s]", messageID, message.UserID)
439
		ctxLogger.Error(h.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg)))
440
		return h.responseInternalServerError(c)
441
	}
442
443
	return h.responseNoContent(c, "message deleted successfully")
444
}
445
446
// PostCallMissed registers a missed phone call
447
// @Summary      Register a missed call event on the mobile phone
448
// @Description  This endpoint is called by the httpSMS android app to register a missed call event on the mobile phone.
449
// @Security	 ApiKeyAuth
450
// @Tags         Messages
451
// @Accept       json
452
// @Produce      json
453
// @Param        payload   	body 		requests.MessageCallMissed  	true	"Payload of the missed call event."
454
// @Success      200  		{object} 	responses.MessageResponse
455
// @Failure      400  		{object}  	responses.BadRequest
456
// @Failure 	 401    	{object}	responses.Unauthorized
457
// @Failure 	 404		{object}	responses.NotFound
458
// @Failure      422  		{object} 	responses.UnprocessableEntity
459
// @Failure      500  		{object}  	responses.InternalServerError
460
// @Router       /messages/calls/missed [post]
461
func (h *MessageHandler) PostCallMissed(c *fiber.Ctx) error {
462
	ctx, span := h.tracer.StartFromFiberCtx(c)
463
	defer span.End()
464
465
	ctxLogger := h.tracer.CtxLogger(h.logger, span)
466
467
	var request requests.MessageCallMissed
468
	if err := c.BodyParser(&request); err != nil {
469
		msg := fmt.Sprintf("cannot marshall [%s] into %T", c.Body(), request)
470
		ctxLogger.Warn(stacktrace.Propagate(err, msg))
471
		return h.responseBadRequest(c, err)
472
	}
473
474
	if errors := h.validator.ValidateCallMissed(ctx, request.Sanitize()); len(errors) != 0 {
475
		msg := fmt.Sprintf("validation errors [%s], for missed call event [%s]", spew.Sdump(errors), c.Body())
476
		ctxLogger.Warn(stacktrace.NewError(msg))
477
		return h.responseUnprocessableEntity(c, errors, "validation errors while storing missed call event")
478
	}
479
480
	if !h.authorizePhoneAPIKey(c, request.To) {
481
		ctxLogger.Warn(stacktrace.NewError(fmt.Sprintf("user with ID [%s] is not authorized to register missed phone call for phone number [%s]", h.userIDFomContext(c), request.To)))
482
		return h.responsePhoneAPIKeyUnauthorized(c, request.To, h.userFromContext(c))
483
	}
484
485
	message, err := h.service.RegisterMissedCall(ctx, request.ToCallMissedParams(h.userIDFomContext(c), c.OriginalURL()))
486
	if err != nil {
487
		msg := fmt.Sprintf("cannot store missed call event for user [%s] with paylod [%s]", h.userIDFomContext(c), c.Body())
488
		ctxLogger.Error(h.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, msg)))
489
		return h.responseInternalServerError(c)
490
	}
491
492
	return h.responseOK(c, "missed call event stored successfully", message)
493
}
494
495
// Search returns a filtered list of messages of a user
496
// @Summary      Search all messages of a user
497
// @Description  This returns the list of all messages based on the filter criteria including missed calls
498
// @Security	 ApiKeyAuth
499
// @Tags         Messages
500
// @Accept       json
501
// @Produce      json
502
// @Param        token    	header string   true   	"Cloudflare turnstile token https://www.cloudflare.com/en-gb/application-services/products/turnstile/"
503
// @Param        owners		query  string  	true 	"the owner's phone numbers" 		default(+18005550199,+18005550100)
504
// @Param        skip		query  int  	false	"number of messages to skip"		minimum(0)
505
// @Param        query		query  string  	false 	"filter messages containing query"
506
// @Param        limit		query  int  	false	"number of messages to return"		minimum(1)	maximum(200)
507
// @Success      200 		{object}	responses.MessagesResponse
508
// @Failure      400		{object}	responses.BadRequest
509
// @Failure 	 401    	{object}	responses.Unauthorized
510
// @Failure      422		{object}	responses.UnprocessableEntity
511
// @Failure      500		{object}	responses.InternalServerError
512
// @Router       /messages/search [get]
513
func (h *MessageHandler) Search(c *fiber.Ctx) error {
514
	ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger)
515
	defer span.End()
516
517
	var request requests.MessageSearch
518
	if err := c.QueryParser(&request); err != nil {
519
		msg := fmt.Sprintf("cannot marshall params in [%s] into [%T]", c.OriginalURL(), request)
520
		ctxLogger.Warn(stacktrace.Propagate(err, msg))
521
		return h.responseBadRequest(c, err)
522
	}
523
524
	request.IPAddress = c.IP()
525
	request.Token = c.Get("token")
526
527
	if errors := h.validator.ValidateMessageSearch(ctx, request.Sanitize()); len(errors) != 0 {
528
		msg := fmt.Sprintf("validation errors [%s], while searching messages [%+#v]", spew.Sdump(errors), request)
529
		ctxLogger.Warn(stacktrace.NewError(msg))
530
		return h.responseUnprocessableEntity(c, errors, "validation errors while searching messages")
531
	}
532
533
	messages, err := h.service.SearchMessages(ctx, request.ToSearchParams(h.userIDFomContext(c)))
534
	if err != nil {
535
		msg := fmt.Sprintf("cannot search messages with params [%+#v]", request)
536
		ctxLogger.Error(stacktrace.Propagate(err, msg))
537
		return h.responseInternalServerError(c)
538
	}
539
540
	return h.responseOK(c, fmt.Sprintf("found %d %s", len(messages), h.pluralize("message", len(messages))), messages)
541
}
542