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

handlers.*PhoneHandler.UpsertFCMToken   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
6
	"github.com/NdoleStudio/httpsms/pkg/requests"
7
	"github.com/NdoleStudio/httpsms/pkg/validators"
8
	"github.com/davecgh/go-spew/spew"
9
10
	"github.com/NdoleStudio/httpsms/pkg/services"
11
	"github.com/NdoleStudio/httpsms/pkg/telemetry"
12
	"github.com/gofiber/fiber/v2"
13
	"github.com/palantir/stacktrace"
14
)
15
16
// PhoneHandler handles phone http requests.
17
type PhoneHandler struct {
18
	handler
19
	logger    telemetry.Logger
20
	tracer    telemetry.Tracer
21
	service   *services.PhoneService
22
	validator *validators.PhoneHandlerValidator
23
}
24
25
// NewPhoneHandler creates a new PhoneHandler
26
func NewPhoneHandler(
27
	logger telemetry.Logger,
28
	tracer telemetry.Tracer,
29
	service *services.PhoneService,
30
	validator *validators.PhoneHandlerValidator,
31
) (h *PhoneHandler) {
32
	return &PhoneHandler{
33
		logger:    logger.WithService(fmt.Sprintf("%T", h)),
34
		tracer:    tracer,
35
		validator: validator,
36
		service:   service,
37
	}
38
}
39
40
// RegisterRoutes registers the routes for the PhoneHandler
41
func (h *PhoneHandler) RegisterRoutes(router fiber.Router, middlewares ...fiber.Handler) {
42
	router.Get("/v1/phones", h.computeRoute(middlewares, h.Index)...)
43
	router.Put("/v1/phones", h.computeRoute(middlewares, h.Upsert)...)
44
	router.Delete("/v1/phones/:phoneID", h.computeRoute(middlewares, h.Delete)...)
45
}
46
47
// RegisterPhoneAPIKeyRoutes registers the routes for the PhoneHandler
48
func (h *PhoneHandler) RegisterPhoneAPIKeyRoutes(router fiber.Router, middlewares ...fiber.Handler) {
49
	router.Put("/v1/phones/fcm-token", h.computeRoute(middlewares, h.UpsertFCMToken)...)
50
}
51
52
// Index returns the phones of a user
53
// @Summary      Get phones of a user
54
// @Description  Get list of phones which a user has registered on the http sms application
55
// @Security	 ApiKeyAuth
56
// @Tags         Phones
57
// @Accept       json
58
// @Produce      json
59
// @Param        skip		query  int  	false	"number of heartbeats to skip"		minimum(0)
60
// @Param        query		query  string  	false 	"filter phones containing query"
61
// @Param        limit		query  int  	false	"number of phones to return"		minimum(1)	maximum(20)
62
// @Success      200 		{object}	responses.PhonesResponse
63
// @Failure      400		{object}	responses.BadRequest
64
// @Failure 	 401    	{object}	responses.Unauthorized
65
// @Failure      422		{object}	responses.UnprocessableEntity
66
// @Failure      500		{object}	responses.InternalServerError
67
// @Router       /phones [get]
68
func (h *PhoneHandler) Index(c *fiber.Ctx) error {
69
	ctx, span := h.tracer.StartFromFiberCtx(c)
70
	defer span.End()
71
72
	ctxLogger := h.tracer.CtxLogger(h.logger, span)
73
74
	var request requests.PhoneIndex
75
	if err := c.QueryParser(&request); err != nil {
76
		msg := fmt.Sprintf("cannot marshall params [%s] into %T", c.OriginalURL(), request)
77
		ctxLogger.Warn(stacktrace.Propagate(err, msg))
78
		return h.responseBadRequest(c, err)
79
	}
80
81
	if errors := h.validator.ValidateIndex(ctx, request.Sanitize()); len(errors) != 0 {
82
		msg := fmt.Sprintf("validation errors [%s], while fetching phones [%+#v]", spew.Sdump(errors), request)
83
		ctxLogger.Warn(stacktrace.NewError(msg))
84
		return h.responseUnprocessableEntity(c, errors, "validation errors while fetching phones")
85
	}
86
87
	phones, err := h.service.Index(ctx, h.userFromContext(c), request.ToIndexParams())
88
	if err != nil {
89
		msg := fmt.Sprintf("cannot index phones with params [%+#v]", request)
90
		ctxLogger.Error(stacktrace.Propagate(err, msg))
91
		return h.responseInternalServerError(c)
92
	}
93
94
	return h.responseOK(c, fmt.Sprintf("fetched %d %s", len(*phones), h.pluralize("phone", len(*phones))), phones)
95
}
96
97
// Upsert a phone
98
// @Summary      Upsert Phone
99
// @Description  Updates properties of a user's phone. If the phone with this number does not exist, a new one will be created. Think of this method like an 'upsert'
100
// @Security	 ApiKeyAuth
101
// @Tags         Phones
102
// @Accept       json
103
// @Produce      json
104
// @Param        payload   	body 		requests.PhoneUpsert  			true 	"Payload of new phone number."
105
// @Success      200 		{object}	responses.PhoneResponse
106
// @Failure      400		{object}	responses.BadRequest
107
// @Failure 	 401    	{object}	responses.Unauthorized
108
// @Failure      422		{object}	responses.UnprocessableEntity
109
// @Failure      500		{object}	responses.InternalServerError
110
// @Router       /phones [put]
111
func (h *PhoneHandler) Upsert(c *fiber.Ctx) error {
112
	ctx, span := h.tracer.StartFromFiberCtx(c)
113
	defer span.End()
114
115
	ctxLogger := h.tracer.CtxLogger(h.logger, span)
116
117
	var request requests.PhoneUpsert
118
	if err := c.BodyParser(&request); err != nil {
119
		msg := fmt.Sprintf("cannot marshall params [%s] into %T", c.OriginalURL(), request)
120
		ctxLogger.Warn(stacktrace.Propagate(err, msg))
121
		return h.responseBadRequest(c, err)
122
	}
123
124
	if errors := h.validator.ValidateUpsert(ctx, request.Sanitize()); len(errors) != 0 {
125
		msg := fmt.Sprintf("validation errors [%s], while updating phones [%+#v]", spew.Sdump(errors), request)
126
		ctxLogger.Warn(stacktrace.NewError(msg))
127
		return h.responseUnprocessableEntity(c, errors, "validation errors while updating phones")
128
	}
129
130
	phone, err := h.service.Upsert(ctx, request.ToUpsertParams(h.userFromContext(c), c.OriginalURL()))
131
	if err != nil {
132
		msg := fmt.Sprintf("cannot update phones with params [%+#v]", request)
133
		ctxLogger.Error(stacktrace.Propagate(err, msg))
134
		return h.responseInternalServerError(c)
135
	}
136
137
	return h.responseOK(c, "phone updated successfully", phone)
138
}
139
140
// Delete a phone
141
// @Summary      Delete Phone
142
// @Description  Delete a phone that has been sored in the database
143
// @Security	 ApiKeyAuth
144
// @Tags         Phones
145
// @Accept       json
146
// @Produce      json
147
// @Param 		 phoneID 	path		string 							true 	"ID of the phone"	default(32343a19-da5e-4b1b-a767-3298a73703ca)
148
// @Success      204		{object}    responses.NoContent
149
// @Failure      400		{object}	responses.BadRequest
150
// @Failure 	 401    	{object}	responses.Unauthorized
151
// @Failure      422		{object}	responses.UnprocessableEntity
152
// @Failure      500		{object}	responses.InternalServerError
153
// @Router       /phones/{phoneID} [delete]
154
func (h *PhoneHandler) Delete(c *fiber.Ctx) error {
155
	ctx, span := h.tracer.StartFromFiberCtx(c)
156
	defer span.End()
157
158
	ctxLogger := h.tracer.CtxLogger(h.logger, span)
159
160
	request := requests.PhoneDelete{PhoneID: c.Params("phoneID")}
161
	if errors := h.validator.ValidateDelete(ctx, request); len(errors) != 0 {
162
		msg := fmt.Sprintf("validation errors [%s], while deleting phone [%+#v]", spew.Sdump(errors), request)
163
		ctxLogger.Warn(stacktrace.NewError(msg))
164
		return h.responseUnprocessableEntity(c, errors, "validation errors while deleting phone")
165
	}
166
167
	err := h.service.Delete(ctx, c.OriginalURL(), h.userIDFomContext(c), request.PhoneIDUuid())
168
	if err != nil {
169
		msg := fmt.Sprintf("cannot delete phones with params [%+#v]", request)
170
		ctxLogger.Error(stacktrace.Propagate(err, msg))
171
		return h.responseInternalServerError(c)
172
	}
173
174
	return h.responseOK(c, "phone deleted successfully", nil)
175
}
176
177
// UpsertFCMToken upserts the FCM token of a phone
178
// @Summary      Upserts the FCM token of a phone
179
// @Description  Updates the FCM token of a phone. If the phone with this number does not exist, a new one will be created. Think of this method like an 'upsert'
180
// @Security	 ApiKeyAuth
181
// @Tags         Phones
182
// @Accept       json
183
// @Produce      json
184
// @Param        payload   	body 		requests.PhoneFCMToken  			true 	"Payload of new FCM token."
185
// @Success      200 		{object}	responses.PhoneResponse
186
// @Failure      400		{object}	responses.BadRequest
187
// @Failure 	 401    	{object}	responses.Unauthorized
188
// @Failure      422		{object}	responses.UnprocessableEntity
189
// @Failure      500		{object}	responses.InternalServerError
190
// @Router       /phones/fcm-token [put]
191
func (h *PhoneHandler) UpsertFCMToken(c *fiber.Ctx) error {
192
	ctx, span := h.tracer.StartFromFiberCtx(c)
193
	defer span.End()
194
195
	ctxLogger := h.tracer.CtxLogger(h.logger, span)
196
197
	var request requests.PhoneFCMToken
198
	if err := c.BodyParser(&request); err != nil {
199
		msg := fmt.Sprintf("cannot marshall params [%s] into %T", c.OriginalURL(), request)
200
		ctxLogger.Warn(stacktrace.Propagate(err, msg))
201
		return h.responseBadRequest(c, err)
202
	}
203
204
	if errors := h.validator.ValidateFCMToken(ctx, request.Sanitize()); len(errors) != 0 {
205
		msg := fmt.Sprintf("validation errors [%s], while updating phones [%+#v]", spew.Sdump(errors), request)
206
		ctxLogger.Warn(stacktrace.NewError(msg))
207
		return h.responseUnprocessableEntity(c, errors, "validation errors while updating phones")
208
	}
209
210
	phone, err := h.service.UpsertFCMToken(ctx, request.ToPhoneFCMTokenParams(h.userFromContext(c), c.OriginalURL()))
211
	if err != nil {
212
		msg := fmt.Sprintf("cannot delete phones with params [%+#v]", request)
213
		ctxLogger.Error(stacktrace.Propagate(err, msg))
214
		return h.responseInternalServerError(c)
215
	}
216
217
	return h.responseOK(c, "FCM token updated successfully", phone)
218
}
219