Skip to content

Commit 66f2b6a

Browse files
committed
Create billing handler routes
1 parent ad1d4a9 commit 66f2b6a

6 files changed

Lines changed: 261 additions & 0 deletions

File tree

api/pkg/di/container.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ func NewContainer(projectID string) (container *Container) {
9393

9494
container.RegisterNotificationListeners()
9595

96+
container.RegisterBillingRoutes()
97+
container.RegisterBillingListeners()
98+
9699
// this has to be last since it registers the /* route
97100
container.RegisterSwaggerRoutes()
98101

@@ -324,6 +327,17 @@ func (container *Container) HeartbeatHandler() (h *handlers.HeartbeatHandler) {
324327
)
325328
}
326329

330+
// BillingHandler creates a new instance of handlers.BillingHandler
331+
func (container *Container) BillingHandler() (h *handlers.BillingHandler) {
332+
container.logger.Debug(fmt.Sprintf("creating %T", h))
333+
return handlers.NewBillingHandler(
334+
container.Logger(),
335+
container.Tracer(),
336+
container.BillingHandlerValidator(),
337+
container.BillingService(),
338+
)
339+
}
340+
327341
// HeartbeatHandlerValidator creates a new instance of validators.HeartbeatHandlerValidator
328342
func (container *Container) HeartbeatHandlerValidator() (validator *validators.HeartbeatHandlerValidator) {
329343
container.logger.Debug(fmt.Sprintf("creating %T", validator))
@@ -333,6 +347,15 @@ func (container *Container) HeartbeatHandlerValidator() (validator *validators.H
333347
)
334348
}
335349

