forked from NdoleStudio/httpsms
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathheartbeat_handler.go
More file actions
157 lines (136 loc) · 6.12 KB
/
Copy pathheartbeat_handler.go
File metadata and controls
157 lines (136 loc) · 6.12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
package handlers
import (
"fmt"
"sync"
"github.com/NdoleStudio/httpsms/pkg/entities"
"github.com/NdoleStudio/httpsms/pkg/requests"
"github.com/NdoleStudio/httpsms/pkg/services"
"github.com/NdoleStudio/httpsms/pkg/telemetry"
"github.com/NdoleStudio/httpsms/pkg/validators"
"github.com/davecgh/go-spew/spew"
"github.com/gofiber/fiber/v3"
"github.com/palantir/stacktrace"
)
// HeartbeatHandler handles heartbeat http requests.
type HeartbeatHandler struct {
handler
logger telemetry.Logger
tracer telemetry.Tracer
validator *validators.HeartbeatHandlerValidator
service *services.HeartbeatService
}
// NewHeartbeatHandler creates a new HeartbeatHandler
func NewHeartbeatHandler(
logger telemetry.Logger,
tracer telemetry.Tracer,
validator *validators.HeartbeatHandlerValidator,
service *services.HeartbeatService,
) (h *HeartbeatHandler) {
return &HeartbeatHandler{
logger: logger.WithService(fmt.Sprintf("%T", h)),
tracer: tracer,
validator: validator,
service: service,
}
}
// RegisterRoutes registers the routes for the HeartbeatHandler
func (h *HeartbeatHandler) RegisterRoutes(router fiber.Router, middlewares ...fiber.Handler) {
h.register(router, fiber.MethodGet, "/v1/heartbeats", middlewares, h.Index)
}
// RegisterPhoneAPIKeyRoutes registers the routes for the HeartbeatHandler
func (h *HeartbeatHandler) RegisterPhoneAPIKeyRoutes(router fiber.Router, middlewares ...fiber.Handler) {
h.register(router, fiber.MethodPost, "/v1/heartbeats", middlewares, h.Store)
}
// Index returns the heartbeats of a phone number
// @Summary Get heartbeats of an owner phone number
// @Description Get the last time a phone number requested for outstanding messages. It will be sorted by timestamp in descending order.
// @Security ApiKeyAuth
// @Tags Heartbeats
// @Accept json
// @Produce json
// @Param owner query string true "the owner's phone number" default(+18005550199)
// @Param skip query int false "number of heartbeats to skip" minimum(0)
// @Param query query string false "filter containing query"
// @Param limit query int false "number of heartbeats to return" minimum(1) maximum(20)
// @Success 200 {object} responses.HeartbeatsResponse
// @Failure 400 {object} responses.BadRequest
// @Failure 401 {object} responses.Unauthorized
// @Failure 422 {object} responses.UnprocessableEntity
// @Failure 500 {object} responses.InternalServerError
// @Router /heartbeats [get]
func (h *HeartbeatHandler) Index(c fiber.Ctx) error {
ctx, span := h.tracer.StartFromFiberCtx(c)
defer span.End()
ctxLogger := h.tracer.CtxLogger(h.logger, span)
var request requests.HeartbeatIndex
if err := c.Bind().Query(&request); err != nil {
msg := fmt.Sprintf("cannot marshall params [%s] into %T", c.OriginalURL(), request)
ctxLogger.Warn(stacktrace.Propagate(err, msg))
return h.responseBadRequest(c, err)
}
if errors := h.validator.ValidateIndex(ctx, request.Sanitize()); len(errors) != 0 {
msg := fmt.Sprintf("validation errors [%s], while fetching heartbeats [%+#v]", spew.Sdump(errors), request)
ctxLogger.Warn(stacktrace.NewError(msg))
return h.responseUnprocessableEntity(c, errors, "validation errors while fetching heartbeats")
}
heartbeats, err := h.service.Index(ctx, h.userIDFomContext(c), request.Owner, request.ToIndexParams())
if err != nil {
msg := fmt.Sprintf("cannot get messgaes with params [%+#v]", request)
ctxLogger.Error(stacktrace.Propagate(err, msg))
return h.responseInternalServerError(c)
}
return h.responseOK(c, fmt.Sprintf("fetched %d %s", len(*heartbeats), h.pluralize("heartbeat", len(*heartbeats))), heartbeats)
}
// Store the heartbeat of a phone number
// @Summary Register heartbeat of an owner phone number
// @Description Store the heartbeat to make notify that a phone number is still active
// @Security ApiKeyAuth
// @Tags Heartbeats
// @Accept json
// @Produce json
// @Param payload body requests.HeartbeatStore true "Payload of the heartbeat request"
// @Success 200 {object} responses.HeartbeatResponse
// @Failure 400 {object} responses.BadRequest
// @Failure 401 {object} responses.Unauthorized
// @Failure 422 {object} responses.UnprocessableEntity
// @Failure 500 {object} responses.InternalServerError
// @Router /heartbeats [post]
func (h *HeartbeatHandler) Store(c fiber.Ctx) error {
ctx, span := h.tracer.StartFromFiberCtx(c)
defer span.End()
ctxLogger := h.tracer.CtxLogger(h.logger, span)
var request requests.HeartbeatStore
if err := c.Bind().Body(&request); err != nil {
msg := fmt.Sprintf("cannot marshall params [%s] into %T", c.OriginalURL(), request)
ctxLogger.Warn(stacktrace.Propagate(err, msg))
return h.responseBadRequest(c, err)
}
if errors := h.validator.ValidateStore(ctx, request.Sanitize()); len(errors) != 0 {
msg := fmt.Sprintf("validation errors [%s], while storing heartbeat [%+#v]", spew.Sdump(errors), request)
ctxLogger.Warn(stacktrace.NewError(msg))
return h.responseUnprocessableEntity(c, errors, "validation errors while storing heartbeat")
}
for _, phoneNumber := range request.PhoneNumbers {
if !h.authorizePhoneAPIKey(c, phoneNumber) {
ctxLogger.Warn(stacktrace.NewError(fmt.Sprintf("phone API Key ID [%s] is not authorized to store heartbeat for phone number [%s]", h.userFromContext(c).PhoneAPIKeyID, phoneNumber)))
return h.responsePhoneAPIKeyUnauthorized(c, phoneNumber, h.userFromContext(c))
}
}
params := request.ToStoreParams(h.userFromContext(c), c.OriginalURL(), c.Get("X-Client-Version"))
wg := sync.WaitGroup{}
responses := make([]*entities.Heartbeat, len(params))
for index, value := range params {
wg.Add(1)
go func(input services.HeartbeatStoreParams, index int) {
response, err := h.service.Store(ctx, input)
if err != nil {
msg := fmt.Sprintf("cannot store heartbeat with params [%+#v]", request)
ctxLogger.Error(stacktrace.Propagate(err, msg))
}
responses[index] = response
wg.Done()
}(value, index)
}
wg.Wait()
return h.responseCreated(c, fmt.Sprintf("[%d] heartbeats received successfully", len(responses)), responses)
}