Passed
Push — main ( 203737...4cb1c0 )
by Acho
01:56
created

handlers.*MessageHandler.Search   A

Complexity

Conditions 4

Size

Total Lines 27
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

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