Skip to content
Draft
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
5 changes: 5 additions & 0 deletions api/requests.http
Original file line number Diff line number Diff line change
Expand Up @@ -265,3 +265,8 @@ GET http://localhost:3000/health/ready HTTP/1.1
###
GET http://localhost:3000/health/live HTTP/1.1

###
HEAD http://localhost:3000/.well-known/api-catalog HTTP/1.1

###
GET http://localhost:3000/.well-known/api-catalog HTTP/1.1
7 changes: 7 additions & 0 deletions internal/sms-gateway/handlers/3rdparty.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ func newThirdPartyHandler(
func (h *thirdPartyHandler) Register(router fiber.Router) {
router = router.Group("/3rdparty/v1")

// Add Link header pointing to api-catalog (RFC 9727 Section 3)
router.Use(func(c *fiber.Ctx) error {
err := c.Next()
c.Set(fiber.HeaderLink, `</.well-known/api-catalog>; rel="api-catalog"`)
return err //nolint:wrapcheck // passed through to fiber's error handler
})

h.healthHandler.Register(router)

router.Use(
Expand Down
96 changes: 96 additions & 0 deletions internal/sms-gateway/handlers/api_catalog.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package handlers

import (
"fmt"
"strings"
"time"

"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/limiter"
"go.uber.org/zap"
)

type APICatalogHandler struct {
config Config
logger *zap.Logger
}

func newAPICatalogHandler(cfg Config, logger *zap.Logger) *APICatalogHandler {
return &APICatalogHandler{
config: cfg,
logger: logger.Named("api_catalog"),
}
}

func (h *APICatalogHandler) get(c *fiber.Ctx) error {
const (
fieldHref = "href"
fieldType = "type"
)

c.Set(fiber.HeaderContentType, `application/linkset+json; profile="https://www.rfc-editor.org/info/rfc9727"`)
c.Set(fiber.HeaderLink, `</.well-known/api-catalog>; rel="api-catalog"`)
c.Set(fiber.HeaderCacheControl, "public, max-age=3600")

host := h.getHost(c)
path := h.getPath(c)
linkset := fiber.Map{
"linkset": []fiber.Map{
{
"anchor": fmt.Sprintf("https://%s/%s/3rdparty/v1", host, path),
"service-desc": []fiber.Map{
{
fieldHref: fmt.Sprintf("https://%s/%s/docs/doc.json", host, path),
fieldType: "application/json",
},
},
"service-doc": []fiber.Map{
{
fieldHref: "https://docs.sms-gate.app/",
fieldType: "text/html",
},
},
"status": []fiber.Map{
{
fieldHref: fmt.Sprintf("https://%s/%s/3rdparty/v1/health/ready", host, path),
fieldType: "application/json",
},
},
},
},
}

return c.JSON(linkset)
}

func (h *APICatalogHandler) head(c *fiber.Ctx) error {
c.Set(fiber.HeaderContentType, `application/linkset+json; profile="https://www.rfc-editor.org/info/rfc9727"`)
c.Set("Link", `</.well-known/api-catalog>; rel="api-catalog"`)
c.Set(fiber.HeaderCacheControl, "public, max-age=3600")
return c.SendStatus(fiber.StatusOK)
}

func (h *APICatalogHandler) getHost(c *fiber.Ctx) string {
if h.config.PublicHost != "" {
return h.config.PublicHost
}
return c.Hostname()
}

func (h *APICatalogHandler) getPath(_ *fiber.Ctx) any {
return strings.TrimLeft(h.config.PublicPath, "/")
}

func (h *APICatalogHandler) Register(app *fiber.App) {
const limit = 60

rateLimiter := limiter.New(limiter.Config{
Max: limit,
Expiration: time.Minute,
LimiterMiddleware: limiter.SlidingWindow{},
})

group := app.Group("/.well-known/api-catalog", rateLimiter)
group.Get("", h.get)
group.Head("", h.head)
}
1 change: 1 addition & 0 deletions internal/sms-gateway/handlers/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ func Module() fx.Option {
}),
fx.Provide(
http.AsRootHandler(newRootHandler),
http.AsRootHandler(newAPICatalogHandler),
http.AsApiHandler(newThirdPartyHandler),
http.AsApiHandler(newMobileHandler),
http.AsApiHandler(newUpstreamHandler),
Expand Down
38 changes: 19 additions & 19 deletions internal/sms-gateway/jwt/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (

// Metric constants.
const (
metricsNamespace = "sms"
metricsSubsystem = "jwt"

MetricTokensIssuedTotal = "jwt_tokens_issued_total" //nolint:gosec // false positive
MetricTokensValidatedTotal = "jwt_tokens_validated_total" //nolint:gosec // false positive
MetricTokensRevokedTotal = "jwt_tokens_revoked_total" //nolint:gosec // false positive
Expand Down Expand Up @@ -37,67 +40,64 @@ type Metrics struct {

// NewMetrics creates and initializes all JWT metrics.
func NewMetrics() *Metrics {
const namespace = "sms"
const subsystem = "auth"

var defBuckets = []float64{.0005, .001, .0025, .005, .01, .025, .05, .1, .25, .5, 1}

return &Metrics{
tokensIssuedCounter: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: MetricTokensIssuedTotal,
Help: "Total number of JWT tokens issued",
}, []string{labelStatus}),

tokensValidatedCounter: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: MetricTokensValidatedTotal,
Help: "Total number of JWT tokens validated",
}, []string{labelStatus}),

tokensRevokedCounter: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: MetricTokensRevokedTotal,
Help: "Total number of JWT tokens revoked",
}, []string{labelStatus}),

tokensRefreshedCounter: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: MetricTokensRefreshedTotal,
Help: "Total number of JWT tokens refreshed",
}, []string{labelStatus}),

