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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Unified the two rate limiter store implementations (`rateLimiterStore` / `tokenRateLimiterStore`) behind a single generic `rateLimiterStore[K comparable]`; `RateLimitMiddleware` and `TokenRateLimitMiddleware` are now thin adapters over a shared factory. No behavior change.
- Folded the `Algorithm` return value into `TransitKey.Algorithm` so `GetTransitKey` and `Get` return `(*TransitKey, error)` instead of a three-value tuple; replaced `crypto/domain.Algorithm` with `keyring.Algorithm` across transit and tokenization layers. No behavior change.
- Collapsed `internal/crypto/{domain,repository,service,usecase}` into `internal/keyring`; all types, stores, cipher primitives, KMS adapter, and KEK use case now live in a single package. No behavior change.
- Replaced seven shallow metrics decorator structs with a single `BusinessMetricsMiddleware` applied per-route in the HTTP server and a `metrics.Track` helper for CLI commands; added `NopBusinessMetrics` so the DI container always returns a valid recorder instead of branching on `MetricsEnabled`. No behavior change.

## [0.28.0] - 2026-03-23

Expand Down
10 changes: 10 additions & 0 deletions cmd/app/auth_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,15 @@ func getAuthCommands() []*cli.Command {
if err != nil {
return err
}
bm, err := container.BusinessMetrics(ctx)
if err != nil {
return err
}

return commands.RunPurgeAuthTokens(
ctx,
tokenUseCase,
bm,
container.Logger(),
commands.DefaultIO().Writer,
int(cmd.Int("days")),
Expand Down Expand Up @@ -89,10 +94,15 @@ func getAuthCommands() []*cli.Command {
if err != nil {
return err
}
bm, err := container.BusinessMetrics(ctx)
if err != nil {
return err
}

return commands.RunCleanExpiredTokens(
ctx,
tokenizationUseCase,
bm,
container.Logger(),
commands.DefaultIO().Writer,
int(cmd.Int("days")),
Expand Down
10 changes: 8 additions & 2 deletions cmd/app/commands/clean_expired_tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"log/slog"

"github.com/allisson/secrets/internal/metrics"
tokenizationUseCase "github.com/allisson/secrets/internal/tokenization/usecase"
)

Expand Down Expand Up @@ -44,6 +45,7 @@ func (r *CleanExpiredTokensResult) ToJSON() string {
func RunCleanExpiredTokens(
ctx context.Context,
tokenizationUseCase tokenizationUseCase.TokenizationUseCase,
bm metrics.BusinessMetrics,
logger *slog.Logger,
writer io.Writer,
days int,
Expand All @@ -61,8 +63,12 @@ func RunCleanExpiredTokens(
)

// Execute deletion or count operation
count, err := tokenizationUseCase.CleanupExpired(ctx, days, dryRun)
if err != nil {
var count int64
if err := metrics.Track(ctx, bm, "tokenization", "tokenize_cleanup_expired", func() error {
var e error
count, e = tokenizationUseCase.CleanupExpired(ctx, days, dryRun)
return e
}); err != nil {
return fmt.Errorf("failed to cleanup expired tokens: %w", err)
}

Expand Down
34 changes: 31 additions & 3 deletions cmd/app/commands/clean_expired_tokens_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/stretchr/testify/require"

"github.com/allisson/secrets/internal/metrics"
tokenizationMocks "github.com/allisson/secrets/internal/tokenization/usecase/mocks"
)

Expand All @@ -21,7 +22,16 @@ func TestRunCleanExpiredTokens(t *testing.T) {
mockUseCase.On("CleanupExpired", ctx, days, false).Return(int64(100), nil)

var out bytes.Buffer
err := RunCleanExpiredTokens(ctx, mockUseCase, logger, &out, days, false, "text")
err := RunCleanExpiredTokens(
ctx,
mockUseCase,
metrics.NewNopBusinessMetrics(),
logger,
&out,
days,
false,
"text",
)

require.NoError(t, err)
require.Contains(t, out.String(), "Successfully deleted 100 expired token(s)")
Expand All @@ -33,7 +43,16 @@ func TestRunCleanExpiredTokens(t *testing.T) {
mockUseCase.On("CleanupExpired", ctx, days, true).Return(int64(50), nil)

var out bytes.Buffer
err := RunCleanExpiredTokens(ctx, mockUseCase, logger, &out, days, true, "json")
err := RunCleanExpiredTokens(
ctx,
mockUseCase,
metrics.NewNopBusinessMetrics(),
logger,
&out,
days,
true,
"json",
)

require.NoError(t, err)
require.Contains(t, out.String(), `"count": 50`)
Expand All @@ -43,7 +62,16 @@ func TestRunCleanExpiredTokens(t *testing.T) {

t.Run("invalid-days", func(t *testing.T) {
mockUseCase := &tokenizationMocks.MockTokenizationUseCase{}
err := RunCleanExpiredTokens(ctx, mockUseCase, logger, &bytes.Buffer{}, -1, false, "text")
err := RunCleanExpiredTokens(
ctx,
mockUseCase,
metrics.NewNopBusinessMetrics(),
logger,
&bytes.Buffer{},
-1,
false,
"text",
)

require.Error(t, err)
require.Contains(t, err.Error(), "days must be a positive number")
Expand Down
10 changes: 8 additions & 2 deletions cmd/app/commands/purge_auth_tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"log/slog"

"github.com/allisson/secrets/internal/auth/usecase"
"github.com/allisson/secrets/internal/metrics"
)

// PurgeAuthTokensResult holds the result of the authentication token purge operation.
Expand Down Expand Up @@ -44,6 +45,7 @@ func (r *PurgeAuthTokensResult) ToJSON() string {
func RunPurgeAuthTokens(
ctx context.Context,
tokenUseCase usecase.TokenUseCase,
bm metrics.BusinessMetrics,
logger *slog.Logger,
writer io.Writer,
days int,
Expand Down Expand Up @@ -77,8 +79,12 @@ func RunPurgeAuthTokens(
}

// Execute purge
count, err := tokenUseCase.PurgeExpiredAndRevoked(ctx, days)
if err != nil {
var count int64
if err := metrics.Track(ctx, bm, "auth", "token_purge", func() error {
var e error
count, e = tokenUseCase.PurgeExpiredAndRevoked(ctx, days)
return e
}); err != nil {
return fmt.Errorf("failed to purge authentication tokens: %w", err)
}

Expand Down
34 changes: 31 additions & 3 deletions cmd/app/commands/purge_auth_tokens_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"github.com/stretchr/testify/require"

usecaseMocks "github.com/allisson/secrets/internal/auth/usecase/mocks"
"github.com/allisson/secrets/internal/metrics"
)

func TestRunPurgeAuthTokens(t *testing.T) {
Expand All @@ -21,7 +22,16 @@ func TestRunPurgeAuthTokens(t *testing.T) {
mockUseCase.EXPECT().PurgeExpiredAndRevoked(ctx, days).Return(int64(100), nil).Once()

var out bytes.Buffer
err := RunPurgeAuthTokens(ctx, mockUseCase, logger, &out, days, false, "text")
err := RunPurgeAuthTokens(
ctx,
mockUseCase,
metrics.NewNopBusinessMetrics(),
logger,
&out,
days,
false,
"text",
)

require.NoError(t, err)
require.Contains(t, out.String(), "Successfully purged 100 expired/revoked authentication token(s)")
Expand All @@ -32,7 +42,16 @@ func TestRunPurgeAuthTokens(t *testing.T) {
mockUseCase.EXPECT().PurgeExpiredAndRevoked(ctx, days).Return(int64(50), nil).Once()

var out bytes.Buffer
err := RunPurgeAuthTokens(ctx, mockUseCase, logger, &out, days, false, "json")
err := RunPurgeAuthTokens(
ctx,
mockUseCase,
metrics.NewNopBusinessMetrics(),
logger,
&out,
days,
false,
"json",
)

require.NoError(t, err)
require.Contains(t, out.String(), `"count": 50`)
Expand All @@ -41,7 +60,16 @@ func TestRunPurgeAuthTokens(t *testing.T) {

t.Run("invalid-days", func(t *testing.T) {
mockUseCase := usecaseMocks.NewMockTokenUseCase(t)
err := RunPurgeAuthTokens(ctx, mockUseCase, logger, &bytes.Buffer{}, -1, false, "text")
err := RunPurgeAuthTokens(
ctx,
mockUseCase,
metrics.NewNopBusinessMetrics(),
logger,
&bytes.Buffer{},
-1,
false,
"text",
)

require.Error(t, err)
require.Contains(t, err.Error(), "days must be a non-negative number")
Expand Down
10 changes: 8 additions & 2 deletions cmd/app/commands/purge_secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"io"
"log/slog"

"github.com/allisson/secrets/internal/metrics"
secretsUseCase "github.com/allisson/secrets/internal/secrets/usecase"
)

Expand Down Expand Up @@ -40,6 +41,7 @@ func (r *PurgeSecretsResult) ToJSON() string {
func RunPurgeSecrets(
ctx context.Context,
secretUseCase secretsUseCase.SecretUseCase,
bm metrics.BusinessMetrics,
logger *slog.Logger,
writer io.Writer,
days int,
Expand All @@ -57,8 +59,12 @@ func RunPurgeSecrets(
)

// Execute purge operation
count, err := secretUseCase.PurgeDeleted(ctx, days, dryRun)
if err != nil {
var count int64
if err := metrics.Track(ctx, bm, "secrets", "secret_purge_deleted", func() error {
var e error
count, e = secretUseCase.PurgeDeleted(ctx, days, dryRun)
return e
}); err != nil {
return fmt.Errorf("failed to purge secrets: %w", err)
}

Expand Down
78 changes: 71 additions & 7 deletions cmd/app/commands/purge_secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/stretchr/testify/require"

"github.com/allisson/secrets/internal/metrics"
secretsMocks "github.com/allisson/secrets/internal/secrets/usecase/mocks"
)

Expand All @@ -21,7 +22,16 @@ func TestRunPurgeSecrets(t *testing.T) {
mockUseCase.On("PurgeDeleted", ctx, days, false).Return(int64(100), nil)

var out bytes.Buffer
err := RunPurgeSecrets(ctx, mockUseCase, logger, &out, days, false, "text")
err := RunPurgeSecrets(
ctx,
mockUseCase,
metrics.NewNopBusinessMetrics(),
logger,
&out,
days,
false,
"text",
)

require.NoError(t, err)
require.Contains(t, out.String(), "Successfully deleted 100 secret(s) older than 30 day(s)")
Expand All @@ -33,7 +43,16 @@ func TestRunPurgeSecrets(t *testing.T) {
mockUseCase.On("PurgeDeleted", ctx, days, true).Return(int64(75), nil)

var out bytes.Buffer
err := RunPurgeSecrets(ctx, mockUseCase, logger, &out, days, true, "text")
err := RunPurgeSecrets(
ctx,
mockUseCase,
metrics.NewNopBusinessMetrics(),
logger,
&out,
days,
true,
"text",
)

require.NoError(t, err)
require.Contains(t, out.String(), "Dry-run mode: Would delete 75 secret(s) older than 30 day(s)")
Expand All @@ -45,7 +64,16 @@ func TestRunPurgeSecrets(t *testing.T) {
mockUseCase.On("PurgeDeleted", ctx, days, true).Return(int64(50), nil)

var out bytes.Buffer
err := RunPurgeSecrets(ctx, mockUseCase, logger, &out, days, true, "json")
err := RunPurgeSecrets(
ctx,
mockUseCase,
metrics.NewNopBusinessMetrics(),
logger,
&out,
days,
true,
"json",
)

require.NoError(t, err)
require.Contains(t, out.String(), `"count": 50`)
Expand All @@ -59,7 +87,16 @@ func TestRunPurgeSecrets(t *testing.T) {
mockUseCase.On("PurgeDeleted", ctx, days, false).Return(int64(25), nil)

var out bytes.Buffer
err := RunPurgeSecrets(ctx, mockUseCase, logger, &out, days, false, "json")
err := RunPurgeSecrets(
ctx,
mockUseCase,
metrics.NewNopBusinessMetrics(),
logger,
&out,
days,
false,
"json",
)

require.NoError(t, err)
require.Contains(t, out.String(), `"count": 25`)
Expand All @@ -70,7 +107,16 @@ func TestRunPurgeSecrets(t *testing.T) {

t.Run("invalid-days-negative", func(t *testing.T) {
mockUseCase := &secretsMocks.MockSecretUseCase{}
err := RunPurgeSecrets(ctx, mockUseCase, logger, &bytes.Buffer{}, -1, false, "text")
err := RunPurgeSecrets(
ctx,
mockUseCase,
metrics.NewNopBusinessMetrics(),
logger,
&bytes.Buffer{},
-1,
false,
"text",
)

require.Error(t, err)
require.Contains(t, err.Error(), "days must be a positive number")
Expand All @@ -81,7 +127,16 @@ func TestRunPurgeSecrets(t *testing.T) {
mockUseCase.On("PurgeDeleted", ctx, 0, false).Return(int64(10), nil)

var out bytes.Buffer
err := RunPurgeSecrets(ctx, mockUseCase, logger, &out, 0, false, "text")
err := RunPurgeSecrets(
ctx,
mockUseCase,
metrics.NewNopBusinessMetrics(),
logger,
&out,
0,
false,
"text",
)

require.NoError(t, err)
require.Contains(t, out.String(), "Successfully deleted 10 secret(s) older than 0 day(s)")
Expand All @@ -93,7 +148,16 @@ func TestRunPurgeSecrets(t *testing.T) {
mockUseCase.On("PurgeDeleted", ctx, days, false).Return(int64(0), nil)

var out bytes.Buffer
err := RunPurgeSecrets(ctx, mockUseCase, logger, &out, days, false, "text")
err := RunPurgeSecrets(
ctx,
mockUseCase,
metrics.NewNopBusinessMetrics(),
logger,
&out,
days,
false,
"text",
)

require.NoError(t, err)
require.Contains(t, out.String(), "Successfully deleted 0 secret(s)")
Expand Down
Loading
Loading