Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions api/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ require (
github.com/redis/go-redis/extra/redisotel/v9 v9.19.0
github.com/redis/go-redis/v9 v9.19.0
github.com/rs/zerolog v1.35.1
github.com/schollz/progressbar/v3 v3.19.0
github.com/stretchr/testify v1.11.1
github.com/swaggo/swag v1.16.6
github.com/thedevsaddam/govalidator v1.9.10
Expand Down Expand Up @@ -142,7 +141,6 @@ require (
github.com/mattn/go-isatty v0.0.22 // indirect
github.com/mattn/go-runewidth v0.0.23 // indirect
github.com/mattn/go-sqlite3 v1.14.44 // indirect
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
Expand All @@ -155,7 +153,6 @@ require (
github.com/redis/go-redis/extra/rediscmd/v9 v9.19.0 // indirect
github.com/richardlehane/mscfb v1.0.6 // indirect
github.com/richardlehane/msoleps v1.0.6 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/segmentio/asm v1.2.1 // indirect
github.com/shopspring/decimal v1.4.0 // indirect
github.com/spiffe/go-spiffe/v2 v2.6.0 // indirect
Expand Down Expand Up @@ -196,7 +193,6 @@ require (
golang.org/x/net v0.53.0 // indirect
golang.org/x/oauth2 v0.36.0 // indirect
golang.org/x/sys v0.44.0 // indirect
golang.org/x/term v0.43.0 // indirect
golang.org/x/text v0.37.0 // indirect
golang.org/x/time v0.15.0 // indirect
golang.org/x/tools v0.44.0 // indirect
Expand Down
10 changes: 0 additions & 10 deletions api/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,6 @@ github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1x
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/chengxilo/virtualterm v1.0.4 h1:Z6IpERbRVlfB8WkOmtbHiDbBANU7cimRIof7mk9/PwM=
github.com/chengxilo/virtualterm v1.0.4/go.mod h1:DyxxBZz/x1iqJjFxTFcr6/x+jSpqN0iwWCOK1q10rlY=
github.com/clipperhouse/displaywidth v0.11.0 h1:lBc6kY44VFw+TDx4I8opi/EtL9m20WSEFgwIwO+UVM8=
github.com/clipperhouse/displaywidth v0.11.0/go.mod h1:bkrFNkf81G8HyVqmKGxsPufD3JhNl3dSqnGhOoSD/o0=
github.com/clipperhouse/uax29/v2 v2.7.0 h1:+gs4oBZ2gPfVrKPthwbMzWZDaAFPGYK72F0NJv2v7Vk=
Expand Down Expand Up @@ -247,8 +245,6 @@ github.com/mattn/go-runewidth v0.0.23 h1:7ykA0T0jkPpzSvMS5i9uoNn2Xy3R383f9HDx3Ry
github.com/mattn/go-runewidth v0.0.23/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-sqlite3 v1.14.44 h1:3VSe+xafpbzsLbdr2AWlAZk9yRHiBhTBakioXaCKTF8=
github.com/mattn/go-sqlite3 v1.14.44/go.mod h1:pjEuOr8IwzLJP2MfGeTb0A35jauH+C2kbHKBr7yXKVQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db h1:62I3jR2EmQ4l5rM/4FEfDWcRD+abF5XlKShorW5LRoQ=
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0dey0ia/Uv7NcFFVbCLtqEBQbrT4OCwCSKTEv6enCw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
Expand Down Expand Up @@ -295,14 +291,10 @@ github.com/richardlehane/mscfb v1.0.6 h1:eN3bvvZCp00bs7Zf52bxNwAx5lJDBK1tCuH19qq
github.com/richardlehane/mscfb v1.0.6/go.mod h1:pe0+IUIc0AHh0+teNzBlJCtSyZdFOGgV4ZK9bsoV+Jo=
github.com/richardlehane/msoleps v1.0.6 h1:9BvkpjvD+iUBalUY4esMwv6uBkfOip/Lzvd93jvR9gg=
github.com/richardlehane/msoleps v1.0.6/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
github.com/rs/zerolog v1.35.1 h1:m7xQeoiLIiV0BCEY4Hs+j2NG4Gp2o2KPKmhnnLiazKI=
github.com/rs/zerolog v1.35.1/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw=
github.com/schollz/progressbar/v3 v3.19.0 h1:Ea18xuIRQXLAUidVDox3AbwfUhD0/1IvohyTutOIFoc=
github.com/schollz/progressbar/v3 v3.19.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
Expand Down Expand Up @@ -485,8 +477,6 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
Expand Down
1 change: 1 addition & 0 deletions api/pkg/di/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,7 @@ func (container *Container) PhoneAPIKeyHandler() (handler *handlers.PhoneAPIKeyH
container.Tracer(),
container.PhoneAPIKeyHandlerValidator(),
container.PhoneAPIKeyService(),
container.EntitlementService(),
)
}

Expand Down
33 changes: 25 additions & 8 deletions api/pkg/handlers/phone_api_key_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ import (
// PhoneAPIKeyHandler handles phone API key http requests
type PhoneAPIKeyHandler struct {
handler
logger telemetry.Logger
tracer telemetry.Tracer
validator *validators.PhoneAPIKeyHandlerValidator
service *services.PhoneAPIKeyService
logger telemetry.Logger
tracer telemetry.Tracer
validator *validators.PhoneAPIKeyHandlerValidator
service *services.PhoneAPIKeyService
entitlementService *services.EntitlementService
}

// NewPhoneAPIKeyHandler creates a new PhoneAPIKeyHandler
Expand All @@ -29,12 +30,14 @@ func NewPhoneAPIKeyHandler(
tracer telemetry.Tracer,
validator *validators.PhoneAPIKeyHandlerValidator,
service *services.PhoneAPIKeyService,
entitlementService *services.EntitlementService,
) *PhoneAPIKeyHandler {
return &PhoneAPIKeyHandler{
logger: logger.WithService(fmt.Sprintf("%T", &PhoneAPIKeyHandler{})),
tracer: tracer,
validator: validator,
service: service,
logger: logger.WithService(fmt.Sprintf("%T", &PhoneAPIKeyHandler{})),
tracer: tracer,
validator: validator,
service: service,
entitlementService: entitlementService,
}
}

Expand Down Expand Up @@ -99,13 +102,27 @@ func (h *PhoneAPIKeyHandler) index(c *fiber.Ctx) error {
// @Success 200 {object} responses.PhoneAPIKeyResponse
// @Failure 400 {object} responses.BadRequest
// @Failure 401 {object} responses.Unauthorized
// @Failure 402 {object} responses.PaymentRequired
// @Failure 422 {object} responses.UnprocessableEntity
// @Failure 500 {object} responses.InternalServerError
// @Router /phone-api-keys [post]
func (h *PhoneAPIKeyHandler) store(c *fiber.Ctx) error {
ctx, span, ctxLogger := h.tracer.StartFromFiberCtxWithLogger(c, h.logger)
defer span.End()

userID := h.userIDFomContext(c)

result, err := h.entitlementService.Check(ctx, userID, "PhoneAPIKey", func() (int, error) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 The entity name must match the key in entityLimits. If the key is renamed to "PhoneApiKey" in entitlement_service.go (to fix the malformed error message), this lookup must be updated too — otherwise the entitlement check always returns Allowed: true because the key isn't found in the map.

Suggested change
result, err := h.entitlementService.Check(ctx, userID, "PhoneAPIKey", func() (int, error) {
result, err := h.entitlementService.Check(ctx, userID, "PhoneApiKey", func() (int, error) {

return h.service.CountByUser(ctx, userID)
})
if err != nil {
ctxLogger.Error(stacktrace.Propagate(err, fmt.Sprintf("cannot check entitlement for phone API keys for user [%s]", userID)))
return h.responseInternalServerError(c)
}
if !result.Allowed {
return h.responsePaymentRequired(c, result.Message)
}

var request requests.PhoneAPIKeyStoreRequest
if err := c.BodyParser(&request); err != nil {
msg := fmt.Sprintf("cannot marshall params [%s] into %T", c.OriginalURL(), request)
Expand Down
17 changes: 17 additions & 0 deletions api/pkg/repositories/gorm_phone_api_key_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,23 @@ WHERE user_id = ? AND array_position(phone_ids, ?) IS NOT NULL;
return nil
}

// CountByUser returns the number of phone API keys owned by a user.
func (repository *gormPhoneAPIKeyRepository) CountByUser(ctx context.Context, userID entities.UserID) (int, error) {
ctx, span := repository.tracer.Start(ctx)
defer span.End()

var count int64
err := repository.db.WithContext(ctx).
Model(&entities.PhoneAPIKey{}).
Where("user_id = ?", userID).
Count(&count).Error
if err != nil {
return 0, repository.tracer.WrapErrorSpan(span, stacktrace.Propagate(err, "cannot count phone API keys for user [%s]", userID))
}

return int(count), nil
}

// Load an entities.PhoneAPIKey based on the entities.UserID
func (repository *gormPhoneAPIKeyRepository) Load(ctx context.Context, userID entities.UserID, phoneAPIKeyID uuid.UUID) (*entities.PhoneAPIKey, error) {
ctx, span := repository.tracer.Start(ctx)
Expand Down
3 changes: 3 additions & 0 deletions api/pkg/repositories/phone_api_key_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ type PhoneAPIKeyRepository interface {
// RemovePhone removes an entities.Phone to an entities.PhoneAPIKey
RemovePhone(ctx context.Context, phoneAPIKey *entities.PhoneAPIKey, phone *entities.Phone) error

// CountByUser returns the number of phone API keys owned by a user
CountByUser(ctx context.Context, userID entities.UserID) (int, error)

// DeleteAllForUser deletes all entities.PhoneAPIKey for a user
DeleteAllForUser(ctx context.Context, userID entities.UserID) error

Expand Down
3 changes: 3 additions & 0 deletions api/pkg/services/entitlement_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ var entityLimits = map[string]map[entities.SubscriptionName]int{
"MessageSendSchedule": {
entities.SubscriptionNameFree: 1,
},
"PhoneAPIKey": {
entities.SubscriptionNameFree: 1,
},
Comment on lines +22 to +24
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 The entity name "PhoneAPIKey" contains consecutive uppercase letters, which breaks formatEntityName. The function splits on every uppercase character boundary individually, so "PhoneAPIKey" becomes the words ["phone", "a", "p", "i", "key"], producing the error message: "Upgrade to a paid plan to create more than [1] phone a p i keys." — which is clearly unreadable. Using "PhoneApiKey" splits correctly into ["phone", "api", "key"]"phone api keys".

Suggested change
"PhoneAPIKey": {
entities.SubscriptionNameFree: 1,
},
"PhoneApiKey": {
entities.SubscriptionNameFree: 1,
},

}

// EntitlementCheckResult holds the outcome of an entitlement check.
Expand Down
8 changes: 8 additions & 0 deletions api/pkg/services/phone_api_key_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ func NewPhoneAPIKeyService(
}
}

// CountByUser returns the number of phone API keys owned by a user.
func (service *PhoneAPIKeyService) CountByUser(ctx context.Context, userID entities.UserID) (int, error) {
ctx, span := service.tracer.Start(ctx)
defer span.End()

return service.repository.CountByUser(ctx, userID)
}

// Index fetches the entities.Webhook for an entities.UserID
func (service *PhoneAPIKeyService) Index(ctx context.Context, userID entities.UserID, params repositories.IndexParams) ([]*entities.PhoneAPIKey, error) {
ctx, span, ctxLogger := service.tracer.StartWithLogger(ctx, service.logger)
Expand Down
Loading