Passed
Push — main ( f24a32...53c867 )
by Acho
03:11
created

handlers.*MessageHandler.PostCallMissed   B

Complexity

Conditions 5

Size

Total Lines 32
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

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