Add uncategorised cookie category
Replace the `required` boolean column on cookie_categories with a `kind` enum (NORMAL, NECESSARY, UNCATEGORISED). The Necessary category remains undeletable and always-on for consent; the new Uncategorised category is also undeletable but users can opt out of it. When a category is deleted, its cookies are merged into the Uncategorised category (lazy-created for legacy banners that don't have one yet). Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -37,10 +37,10 @@ type (
|
||||
}
|
||||
|
||||
CookieBannerVersionSnapshotCategory struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Required bool `json:"required"`
|
||||
Cookies CookieItems `json:"cookies"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Kind CookieCategoryKind `json:"kind"`
|
||||
Cookies CookieItems `json:"cookies"`
|
||||
}
|
||||
|
||||
CookieBannerVersion struct {
|
||||
|
||||
@@ -38,16 +38,16 @@ type (
|
||||
CookieItems []CookieItem
|
||||
|
||||
CookieCategory struct {
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
CookieBannerID gid.GID `db:"cookie_banner_id"`
|
||||
Name string `db:"name"`
|
||||
Description string `db:"description"`
|
||||
Required bool `db:"required"`
|
||||
Rank int `db:"rank"`
|
||||
Cookies CookieItems `db:"cookies"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
CookieBannerID gid.GID `db:"cookie_banner_id"`
|
||||
Name string `db:"name"`
|
||||
Description string `db:"description"`
|
||||
Kind CookieCategoryKind `db:"kind"`
|
||||
Rank int `db:"rank"`
|
||||
Cookies CookieItems `db:"cookies"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
CookieCategories []*CookieCategory
|
||||
@@ -105,7 +105,7 @@ SELECT
|
||||
cookie_banner_id,
|
||||
name,
|
||||
description,
|
||||
required,
|
||||
kind,
|
||||
rank,
|
||||
cookies,
|
||||
created_at,
|
||||
@@ -155,7 +155,7 @@ SELECT
|
||||
cookie_banner_id,
|
||||
name,
|
||||
description,
|
||||
required,
|
||||
kind,
|
||||
rank,
|
||||
cookies,
|
||||
created_at,
|
||||
@@ -233,7 +233,7 @@ SELECT
|
||||
cookie_banner_id,
|
||||
name,
|
||||
description,
|
||||
required,
|
||||
kind,
|
||||
rank,
|
||||
cookies,
|
||||
created_at,
|
||||
@@ -280,7 +280,7 @@ INSERT INTO cookie_categories (
|
||||
cookie_banner_id,
|
||||
name,
|
||||
description,
|
||||
required,
|
||||
kind,
|
||||
rank,
|
||||
cookies,
|
||||
created_at,
|
||||
@@ -292,7 +292,7 @@ INSERT INTO cookie_categories (
|
||||
@cookie_banner_id,
|
||||
@name,
|
||||
@description,
|
||||
@required,
|
||||
@kind,
|
||||
@rank,
|
||||
@cookies,
|
||||
@created_at,
|
||||
@@ -307,7 +307,7 @@ INSERT INTO cookie_categories (
|
||||
"cookie_banner_id": c.CookieBannerID,
|
||||
"name": c.Name,
|
||||
"description": c.Description,
|
||||
"required": c.Required,
|
||||
"kind": c.Kind,
|
||||
"rank": c.Rank,
|
||||
"cookies": c.Cookies,
|
||||
"created_at": c.CreatedAt,
|
||||
@@ -343,7 +343,7 @@ RETURNING
|
||||
cookie_banner_id,
|
||||
name,
|
||||
description,
|
||||
required,
|
||||
kind,
|
||||
rank,
|
||||
cookies,
|
||||
created_at,
|
||||
@@ -449,3 +449,56 @@ WHERE
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CookieCategory) LoadUncategorisedByCookieBannerID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
cookieBannerID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
cookie_banner_id,
|
||||
name,
|
||||
description,
|
||||
kind,
|
||||
rank,
|
||||
cookies,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
cookie_categories
|
||||
WHERE
|
||||
%s
|
||||
AND cookie_banner_id = @cookie_banner_id
|
||||
AND kind = @kind
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"cookie_banner_id": cookieBannerID,
|
||||
"kind": CookieCategoryKindUncategorised,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query uncategorised cookie category: %w", err)
|
||||
}
|
||||
|
||||
category, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CookieCategory])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return ErrResourceNotFound
|
||||
}
|
||||
return fmt.Errorf("cannot collect uncategorised cookie category: %w", err)
|
||||
}
|
||||
|
||||
*c = category
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
27
pkg/coredata/cookie_category_kind.go
Normal file
27
pkg/coredata/cookie_category_kind.go
Normal file
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
type CookieCategoryKind string
|
||||
|
||||
const (
|
||||
CookieCategoryKindNormal CookieCategoryKind = "NORMAL"
|
||||
CookieCategoryKindNecessary CookieCategoryKind = "NECESSARY"
|
||||
CookieCategoryKindUncategorised CookieCategoryKind = "UNCATEGORISED"
|
||||
)
|
||||
|
||||
func (k CookieCategoryKind) IsRequired() bool {
|
||||
return k == CookieCategoryKindNecessary
|
||||
}
|
||||
27
pkg/coredata/migrations/20260420T143027Z.sql
Normal file
27
pkg/coredata/migrations/20260420T143027Z.sql
Normal file
@@ -0,0 +1,27 @@
|
||||
-- Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||
--
|
||||
-- Permission to use, copy, modify, and/or distribute this software for any
|
||||
-- purpose with or without fee is hereby granted, provided that the above
|
||||
-- copyright notice and this permission notice appear in all copies.
|
||||
--
|
||||
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
-- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
-- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
-- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
-- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
-- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
-- PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
CREATE TYPE cookie_category_kind AS ENUM ('NORMAL', 'NECESSARY', 'UNCATEGORISED');
|
||||
|
||||
ALTER TABLE cookie_categories ADD COLUMN kind cookie_category_kind NOT NULL DEFAULT 'NORMAL';
|
||||
UPDATE cookie_categories SET kind = 'NECESSARY' WHERE required = TRUE;
|
||||
ALTER TABLE cookie_categories ALTER COLUMN kind DROP DEFAULT;
|
||||
|
||||
DROP INDEX idx_cookie_categories_one_required_per_banner;
|
||||
ALTER TABLE cookie_categories DROP COLUMN required;
|
||||
|
||||
CREATE UNIQUE INDEX idx_cookie_categories_one_necessary_per_banner
|
||||
ON cookie_categories (cookie_banner_id) WHERE kind = 'NECESSARY';
|
||||
CREATE UNIQUE INDEX idx_cookie_categories_one_uncategorised_per_banner
|
||||
ON cookie_categories (cookie_banner_id) WHERE kind = 'UNCATEGORISED';
|
||||
Reference in New Issue
Block a user