350+
// BillingHandlerValidator creates a new instance of validators.BillingHandlerValidator
351+
func (container *Container) BillingHandlerValidator() (validator *validators.BillingHandlerValidator) {
352+
container.logger.Debug(fmt.Sprintf("creating %T", validator))
353+
return validators.NewBillingHandlerValidator(
354+
container.Logger(),
355+
container.Tracer(),
356+
)
357+
}
358+
336359
// MessageThreadHandler creates a new instance of handlers.MessageThreadHandler
337360
func (container *Container) MessageThreadHandler() (h *handlers.MessageThreadHandler) {
338361
container.logger.Debug(fmt.Sprintf("creating %T", h))
@@ -725,6 +748,12 @@ func (container *Container) RegisterHeartbeatRoutes() {
725748
container.HeartbeatHandler().RegisterRoutes(container.AuthRouter())
726749
}
727750

751+
// RegisterBillingRoutes registers routes for the /billing prefix
752+
func (container *Container) RegisterBillingRoutes() {
753+
container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.BillingHandler{}))
754+
container.BillingHandler().RegisterRoutes(container.AuthRouter())
755+
}
756+
728757
// RegisterPhoneRoutes registers routes for the /phone prefix
729758
func (container *Container) RegisterPhoneRoutes() {
730759
container.logger.Debug(fmt.Sprintf("registering %T routes", &handlers.PhoneHandler{}))
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package handlers
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/NdoleStudio/httpsms/pkg/requests"
7+
"github.com/NdoleStudio/httpsms/pkg/services"
8+
"github.com/NdoleStudio/httpsms/pkg/telemetry"
9+
"github.com/NdoleStudio/httpsms/pkg/validators"
10+
"github.com/davecgh/go-spew/spew"
11+
"github.com/gofiber/fiber/v2"
12+
"github.com/palantir/stacktrace"
13+
)
14+
15+
// BillingHandler handles billing http requests.
16+
type BillingHandler struct {
17+
handler
18+
logger telemetry.Logger
19+
tracer telemetry.Tracer
20+
validator *validators.BillingHandlerValidator
21+
service *services.BillingService
22+
}
23+
24+
// NewBillingHandler creates a new BillingHandler
25+
func NewBillingHandler(
26+
logger telemetry.Logger,
27+
tracer telemetry.Tracer,
28+
validator *validators.BillingHandlerValidator,
29+
service *services.BillingService,
30+
) (h *BillingHandler) {
31+
return &BillingHandler{
32+
logger: logger.WithService(fmt.Sprintf("%T", h)),
33+
tracer: tracer,
34+
validator: validator,
35+
service: service,
36+
}
37+
}
38+
39+
// RegisterRoutes registers the routes for the MessageHandler
40+
func (h *BillingHandler) RegisterRoutes(router fiber.Router) {
41+
router.Get("/billing/usage-history", h.UsageHistory)
42+
router.Get("/billing/usage", h.Usage)
43+
}
44+
45+
// UsageHistory returns the usage history of a user
46+
// @Summary Get billing usage history.
47+
// @Description Get billing usage records of sent and received messages for a user in the past. It will be sorted by timestamp in descending order.
48+
// @Security ApiKeyAuth
49+
// @Tags Billing
50+
// @Accept json
51+
// @Produce json
52+
// @Param skip query int false "number of heartbeats to skip" minimum(0)
53+
// @Param limit query int false "number of heartbeats to return" minimum(1) maximum(100)
54+
// @Success 200 {object} responses.BillingUsagesResponse
55+
// @Failure 400 {object} responses.BadRequest
56+
// @Failure 401 {object} responses.Unauthorized
57+
// @Failure 422 {object} responses.UnprocessableEntity
58+
// @Failure 500 {object} responses.InternalServerError
59+
// @Router /billing/usage-history [get]
60+
func (h *BillingHandler) UsageHistory(c *fiber.Ctx) error {
61+
ctx, span := h.tracer.StartFromFiberCtx(c)
62+
defer span.End()
63+
64+
ctxLogger := h.tracer.CtxLogger(h.logger, span)
65+
66+
var request requests.BillingUsageHistory
67+
if err := c.QueryParser(&request); err != nil {
68+
msg := fmt.Sprintf("cannot marshall params [%s] into %T", c.OriginalURL(), request)
69+
ctxLogger.Warn(stacktrace.Propagate(err, msg))
70+
return h.responseBadRequest(c, err)
71+
}
72+
73+
if errors := h.validator.ValidateHistory(ctx, request.Sanitize()); len(errors) != 0 {
74+
msg := fmt.Sprintf("validation errors [%s], while fetching heartbeats [%+#v]", spew.Sdump(errors), request)
75+
ctxLogger.Warn(stacktrace.NewError(msg))
76+
return h.responseUnprocessableEntity(c, errors, "validation errors while fetching usage history")
77+
}
78+
79+
heartbeats, err := h.service.GetUsageHistory(ctx, h.userIDFomContext(c), request.ToIndexParams())
80+
if err != nil {
81+
msg := fmt.Sprintf("cannot get billing usage history with params [%+#v]", request)
82+
ctxLogger.Error(stacktrace.Propagate(err, msg))
83+
return h.responseInternalServerError(c)
84+
}
85+
86+
return h.responseOK(c, fmt.Sprintf("fetched %d billing usage %s", len(*heartbeats), h.pluralize("record", len(*heartbeats))), heartbeats)
87+
}
88+
89+
// Usage returns the current usage history of a user
90+
// @Summary Get Billing Usage.
91+
// @Description Get the summary of sent and received messages for a user in the current month
92+
// @Security ApiKeyAuth
93+
// @Tags Billing
94+
// @Accept json
95+
// @Produce json
96+
// @Success 200 {object} responses.BillingUsageResponse
97+
// @Failure 400 {object} responses.BadRequest
98+
// @Failure 401 {object} responses.Unauthorized
99+
// @Failure 422 {object} responses.UnprocessableEntity
100+
// @Failure 500 {object} responses.InternalServerError
101+
// @Router /billing/usage [get]
102+
func (h *BillingHandler) Usage(c *fiber.Ctx) error {
103+
ctx, span := h.tracer.StartFromFiberCtx(c)
104+
defer span.End()
105+
106+
ctxLogger := h.tracer.CtxLogger(h.logger, span)
107+
108+
billingUsage, err := h.service.GetCurrentUsage(ctx, h.userIDFomContext(c))
109+
if err != nil {
110+
msg := fmt.Sprintf("cannot get current usage record for user [%s]", h.userFromContext(c))
111+
ctxLogger.Error(stacktrace.Propagate(err, msg))
112+
return h.responseInternalServerError(c)
113+
}
114+
115+
return h.responseOK(c, "fetched current billing usage", billingUsage)
116+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package requests
2+
3+
import (
4+
"strings"
5+
6+
"github.com/NdoleStudio/httpsms/pkg/repositories"
7+
)
8+
9+
// BillingUsageHistory is the payload for fetching the entities.BillingUsage history
10+
type BillingUsageHistory struct {
11+
request
12+
Skip string `json:"skip" query:"skip"`
13+
Limit string `json:"limit" query:"limit"`
14+
}
15+
16+
// Sanitize sets defaults to MessageOutstanding
17+
func (input *BillingUsageHistory) Sanitize() BillingUsageHistory {
18+
if strings.TrimSpace(input.Limit) == "" {
19+
input.Limit = "12"
20+
}
21+
input.Skip = strings.TrimSpace(input.Skip)
22+
if input.Skip == "" {
23+
input.Skip = "0"
24+
}
25+
return *input
26+
}
27+
28+
// ToIndexParams converts BillingUsageHistory to repositories.IndexParams
29+
func (input *BillingUsageHistory) ToIndexParams() repositories.IndexParams {
30+
return repositories.IndexParams{
31+
Skip: input.getInt(input.Skip),
32+
Limit: input.getInt(input.Limit),
33+
}
34+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package responses
2+
3+
import "github.com/NdoleStudio/httpsms/pkg/entities"
4+
5+
// BillingUsagesResponse is the payload containing []entities.BillingUsage
6+
type BillingUsagesResponse struct {
7+
response
8+
Data []entities.Heartbeat `json:"data"`
9+
}
10+
11+
// BillingUsageResponse is the payload containing entities.BillingUsage
12+
type BillingUsageResponse struct {
13+
response
14+
Data entities.Heartbeat `json:"data"`
15+
}

api/pkg/services/billing_service.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,22 @@ func NewBillingService(
3333
}
3434
}
3535

36+
// GetCurrentUsage gets the current billing usage for a user
37+
func (service *BillingService) GetCurrentUsage(ctx context.Context, userID entities.UserID) (*entities.BillingUsage, error) {
38+
ctx, span := service.tracer.Start(ctx)
39+
defer span.End()
40+
41+
return service.usageRepository.GetCurrent(ctx, userID)
42+
}
43+
44+
// GetUsageHistory gets the billing usage history for a user
45+
func (service *BillingService) GetUsageHistory(ctx context.Context, userID entities.UserID, params repositories.IndexParams) (*[]entities.BillingUsage, error) {
46+
ctx, span := service.tracer.Start(ctx)
47+
defer span.End()
48+
49+
return service.usageRepository.GetHistory(ctx, userID, params)
50+
}
51+
3652
// RegisterSentMessage records the billing usage for a sent message
3753
func (service *BillingService) RegisterSentMessage(ctx context.Context, messageID uuid.UUID, timestamp time.Time, userID entities.UserID) error {
3854
ctx, span := service.tracer.Start(ctx)
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package validators
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/url"
7+
8+
"github.com/NdoleStudio/httpsms/pkg/requests"
9+
10+
"github.com/NdoleStudio/httpsms/pkg/telemetry"
11+
"github.com/thedevsaddam/govalidator"
12+
)
13+
14+
// BillingHandlerValidator validates models used in handlers.BillingHandler
15+
type BillingHandlerValidator struct {
16+
validator
17+
logger telemetry.Logger
18+
tracer telemetry.Tracer
19+
}
20+
21+
// NewBillingHandlerValidator creates a new handlers.BillingHandler validator
22+
func NewBillingHandlerValidator(
23+
logger telemetry.Logger,
24+
tracer telemetry.Tracer,
25+
) (v *BillingHandlerValidator) {
26+
return &BillingHandlerValidator{
27+
logger: logger.WithService(fmt.Sprintf("%T", v)),
28+
tracer: tracer,
29+
}
30+
}
31+
32+
// ValidateHistory validates the requests.BillingUsageHistory request
33+
func (validator *BillingHandlerValidator) ValidateHistory(_ context.Context, request requests.BillingUsageHistory) url.Values {
34+
v := govalidator.New(govalidator.Options{
35+
Data: &request,
36+
Rules: govalidator.MapData{
37+
"limit": []string{
38+
"required",
39+
"numeric",
40+
"min:1",
41+
"max:100",
42+
},
43+
"skip": []string{
44+
"required",
45+
"numeric",
46+
"min:0",
47+
},
48+
},
49+
})
50+
return v.ValidateStruct()
51+
}

0 commit comments

Comments
 (0)