Skip to content

Commit bea48a1

Browse files
authored
feat(tokenization): delete tokenization keys by name (#117)
Update the Delete method in TokenizationKeyUseCase and TokenizationKeyRepository to accept a key name instead of a UUID. This change ensures that when a tokenization key is deleted by name, all its versions are correctly soft-deleted simultaneously. Key changes: - Changed Delete method signature to accept name string in both UseCase and Repository interfaces. - Updated MySQL and PostgreSQL repository implementations to soft-delete all versions by name. - Changed HTTP route from DELETE /v1/tokenization/keys/:id to DELETE /v1/tokenization/keys/:name. - Updated TokenizationKeyHandler.DeleteHandler to extract the name from the path. - Updated OpenAPI specification and docs/engines/tokenization.md to reflect the new API contract. - Updated unit tests for handler, use case, and both repository implementations. - Updated integration tests in tokenization_flow_test.go to verify name-based deletion. - Cleaned up unused uuid imports and variables introduced by the change.
1 parent 6d63f2b commit bea48a1

16 files changed

Lines changed: 98 additions & 137 deletions

docs/engines/tokenization.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ Example response (`200 OK`):
172172

173173
#### Delete Tokenization Key
174174

175-
- **Endpoint**: `DELETE /v1/tokenization/keys/:id`
175+
- **Endpoint**: `DELETE /v1/tokenization/keys/:name`
176176
- **Capability**: `delete`
177177
- **Success**: `204 No Content`
178178

docs/openapi.yaml

Lines changed: 20 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -811,31 +811,14 @@ paths:
811811
$ref: "#/components/schemas/ErrorResponse"
812812
"429":
813813
$ref: "#/components/responses/TooManyRequests"
814-
/v1/tokenization/keys/{name}/rotate:
815-
post:
814+
delete:
816815
tags: [tokenization]
817-
summary: Rotate tokenization key
816+
summary: Delete tokenization key
818817
security:
819818
- bearerAuth: []
820-
parameters:
821-
- name: name
822-
in: path
823-
required: true
824-
schema:
825-
type: string
826-
requestBody:
827-
required: true
828-
content:
829-
application/json:
830-
schema:
831-
$ref: "#/components/schemas/TokenizationKeyRotateRequest"
832819
responses:
833-
"201":
834-
description: New tokenization key version created
835-
content:
836-
application/json:
837-
schema:
838-
$ref: "#/components/schemas/TokenizationKeyResponse"
820+
"204":
821+
description: Deleted
839822
"401":
840823
$ref: "#/components/responses/Unauthorized"
841824
"403":
@@ -850,22 +833,31 @@ paths:
850833
$ref: "#/components/responses/ValidationError"
851834
"429":
852835
$ref: "#/components/responses/TooManyRequests"
853-
/v1/tokenization/keys/{id}:
854-
delete:
836+
/v1/tokenization/keys/{name}/rotate:
837+
post:
855838
tags: [tokenization]
856-
summary: Delete tokenization key
839+
summary: Rotate tokenization key
857840
security:
858841
- bearerAuth: []
859842
parameters:
860-
- name: id
843+
- name: name
861844
in: path
862845
required: true
863846
schema:
864847
type: string
865-
format: uuid
848+
requestBody:
849+
required: true
850+
content:
851+
application/json:
852+
schema:
853+
$ref: "#/components/schemas/TokenizationKeyRotateRequest"
866854
responses:
867-
"204":
868-
description: Deleted
855+
"201":
856+
description: New tokenization key version created
857+
content:
858+
application/json:
859+
schema:
860+
$ref: "#/components/schemas/TokenizationKeyResponse"
869861
"401":
870862
$ref: "#/components/responses/Unauthorized"
871863
"403":

internal/http/server.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,7 @@ func (s *Server) registerTokenizationRoutes(
398398
)
399399

400400
// Delete tokenization key
401-
keys.DELETE("/:id",
401+
keys.DELETE("/:name",
402402
authHTTP.AuthorizationMiddleware(authDomain.DeleteCapability, auditLogUseCase, s.logger),
403403
tokenizationKeyHandler.DeleteHandler,
404404
)

internal/tokenization/http/tokenization_key_handler.go

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"net/http"
88

99
"github.com/gin-gonic/gin"
10-
"github.com/google/uuid"
1110

1211
"github.com/allisson/secrets/internal/httputil"
1312
"github.com/allisson/secrets/internal/tokenization/http/dto"
@@ -140,21 +139,21 @@ func (h *TokenizationKeyHandler) RotateHandler(c *gin.Context) {
140139
c.JSON(http.StatusCreated, response)
141140
}
142141

143-
// DeleteHandler soft-deletes a tokenization key by ID.
144-
// DELETE /v1/tokenization/keys/:id - Requires DeleteCapability.
142+
// DeleteHandler soft-deletes a tokenization key by name.
143+
// DELETE /v1/tokenization/keys/:name - Requires DeleteCapability.
145144
// Returns 204 No Content on success.
146145
func (h *TokenizationKeyHandler) DeleteHandler(c *gin.Context) {
147-
// Parse and validate UUID
148-
keyID, err := uuid.Parse(c.Param("id"))
149-
if err != nil {
146+
// Get key name from URL parameter
147+
name := c.Param("name")
148+
if name == "" {
150149
httputil.HandleBadRequestGin(c,
151-
fmt.Errorf("invalid key ID format: must be a valid UUID"),
150+
fmt.Errorf("key name is required"),
152151
h.logger)
153152
return
154153
}
155154

156155
// Call use case
157-
if err := h.keyUseCase.Delete(c.Request.Context(), keyID); err != nil {
156+
if err := h.keyUseCase.Delete(c.Request.Context(), name); err != nil {
158157
httputil.HandleErrorGin(c, err, h.logger)
159158
return
160159
}

internal/tokenization/http/tokenization_key_handler_test.go

Lines changed: 12 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -351,51 +351,34 @@ func TestTokenizationKeyHandler_DeleteHandler(t *testing.T) {
351351
t.Run("Success_DeleteKey", func(t *testing.T) {
352352
handler, mockUseCase := setupTestKeyHandler(t)
353353

354-
keyID := uuid.Must(uuid.NewV7())
354+
keyName := "test-key"
355355

356356
mockUseCase.EXPECT().
357-
Delete(mock.Anything, keyID).
357+
Delete(mock.Anything, keyName).
358358
Return(nil).
359359
Once()
360360

361-
c, w := createTestContext(http.MethodDelete, "/v1/tokenization/keys/"+keyID.String(), nil)
362-
c.Params = gin.Params{{Key: "id", Value: keyID.String()}}
361+
c, w := createTestContext(http.MethodDelete, "/v1/tokenization/keys/"+keyName, nil)
362+
c.Params = gin.Params{{Key: "name", Value: keyName}}
363363

364364
handler.DeleteHandler(c)
365365

366366
assert.Equal(t, http.StatusNoContent, w.Code)
367367
assert.Empty(t, w.Body.String())
368368
})
369369

370-
t.Run("Error_InvalidUUID", func(t *testing.T) {
371-
handler, _ := setupTestKeyHandler(t)
372-
373-
c, w := createTestContext(http.MethodDelete, "/v1/tokenization/keys/invalid-uuid", nil)
374-
c.Params = gin.Params{{Key: "id", Value: "invalid-uuid"}}
375-
376-
handler.DeleteHandler(c)
377-
378-
assert.Equal(t, http.StatusBadRequest, w.Code)
379-
380-
var response map[string]interface{}
381-
err := json.Unmarshal(w.Body.Bytes(), &response)
382-
assert.NoError(t, err)
383-
assert.Equal(t, "bad_request", response["error"])
384-
assert.Contains(t, response["message"], "invalid key ID format")
385-
})
386-
387370
t.Run("Error_KeyNotFound", func(t *testing.T) {
388371
handler, mockUseCase := setupTestKeyHandler(t)
389372

390-
keyID := uuid.Must(uuid.NewV7())
373+
keyName := "non-existent-key"
391374

392375
mockUseCase.EXPECT().
393-
Delete(mock.Anything, keyID).
376+
Delete(mock.Anything, keyName).
394377
Return(tokenizationDomain.ErrTokenizationKeyNotFound).
395378
Once()
396379

397-
c, w := createTestContext(http.MethodDelete, "/v1/tokenization/keys/"+keyID.String(), nil)
398-
c.Params = gin.Params{{Key: "id", Value: keyID.String()}}
380+
c, w := createTestContext(http.MethodDelete, "/v1/tokenization/keys/"+keyName, nil)
381+
c.Params = gin.Params{{Key: "name", Value: keyName}}
399382

400383
handler.DeleteHandler(c)
401384

@@ -405,16 +388,16 @@ func TestTokenizationKeyHandler_DeleteHandler(t *testing.T) {
405388
t.Run("Error_UseCaseError", func(t *testing.T) {
406389
handler, mockUseCase := setupTestKeyHandler(t)
407390

408-
keyID := uuid.Must(uuid.NewV7())
391+
keyName := "test-key"
409392
dbError := errors.New("database error")
410393

411394
mockUseCase.EXPECT().
412-
Delete(mock.Anything, keyID).
395+
Delete(mock.Anything, keyName).
413396
Return(dbError).
414397
Once()
415398

416-
c, w := createTestContext(http.MethodDelete, "/v1/tokenization/keys/"+keyID.String(), nil)
417-
c.Params = gin.Params{{Key: "id", Value: keyID.String()}}
399+
c, w := createTestContext(http.MethodDelete, "/v1/tokenization/keys/"+keyName, nil)
400+
c.Params = gin.Params{{Key: "name", Value: keyName}}
418401

419402
handler.DeleteHandler(c)
420403

internal/tokenization/repository/mysql/mysql_repository.go

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -60,17 +60,12 @@ func (m *MySQLTokenizationKeyRepository) Create(
6060
}
6161

6262
// Delete soft-deletes a tokenization key by setting its deleted_at timestamp.
63-
func (m *MySQLTokenizationKeyRepository) Delete(ctx context.Context, keyID uuid.UUID) error {
63+
func (m *MySQLTokenizationKeyRepository) Delete(ctx context.Context, name string) error {
6464
querier := database.GetTx(ctx, m.db)
6565

66-
query := `UPDATE tokenization_keys SET deleted_at = NOW() WHERE id = ?`
66+
query := `UPDATE tokenization_keys SET deleted_at = NOW() WHERE name = ?`
6767

68-
id, err := keyID.MarshalBinary()
69-
if err != nil {
70-
return apperrors.Wrap(err, "failed to marshal tokenization key id")
71-
}
72-
73-
_, err = querier.ExecContext(ctx, query, id)
68+
_, err := querier.ExecContext(ctx, query, name)
7469
if err != nil {
7570
return apperrors.Wrap(err, "failed to delete tokenization key")
7671
}

internal/tokenization/repository/mysql/mysql_repository_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ func TestMySQLTokenizationKeyRepository_Delete(t *testing.T) {
293293
require.NoError(t, err)
294294

295295
// Delete the key
296-
err = repo.Delete(ctx, key.ID)
296+
err = repo.Delete(ctx, key.Name)
297297
require.NoError(t, err)
298298

299299
// Verify soft delete - key should not be found

internal/tokenization/repository/postgresql/postgresql_repository.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ func (p *PostgreSQLTokenizationKeyRepository) Create(
5252
}
5353

5454
// Delete soft-deletes a tokenization key by setting its deleted_at timestamp.
55-
func (p *PostgreSQLTokenizationKeyRepository) Delete(ctx context.Context, keyID uuid.UUID) error {
55+
func (p *PostgreSQLTokenizationKeyRepository) Delete(ctx context.Context, name string) error {
5656
querier := database.GetTx(ctx, p.db)
5757

58-
query := `UPDATE tokenization_keys SET deleted_at = NOW() WHERE id = $1`
58+
query := `UPDATE tokenization_keys SET deleted_at = NOW() WHERE name = $1`
5959

60-
_, err := querier.ExecContext(ctx, query, keyID)
60+
_, err := querier.ExecContext(ctx, query, name)
6161
if err != nil {
6262
return apperrors.Wrap(err, "failed to delete tokenization key")
6363
}

internal/tokenization/repository/postgresql/postgresql_repository_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ func TestPostgreSQLTokenizationKeyRepository_Delete(t *testing.T) {
293293
require.NoError(t, err)
294294

295295
// Delete the key
296-
err = repo.Delete(ctx, key.ID)
296+
err = repo.Delete(ctx, key.Name)
297297
require.NoError(t, err)
298298

299299
// Verify soft delete - key should not be found

internal/tokenization/usecase/interface.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ type DekRepository interface {
2121
// TokenizationKeyRepository defines the interface for tokenization key persistence.
2222
type TokenizationKeyRepository interface {
2323
Create(ctx context.Context, key *tokenizationDomain.TokenizationKey) error
24-
Delete(ctx context.Context, keyID uuid.UUID) error
24+
Delete(ctx context.Context, name string) error
2525
Get(ctx context.Context, keyID uuid.UUID) (*tokenizationDomain.TokenizationKey, error)
2626
GetByName(ctx context.Context, name string) (*tokenizationDomain.TokenizationKey, error)
2727
GetByNameAndVersion(
@@ -88,8 +88,8 @@ type TokenizationKeyUseCase interface {
8888
alg cryptoDomain.Algorithm,
8989
) (*tokenizationDomain.TokenizationKey, error)
9090

91-
// Delete soft deletes a tokenization key and all its versions by key ID.
92-
Delete(ctx context.Context, keyID uuid.UUID) error
91+
// Delete soft deletes a tokenization key and all its versions by name.
92+
Delete(ctx context.Context, name string) error
9393

9494
// GetByName retrieves a single tokenization key by its name.
9595
// Returns the latest version for the key. Filters out soft-deleted keys.

0 commit comments

Comments
 (0)