Add slug to cookie categories for stable consent identifiers

The category slug provides a stable, URL-safe key used as the
data-cookie-consent attribute value and consent data key, replacing
the fragile category name. This prevents breakage when categories
are renamed.

- Add slug column with unique-per-banner constraint and backfill migration
- Add Slug validator (lowercase alphanumeric + hyphens)
- Propagate slug through GraphQL schema, service layer, and snapshot
- Update console UI with slug field in create/edit forms
- Switch cookie-banner widget to use slug as consent data keys

Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
Émile Ré
2026-04-23 19:07:11 +04:00
parent bb39daebc5
commit 165b9ad9d3
19 changed files with 218 additions and 22 deletions

View File

@@ -419,6 +419,7 @@ func (r *mutationResolver) CreateCookieCategory(ctx context.Context, input types
cookiebanner.CreateCookieCategoryRequest{
CookieBannerID: input.CookieBannerID,
Name: input.Name,
Slug: input.Slug,
Description: input.Description,
Rank: input.Rank,
},
@@ -427,6 +428,9 @@ func (r *mutationResolver) CreateCookieCategory(ctx context.Context, input types
if errors.Is(err, cookiebanner.ErrBannerNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
if errors.Is(err, cookiebanner.ErrCategorySlugAlreadyExists) {
return nil, gqlutils.Conflict(ctx, err)
}
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
}
@@ -460,6 +464,7 @@ func (r *mutationResolver) UpdateCookieCategory(ctx context.Context, input types
cookiebanner.UpdateCookieCategoryRequest{
CookieCategoryID: input.CookieCategoryID,
Name: input.Name,
Slug: input.Slug,
Description: input.Description,
},
)
@@ -467,6 +472,9 @@ func (r *mutationResolver) UpdateCookieCategory(ctx context.Context, input types
if errors.Is(err, cookiebanner.ErrCategoryNotFound) {
return nil, gqlutils.NotFound(ctx, err)
}
if errors.Is(err, cookiebanner.ErrCategorySlugAlreadyExists) {
return nil, gqlutils.Conflict(ctx, err)
}
if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok {
return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors)
}

View File

@@ -119,6 +119,7 @@ type CookieCategory implements Node {
id: ID!
cookieBanner: CookieBanner @goField(forceResolver: true)
name: String!
slug: String!
description: String!
kind: CookieCategoryKind!
rank: Int!
@@ -300,6 +301,7 @@ input PublishCookieBannerVersionInput {
input CreateCookieCategoryInput {
cookieBannerId: ID!
name: String!
slug: String!
description: String!
rank: Int!
}
@@ -307,6 +309,7 @@ input CreateCookieCategoryInput {
input UpdateCookieCategoryInput {
cookieCategoryId: ID!
name: String
slug: String
description: String
}

View File

@@ -67,6 +67,7 @@ func NewCookieCategory(c *coredata.CookieCategory) *CookieCategory {
ID: c.CookieBannerID,
},
Name: c.Name,
Slug: c.Slug,
Description: c.Description,
Kind: c.Kind,
Rank: c.Rank,