issuanceDurationHistogram: promauto.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: MetricIssuanceDurationSeconds,
Help: "JWT issuance duration in seconds",
Buckets: defBuckets,
}),

validationDurationHistogram: promauto.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: MetricValidationDurationSeconds,
Help: "JWT validation duration in seconds",
Buckets: defBuckets,
}),

revocationDurationHistogram: promauto.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: MetricRevocationDurationSeconds,
Help: "JWT revocation duration in seconds",
Buckets: defBuckets,
}),

refreshDurationHistogram: promauto.NewHistogram(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: MetricRefreshDurationSeconds,
Help: "JWT refresh duration in seconds",
Buckets: defBuckets,
Expand Down
18 changes: 9 additions & 9 deletions internal/sms-gateway/modules/events/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (

// Metric constants.
const (
metricsNamespace = "sms"
metricsSubsystem = "events"

MetricEnqueuedTotal = "enqueued_total"
MetricSentTotal = "sent_total"
MetricFailedTotal = "failed_total"
Expand Down Expand Up @@ -35,25 +38,22 @@ type metrics struct {

// newMetrics creates and initializes all events metrics.
func newMetrics() *metrics {
const namespace = "sms"
const subsystem = "events"

return &metrics{
enqueuedCounter: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: MetricEnqueuedTotal,
Help: "Total number of events enqueued",
}, []string{LabelEvent}),
sentCounter: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: MetricSentTotal,
Help: "Total number of events sent",
}, []string{LabelEvent, LabelDeliveryType}),
failedCounter: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: MetricFailedTotal,
Help: "Total number of failed notifications",
}, []string{LabelEvent, LabelDeliveryType, LabelReason}),
Expand Down
22 changes: 11 additions & 11 deletions internal/sms-gateway/modules/push/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ import (
type BlacklistOperation string

const (
metricsNamespace = "sms"
metricsSubsystem = "push"

BlacklistOperationAdded BlacklistOperation = "added"
BlacklistOperationSkipped BlacklistOperation = "skipped"
)
Expand All @@ -20,34 +23,31 @@ type metrics struct {
}

func newMetrics() *metrics {
const namespace = "sms"
const subsystem = "push"

return &metrics{
enqueuedCounter: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: "enqueued_total",
Help: "Total number of messages enqueued",
}, []string{"event"}),

retriesCounter: promauto.NewCounter(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: "retries_total",
Help: "Total retry attempts",
}),

blacklistCounter: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: "blacklist_total",
Help: "Blacklist operations",
}, []string{"operation"}),

errorsCounter: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: "errors_total",
Help: "Total number of errors",
}, []string{}),
Expand Down
25 changes: 13 additions & 12 deletions internal/sms-gateway/modules/sse/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import (

// Metric constants.
const (
metricsNamespace = "sms"
metricsSubsystem = "sse"

MetricActiveConnections = "active_connections"
MetricEventsSent = "events_sent_total"
MetricConnectionErrors = "connection_errors_total"
Expand All @@ -33,39 +36,37 @@ type metrics struct {

// newMetrics creates and initializes all SSE metrics.
func newMetrics() *metrics {
const namespace = "sms"
const subsystem = "sse"
var defBuckets = []float64{1e-6, 5e-6, 1e-5, 5e-5, 1e-4, 5e-4, .001, .005, .01, .05, .1}

metrics := &metrics{
activeConnections: promauto.NewGaugeVec(prometheus.GaugeOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: MetricActiveConnections,
Help: "Current number of active SSE connections",
}, []string{}),
eventsSent: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: MetricEventsSent,
Help: "Total number of SSE events sent, labeled by event type",
}, []string{LabelEventType}),
connectionErrors: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: MetricConnectionErrors,
Help: "Total number of SSE connection errors, labeled by error type",
}, []string{LabelErrorType}),
eventDeliveryLatency: promauto.NewHistogramVec(prometheus.HistogramOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: MetricEventLatency,
Help: "Event delivery latency in seconds",
Buckets: defBuckets,
}, []string{}),
keepalivesSent: promauto.NewCounterVec(prometheus.CounterOpts{
Namespace: namespace,
Subsystem: subsystem,
Namespace: metricsNamespace,
Subsystem: metricsSubsystem,
Name: MetricKeepalivesSent,
Help: "Total keepalive messages sent",
}, []string{}),
Expand Down
Loading
Loading