From af6e420f545f3d63e203263e6b25948461e95cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Wed, 6 May 2026 10:21:33 +0400 Subject: [PATCH] Remove cookie_patterns legacy, migrate to tracker_patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Delete coredata.CookiePattern and all associated CRUD methods, rename shared types (CookiePatternOrderField, CookiePatternFilter, CookiePatternMatchType) to TrackerPattern equivalents, and migrate all API surfaces (GraphQL, MCP, CLI, n8n) to tracker_pattern naming. The worker was already migrated in the base branch; this commit completes the removal by dropping the old GraphQL schema/resolvers, service methods, CLI commands, and n8n operations that operated on the legacy cookie_patterns table. Signed-off-by: Émile Ré --- e2e/console/cookie_banner_versioning_test.go | 84 +- ...attern_test.go => tracker_pattern_test.go} | 294 +++---- e2e/internal/factory/factory.go | 23 +- packages/n8n-node/nodes/Probo/Probo.node.ts | 10 +- .../n8n-node/nodes/Probo/actions/index.ts | 4 +- .../create.operation.ts | 26 +- .../delete.operation.ts | 18 +- .../get.operation.ts | 18 +- .../getAll.operation.ts | 16 +- .../index.ts | 26 +- .../move.operation.ts | 20 +- .../update.operation.ts | 32 +- pkg/cmd/root/root.go | 4 +- .../create/create.go | 24 +- .../delete/delete.go | 16 +- .../list/list.go | 24 +- .../move/move.go | 22 +- .../tracker_pattern.go} | 20 +- .../update/update.go | 22 +- .../view/view.go | 11 +- pkg/cookiebanner/errors.go | 2 +- pkg/cookiebanner/service.go | 674 ++++----------- pkg/cookiebanner/worker.go | 12 +- pkg/cookiebanner/worker_test.go | 2 +- pkg/coredata/cookie_pattern.go | 794 ------------------ pkg/coredata/cookie_pattern_order_field.go | 71 -- pkg/coredata/entity_type_reg.go | 4 +- pkg/coredata/tracker_pattern.go | 171 +++- ...rn_filter.go => tracker_pattern_filter.go} | 20 +- ..._type.go => tracker_pattern_match_type.go} | 40 +- pkg/coredata/tracker_pattern_order_field.go | 71 ++ pkg/probo/actions.go | 12 +- .../api/console/v1/cookie_banner_resolvers.go | 387 ++------- .../console/v1/graphql/cookie_banner.graphql | 164 +--- .../api/console/v1/types/cookie_pattern.go | 119 --- .../api/console/v1/types/tracker_pattern.go | 8 +- pkg/server/api/mcp/v1/schema.resolvers.go | 71 +- pkg/server/api/mcp/v1/specification.yaml | 121 +-- .../{cookie_pattern.go => tracker_pattern.go} | 26 +- 39 files changed, 1008 insertions(+), 2475 deletions(-) rename e2e/console/{cookie_pattern_test.go => tracker_pattern_test.go} (65%) rename packages/n8n-node/nodes/Probo/actions/{cookiePattern => trackerPattern}/create.operation.ts (85%) rename packages/n8n-node/nodes/Probo/actions/{cookiePattern => trackerPattern}/delete.operation.ts (77%) rename packages/n8n-node/nodes/Probo/actions/{cookiePattern => trackerPattern}/get.operation.ts (80%) rename packages/n8n-node/nodes/Probo/actions/{cookiePattern => trackerPattern}/getAll.operation.ts (87%) rename packages/n8n-node/nodes/Probo/actions/{cookiePattern => trackerPattern}/index.ts (77%) rename packages/n8n-node/nodes/Probo/actions/{cookiePattern => trackerPattern}/move.operation.ts (79%) rename packages/n8n-node/nodes/Probo/actions/{cookiePattern => trackerPattern}/update.operation.ts (81%) rename pkg/cmd/{cookie-pattern => tracker-pattern}/create/create.go (86%) rename pkg/cmd/{cookie-pattern => tracker-pattern}/delete/delete.go (78%) rename pkg/cmd/{cookie-pattern => tracker-pattern}/list/list.go (81%) rename pkg/cmd/{cookie-pattern => tracker-pattern}/move/move.go (81%) rename pkg/cmd/{cookie-pattern/cookie_pattern.go => tracker-pattern/tracker_pattern.go} (70%) rename pkg/cmd/{cookie-pattern => tracker-pattern}/update/update.go (85%) rename pkg/cmd/{cookie-pattern => tracker-pattern}/view/view.go (92%) delete mode 100644 pkg/coredata/cookie_pattern.go delete mode 100644 pkg/coredata/cookie_pattern_order_field.go rename pkg/coredata/{cookie_pattern_filter.go => tracker_pattern_filter.go} (86%) rename pkg/coredata/{cookie_pattern_match_type.go => tracker_pattern_match_type.go} (52%) create mode 100644 pkg/coredata/tracker_pattern_order_field.go delete mode 100644 pkg/server/api/console/v1/types/cookie_pattern.go rename pkg/server/api/mcp/v1/types/{cookie_pattern.go => tracker_pattern.go} (68%) diff --git a/e2e/console/cookie_banner_versioning_test.go b/e2e/console/cookie_banner_versioning_test.go index 1eccdaeab..cdf8830a9 100644 --- a/e2e/console/cookie_banner_versioning_test.go +++ b/e2e/console/cookie_banner_versioning_test.go @@ -104,9 +104,9 @@ func setPatternExcluded(t *testing.T, c *testutil.Client, patternID string, excl t.Helper() const query = ` - mutation UpdateCookiePattern($input: UpdateCookiePatternInput!) { - updateCookiePattern(input: $input) { - cookiePattern { id excluded } + mutation UpdateTrackerPattern($input: UpdateTrackerPatternInput!) { + updateTrackerPattern(input: $input) { + trackerPattern { id excluded } } } ` @@ -114,10 +114,10 @@ func setPatternExcluded(t *testing.T, c *testutil.Client, patternID string, excl var result struct{} require.NoError(t, c.Execute(query, map[string]any{ "input": map[string]any{ - "cookiePatternId": patternID, - "excluded": excluded, + "trackerPatternId": patternID, + "excluded": excluded, }, - }, &result), "updateCookiePattern excluded mutation failed") + }, &result), "updateTrackerPattern excluded mutation failed") } // upsertTranslation upserts a translation for a banner+language pair and @@ -323,13 +323,13 @@ func TestCookieBannerVersioning_NoOpUpdates(t *testing.T) { assert.Equal(t, "PUBLISHED", got.State) }) - t.Run("UpdateCookiePattern on visible pattern with same value does not bump version", func(t *testing.T) { + t.Run("UpdateTrackerPattern on visible pattern with same value does not bump version", func(t *testing.T) { t.Parallel() owner := testutil.NewClient(t, testutil.RoleOwner) bannerID := factory.CreateCookieBanner(owner) categoryID := factory.CreateCookieCategory(owner, bannerID, factory.Attrs{"slug": "visible-noop"}) - patternID := factory.CreateCookiePattern(owner, categoryID, factory.Attrs{ + patternID := factory.CreateTrackerPattern(owner, categoryID, factory.Attrs{ "displayName": "GA Tracker", "description": "Original description", }) @@ -338,17 +338,17 @@ func TestCookieBannerVersioning_NoOpUpdates(t *testing.T) { baseline := published.Version const query = ` - mutation UpdateCookiePattern($input: UpdateCookiePatternInput!) { - updateCookiePattern(input: $input) { cookiePattern { id } } + mutation UpdateTrackerPattern($input: UpdateTrackerPatternInput!) { + updateTrackerPattern(input: $input) { trackerPattern { id } } } ` var result struct{} err := owner.Execute(query, map[string]any{ "input": map[string]any{ - "cookiePatternId": patternID, - "displayName": "GA Tracker", - "description": "Original description", + "trackerPatternId": patternID, + "displayName": "GA Tracker", + "description": "Original description", }, }, &result) require.NoError(t, err) @@ -368,7 +368,7 @@ func TestCookieBannerVersioning_ExcludedPattern(t *testing.T) { bannerID := factory.CreateCookieBanner(owner) categoryID := factory.CreateCookieCategory(owner, bannerID, factory.Attrs{"slug": "excl-update"}) - patternID := factory.CreateCookiePattern(owner, categoryID, factory.Attrs{ + patternID := factory.CreateTrackerPattern(owner, categoryID, factory.Attrs{ "displayName": "Original", }) @@ -377,32 +377,32 @@ func TestCookieBannerVersioning_ExcludedPattern(t *testing.T) { baseline := published.Version const query = ` - mutation UpdateCookiePattern($input: UpdateCookiePatternInput!) { - updateCookiePattern(input: $input) { - cookiePattern { id displayName description } + mutation UpdateTrackerPattern($input: UpdateTrackerPatternInput!) { + updateTrackerPattern(input: $input) { + trackerPattern { id displayName description } } } ` var result struct { - UpdateCookiePattern struct { - CookiePattern struct { + UpdateTrackerPattern struct { + TrackerPattern struct { DisplayName string `json:"displayName"` Description string `json:"description"` - } `json:"cookiePattern"` - } `json:"updateCookiePattern"` + } `json:"trackerPattern"` + } `json:"updateTrackerPattern"` } err := owner.Execute(query, map[string]any{ "input": map[string]any{ - "cookiePatternId": patternID, - "displayName": "Renamed Excluded", - "description": "Now with notes", + "trackerPatternId": patternID, + "displayName": "Renamed Excluded", + "description": "Now with notes", }, }, &result) require.NoError(t, err) - assert.Equal(t, "Renamed Excluded", result.UpdateCookiePattern.CookiePattern.DisplayName) - assert.Equal(t, "Now with notes", result.UpdateCookiePattern.CookiePattern.Description) + assert.Equal(t, "Renamed Excluded", result.UpdateTrackerPattern.TrackerPattern.DisplayName) + assert.Equal(t, "Now with notes", result.UpdateTrackerPattern.TrackerPattern.Description) got := latestVersion(t, owner, bannerID) assert.Equal(t, baseline, got.Version, "excluded pattern fields are invisible to visitors") @@ -415,23 +415,23 @@ func TestCookieBannerVersioning_ExcludedPattern(t *testing.T) { bannerID := factory.CreateCookieBanner(owner) categoryID := factory.CreateCookieCategory(owner, bannerID, factory.Attrs{"slug": "excl-delete"}) - patternID := factory.CreateCookiePattern(owner, categoryID) + patternID := factory.CreateTrackerPattern(owner, categoryID) setPatternExcluded(t, owner, patternID, true) published := publishBanner(t, owner, bannerID) baseline := published.Version const query = ` - mutation DeleteCookiePattern($input: DeleteCookiePatternInput!) { - deleteCookiePattern(input: $input) { - deletedCookiePatternId + mutation DeleteTrackerPattern($input: DeleteTrackerPatternInput!) { + deleteTrackerPattern(input: $input) { + deletedTrackerPatternId } } ` var result struct{} err := owner.Execute(query, map[string]any{ - "input": map[string]any{"cookiePatternId": patternID}, + "input": map[string]any{"trackerPatternId": patternID}, }, &result) require.NoError(t, err) @@ -447,16 +447,16 @@ func TestCookieBannerVersioning_ExcludedPattern(t *testing.T) { bannerID := factory.CreateCookieBanner(owner) categoryA := factory.CreateCookieCategory(owner, bannerID, factory.Attrs{"slug": "excl-move-a"}) categoryB := factory.CreateCookieCategory(owner, bannerID, factory.Attrs{"slug": "excl-move-b"}) - patternID := factory.CreateCookiePattern(owner, categoryA) + patternID := factory.CreateTrackerPattern(owner, categoryA) setPatternExcluded(t, owner, patternID, true) published := publishBanner(t, owner, bannerID) baseline := published.Version const query = ` - mutation MoveCookiePatternToCategory($input: MoveCookiePatternToCategoryInput!) { - moveCookiePatternToCategory(input: $input) { - cookiePattern { id } + mutation MoveTrackerPatternToCategory($input: MoveTrackerPatternToCategoryInput!) { + moveTrackerPatternToCategory(input: $input) { + trackerPattern { id } } } ` @@ -464,7 +464,7 @@ func TestCookieBannerVersioning_ExcludedPattern(t *testing.T) { var result struct{} err := owner.Execute(query, map[string]any{ "input": map[string]any{ - "cookiePatternId": patternID, + "trackerPatternId": patternID, "targetCookieCategoryId": categoryB, }, }, &result) @@ -604,13 +604,13 @@ func TestCookieBannerVersioning_RealChangesStillBumpVersion(t *testing.T) { assert.Equal(t, "DRAFT", got.State) }) - t.Run("UpdateCookiePattern displayName change on visible pattern creates a new draft", func(t *testing.T) { + t.Run("UpdateTrackerPattern displayName change on visible pattern creates a new draft", func(t *testing.T) { t.Parallel() owner := testutil.NewClient(t, testutil.RoleOwner) bannerID := factory.CreateCookieBanner(owner) categoryID := factory.CreateCookieCategory(owner, bannerID, factory.Attrs{"slug": "real-change"}) - patternID := factory.CreateCookiePattern(owner, categoryID, factory.Attrs{ + patternID := factory.CreateTrackerPattern(owner, categoryID, factory.Attrs{ "displayName": "Original", }) @@ -618,15 +618,15 @@ func TestCookieBannerVersioning_RealChangesStillBumpVersion(t *testing.T) { baseline := published.Version const query = ` - mutation UpdateCookiePattern($input: UpdateCookiePatternInput!) { - updateCookiePattern(input: $input) { cookiePattern { id } } + mutation UpdateTrackerPattern($input: UpdateTrackerPatternInput!) { + updateTrackerPattern(input: $input) { trackerPattern { id } } } ` var result struct{} err := owner.Execute(query, map[string]any{ "input": map[string]any{ - "cookiePatternId": patternID, - "displayName": "Renamed", + "trackerPatternId": patternID, + "displayName": "Renamed", }, }, &result) require.NoError(t, err) diff --git a/e2e/console/cookie_pattern_test.go b/e2e/console/tracker_pattern_test.go similarity index 65% rename from e2e/console/cookie_pattern_test.go rename to e2e/console/tracker_pattern_test.go index b518d0dd8..295eefa8c 100644 --- a/e2e/console/cookie_pattern_test.go +++ b/e2e/console/tracker_pattern_test.go @@ -23,7 +23,7 @@ import ( "go.probo.inc/probo/e2e/internal/testutil" ) -func TestCookiePattern_Create(t *testing.T) { +func TestTrackerPattern_Create(t *testing.T) { t.Parallel() t.Run("with EXACT match type", func(t *testing.T) { @@ -34,9 +34,9 @@ func TestCookiePattern_Create(t *testing.T) { categoryID := factory.CreateCookieCategory(owner, bannerID) const query = ` - mutation CreateCookiePattern($input: CreateCookiePatternInput!) { - createCookiePattern(input: $input) { - cookiePatternEdge { + mutation CreateTrackerPattern($input: CreateTrackerPatternInput!) { + createTrackerPattern(input: $input) { + trackerPatternEdge { node { id pattern @@ -57,8 +57,8 @@ func TestCookiePattern_Create(t *testing.T) { ` var result struct { - CreateCookiePattern struct { - CookiePatternEdge struct { + CreateTrackerPattern struct { + TrackerPatternEdge struct { Node struct { ID string `json:"id"` Pattern string `json:"pattern"` @@ -70,11 +70,11 @@ func TestCookiePattern_Create(t *testing.T) { CreatedAt string `json:"createdAt"` UpdatedAt string `json:"updatedAt"` } `json:"node"` - } `json:"cookiePatternEdge"` + } `json:"trackerPatternEdge"` CookieBanner struct { ID string `json:"id"` } `json:"cookieBanner"` - } `json:"createCookiePattern"` + } `json:"createTrackerPattern"` } maxAge := 86400 @@ -90,7 +90,7 @@ func TestCookiePattern_Create(t *testing.T) { }, &result) require.NoError(t, err) - node := result.CreateCookiePattern.CookiePatternEdge.Node + node := result.CreateTrackerPattern.TrackerPatternEdge.Node assert.NotEmpty(t, node.ID) assert.Equal(t, "_ga", node.Pattern) assert.Equal(t, "EXACT", node.MatchType) @@ -99,7 +99,7 @@ func TestCookiePattern_Create(t *testing.T) { assert.Equal(t, maxAge, *node.MaxAgeSeconds) assert.Equal(t, "Google Analytics tracking cookie", node.Description) assert.Equal(t, "SCRIPT", node.Source) - assert.Equal(t, bannerID, result.CreateCookiePattern.CookieBanner.ID) + assert.Equal(t, bannerID, result.CreateTrackerPattern.CookieBanner.ID) }) t.Run("with PREFIX match type", func(t *testing.T) { @@ -110,9 +110,9 @@ func TestCookiePattern_Create(t *testing.T) { categoryID := factory.CreateCookieCategory(owner, bannerID) const query = ` - mutation CreateCookiePattern($input: CreateCookiePatternInput!) { - createCookiePattern(input: $input) { - cookiePatternEdge { + mutation CreateTrackerPattern($input: CreateTrackerPatternInput!) { + createTrackerPattern(input: $input) { + trackerPatternEdge { node { id pattern @@ -126,8 +126,8 @@ func TestCookiePattern_Create(t *testing.T) { ` var result struct { - CreateCookiePattern struct { - CookiePatternEdge struct { + CreateTrackerPattern struct { + TrackerPatternEdge struct { Node struct { ID string `json:"id"` Pattern string `json:"pattern"` @@ -135,8 +135,8 @@ func TestCookiePattern_Create(t *testing.T) { DisplayName string `json:"displayName"` MaxAgeSeconds *int `json:"maxAgeSeconds"` } `json:"node"` - } `json:"cookiePatternEdge"` - } `json:"createCookiePattern"` + } `json:"trackerPatternEdge"` + } `json:"createTrackerPattern"` } err := owner.Execute(query, map[string]any{ @@ -150,7 +150,7 @@ func TestCookiePattern_Create(t *testing.T) { }, &result) require.NoError(t, err) - node := result.CreateCookiePattern.CookiePatternEdge.Node + node := result.CreateTrackerPattern.TrackerPatternEdge.Node assert.Equal(t, "_gat_", node.Pattern) assert.Equal(t, "PREFIX", node.MatchType) assert.Nil(t, node.MaxAgeSeconds) @@ -163,15 +163,15 @@ func TestCookiePattern_Create(t *testing.T) { bannerID := factory.CreateCookieBanner(owner) categoryID := factory.CreateCookieCategory(owner, bannerID) - factory.CreateCookiePattern(owner, categoryID, factory.Attrs{ + factory.CreateTrackerPattern(owner, categoryID, factory.Attrs{ "pattern": "duplicate_cookie", "displayName": "First", }) _, err := owner.Do(` - mutation CreateCookiePattern($input: CreateCookiePatternInput!) { - createCookiePattern(input: $input) { - cookiePatternEdge { node { id } } + mutation CreateTrackerPattern($input: CreateTrackerPatternInput!) { + createTrackerPattern(input: $input) { + trackerPatternEdge { node { id } } cookieBanner { id } } } @@ -188,7 +188,7 @@ func TestCookiePattern_Create(t *testing.T) { }) } -func TestCookiePattern_Update(t *testing.T) { +func TestTrackerPattern_Update(t *testing.T) { t.Parallel() t.Run("update displayName and description", func(t *testing.T) { @@ -197,15 +197,15 @@ func TestCookiePattern_Update(t *testing.T) { bannerID := factory.CreateCookieBanner(owner) categoryID := factory.CreateCookieCategory(owner, bannerID) - patternID := factory.CreateCookiePattern(owner, categoryID, factory.Attrs{ + patternID := factory.CreateTrackerPattern(owner, categoryID, factory.Attrs{ "displayName": "Original Name", "description": "Original description", }) const query = ` - mutation UpdateCookiePattern($input: UpdateCookiePatternInput!) { - updateCookiePattern(input: $input) { - cookiePattern { + mutation UpdateTrackerPattern($input: UpdateTrackerPatternInput!) { + updateTrackerPattern(input: $input) { + trackerPattern { id displayName description @@ -218,31 +218,31 @@ func TestCookiePattern_Update(t *testing.T) { ` var result struct { - UpdateCookiePattern struct { - CookiePattern struct { + UpdateTrackerPattern struct { + TrackerPattern struct { ID string `json:"id"` DisplayName string `json:"displayName"` Description string `json:"description"` - } `json:"cookiePattern"` + } `json:"trackerPattern"` CookieBanner struct { ID string `json:"id"` } `json:"cookieBanner"` - } `json:"updateCookiePattern"` + } `json:"updateTrackerPattern"` } err := owner.Execute(query, map[string]any{ "input": map[string]any{ - "cookiePatternId": patternID, - "displayName": "Updated Name", - "description": "Updated description", + "trackerPatternId": patternID, + "displayName": "Updated Name", + "description": "Updated description", }, }, &result) require.NoError(t, err) - assert.Equal(t, patternID, result.UpdateCookiePattern.CookiePattern.ID) - assert.Equal(t, "Updated Name", result.UpdateCookiePattern.CookiePattern.DisplayName) - assert.Equal(t, "Updated description", result.UpdateCookiePattern.CookiePattern.Description) - assert.Equal(t, bannerID, result.UpdateCookiePattern.CookieBanner.ID) + assert.Equal(t, patternID, result.UpdateTrackerPattern.TrackerPattern.ID) + assert.Equal(t, "Updated Name", result.UpdateTrackerPattern.TrackerPattern.DisplayName) + assert.Equal(t, "Updated description", result.UpdateTrackerPattern.TrackerPattern.Description) + assert.Equal(t, bannerID, result.UpdateTrackerPattern.CookieBanner.ID) }) t.Run("update maxAgeSeconds", func(t *testing.T) { @@ -251,12 +251,12 @@ func TestCookiePattern_Update(t *testing.T) { bannerID := factory.CreateCookieBanner(owner) categoryID := factory.CreateCookieCategory(owner, bannerID) - patternID := factory.CreateCookiePattern(owner, categoryID) + patternID := factory.CreateTrackerPattern(owner, categoryID) const query = ` - mutation UpdateCookiePattern($input: UpdateCookiePatternInput!) { - updateCookiePattern(input: $input) { - cookiePattern { + mutation UpdateTrackerPattern($input: UpdateTrackerPatternInput!) { + updateTrackerPattern(input: $input) { + trackerPattern { id maxAgeSeconds } @@ -265,28 +265,28 @@ func TestCookiePattern_Update(t *testing.T) { ` var result struct { - UpdateCookiePattern struct { - CookiePattern struct { + UpdateTrackerPattern struct { + TrackerPattern struct { ID string `json:"id"` MaxAgeSeconds *int `json:"maxAgeSeconds"` - } `json:"cookiePattern"` - } `json:"updateCookiePattern"` + } `json:"trackerPattern"` + } `json:"updateTrackerPattern"` } err := owner.Execute(query, map[string]any{ "input": map[string]any{ - "cookiePatternId": patternID, - "maxAgeSeconds": 7200, + "trackerPatternId": patternID, + "maxAgeSeconds": 7200, }, }, &result) require.NoError(t, err) - require.NotNil(t, result.UpdateCookiePattern.CookiePattern.MaxAgeSeconds) - assert.Equal(t, 7200, *result.UpdateCookiePattern.CookiePattern.MaxAgeSeconds) + require.NotNil(t, result.UpdateTrackerPattern.TrackerPattern.MaxAgeSeconds) + assert.Equal(t, 7200, *result.UpdateTrackerPattern.TrackerPattern.MaxAgeSeconds) }) } -func TestCookiePattern_Excluded(t *testing.T) { +func TestTrackerPattern_Excluded(t *testing.T) { t.Parallel() t.Run("defaults to false on create", func(t *testing.T) { @@ -297,9 +297,9 @@ func TestCookiePattern_Excluded(t *testing.T) { categoryID := factory.CreateCookieCategory(owner, bannerID) const query = ` - mutation CreateCookiePattern($input: CreateCookiePatternInput!) { - createCookiePattern(input: $input) { - cookiePatternEdge { + mutation CreateTrackerPattern($input: CreateTrackerPatternInput!) { + createTrackerPattern(input: $input) { + trackerPatternEdge { node { id excluded @@ -310,14 +310,14 @@ func TestCookiePattern_Excluded(t *testing.T) { ` var result struct { - CreateCookiePattern struct { - CookiePatternEdge struct { + CreateTrackerPattern struct { + TrackerPatternEdge struct { Node struct { ID string `json:"id"` Excluded bool `json:"excluded"` } `json:"node"` - } `json:"cookiePatternEdge"` - } `json:"createCookiePattern"` + } `json:"trackerPatternEdge"` + } `json:"createTrackerPattern"` } err := owner.Execute(query, map[string]any{ @@ -331,7 +331,7 @@ func TestCookiePattern_Excluded(t *testing.T) { }, &result) require.NoError(t, err) - assert.False(t, result.CreateCookiePattern.CookiePatternEdge.Node.Excluded) + assert.False(t, result.CreateTrackerPattern.TrackerPatternEdge.Node.Excluded) }) t.Run("can be set to true via update", func(t *testing.T) { @@ -340,12 +340,12 @@ func TestCookiePattern_Excluded(t *testing.T) { bannerID := factory.CreateCookieBanner(owner) categoryID := factory.CreateCookieCategory(owner, bannerID) - patternID := factory.CreateCookiePattern(owner, categoryID) + patternID := factory.CreateTrackerPattern(owner, categoryID) const query = ` - mutation UpdateCookiePattern($input: UpdateCookiePatternInput!) { - updateCookiePattern(input: $input) { - cookiePattern { + mutation UpdateTrackerPattern($input: UpdateTrackerPatternInput!) { + updateTrackerPattern(input: $input) { + trackerPattern { id excluded } @@ -354,23 +354,23 @@ func TestCookiePattern_Excluded(t *testing.T) { ` var result struct { - UpdateCookiePattern struct { - CookiePattern struct { + UpdateTrackerPattern struct { + TrackerPattern struct { ID string `json:"id"` Excluded bool `json:"excluded"` - } `json:"cookiePattern"` - } `json:"updateCookiePattern"` + } `json:"trackerPattern"` + } `json:"updateTrackerPattern"` } err := owner.Execute(query, map[string]any{ "input": map[string]any{ - "cookiePatternId": patternID, - "excluded": true, + "trackerPatternId": patternID, + "excluded": true, }, }, &result) require.NoError(t, err) - assert.True(t, result.UpdateCookiePattern.CookiePattern.Excluded) + assert.True(t, result.UpdateTrackerPattern.TrackerPattern.Excluded) }) t.Run("can be toggled back to false", func(t *testing.T) { @@ -379,12 +379,12 @@ func TestCookiePattern_Excluded(t *testing.T) { bannerID := factory.CreateCookieBanner(owner) categoryID := factory.CreateCookieCategory(owner, bannerID) - patternID := factory.CreateCookiePattern(owner, categoryID) + patternID := factory.CreateTrackerPattern(owner, categoryID) const updateQuery = ` - mutation UpdateCookiePattern($input: UpdateCookiePatternInput!) { - updateCookiePattern(input: $input) { - cookiePattern { + mutation UpdateTrackerPattern($input: UpdateTrackerPatternInput!) { + updateTrackerPattern(input: $input) { + trackerPattern { id excluded } @@ -393,35 +393,35 @@ func TestCookiePattern_Excluded(t *testing.T) { ` var result struct { - UpdateCookiePattern struct { - CookiePattern struct { + UpdateTrackerPattern struct { + TrackerPattern struct { ID string `json:"id"` Excluded bool `json:"excluded"` - } `json:"cookiePattern"` - } `json:"updateCookiePattern"` + } `json:"trackerPattern"` + } `json:"updateTrackerPattern"` } err := owner.Execute(updateQuery, map[string]any{ "input": map[string]any{ - "cookiePatternId": patternID, - "excluded": true, + "trackerPatternId": patternID, + "excluded": true, }, }, &result) require.NoError(t, err) - assert.True(t, result.UpdateCookiePattern.CookiePattern.Excluded) + assert.True(t, result.UpdateTrackerPattern.TrackerPattern.Excluded) err = owner.Execute(updateQuery, map[string]any{ "input": map[string]any{ - "cookiePatternId": patternID, - "excluded": false, + "trackerPatternId": patternID, + "excluded": false, }, }, &result) require.NoError(t, err) - assert.False(t, result.UpdateCookiePattern.CookiePattern.Excluded) + assert.False(t, result.UpdateTrackerPattern.TrackerPattern.Excluded) }) } -func TestCookiePattern_Delete(t *testing.T) { +func TestTrackerPattern_Delete(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -430,12 +430,12 @@ func TestCookiePattern_Delete(t *testing.T) { bannerID := factory.CreateCookieBanner(owner) categoryID := factory.CreateCookieCategory(owner, bannerID) - patternID := factory.CreateCookiePattern(owner, categoryID) + patternID := factory.CreateTrackerPattern(owner, categoryID) const query = ` - mutation DeleteCookiePattern($input: DeleteCookiePatternInput!) { - deleteCookiePattern(input: $input) { - deletedCookiePatternId + mutation DeleteTrackerPattern($input: DeleteTrackerPatternInput!) { + deleteTrackerPattern(input: $input) { + deletedTrackerPatternId cookieBanner { id } @@ -444,25 +444,25 @@ func TestCookiePattern_Delete(t *testing.T) { ` var result struct { - DeleteCookiePattern struct { - DeletedCookiePatternID string `json:"deletedCookiePatternId"` - CookieBanner struct { + DeleteTrackerPattern struct { + DeletedTrackerPatternID string `json:"deletedTrackerPatternId"` + CookieBanner struct { ID string `json:"id"` } `json:"cookieBanner"` - } `json:"deleteCookiePattern"` + } `json:"deleteTrackerPattern"` } err := owner.Execute(query, map[string]any{ - "input": map[string]any{"cookiePatternId": patternID}, + "input": map[string]any{"trackerPatternId": patternID}, }, &result) require.NoError(t, err) - assert.Equal(t, patternID, result.DeleteCookiePattern.DeletedCookiePatternID) - assert.Equal(t, bannerID, result.DeleteCookiePattern.CookieBanner.ID) + assert.Equal(t, patternID, result.DeleteTrackerPattern.DeletedTrackerPatternID) + assert.Equal(t, bannerID, result.DeleteTrackerPattern.CookieBanner.ID) }) } -func TestCookiePattern_MoveToCategory(t *testing.T) { +func TestTrackerPattern_MoveToCategory(t *testing.T) { t.Parallel() t.Run("success", func(t *testing.T) { @@ -472,12 +472,12 @@ func TestCookiePattern_MoveToCategory(t *testing.T) { bannerID := factory.CreateCookieBanner(owner) categoryA := factory.CreateCookieCategory(owner, bannerID, factory.Attrs{"slug": "cat-a-move"}) categoryB := factory.CreateCookieCategory(owner, bannerID, factory.Attrs{"slug": "cat-b-move"}) - patternID := factory.CreateCookiePattern(owner, categoryA) + patternID := factory.CreateTrackerPattern(owner, categoryA) const query = ` - mutation MoveCookiePatternToCategory($input: MoveCookiePatternToCategoryInput!) { - moveCookiePatternToCategory(input: $input) { - cookiePattern { + mutation MoveTrackerPatternToCategory($input: MoveTrackerPatternToCategoryInput!) { + moveTrackerPatternToCategory(input: $input) { + trackerPattern { id cookieCategory { id @@ -491,30 +491,30 @@ func TestCookiePattern_MoveToCategory(t *testing.T) { ` var result struct { - MoveCookiePatternToCategory struct { - CookiePattern struct { + MoveTrackerPatternToCategory struct { + TrackerPattern struct { ID string `json:"id"` CookieCategory struct { ID string `json:"id"` } `json:"cookieCategory"` - } `json:"cookiePattern"` + } `json:"trackerPattern"` CookieBanner struct { ID string `json:"id"` } `json:"cookieBanner"` - } `json:"moveCookiePatternToCategory"` + } `json:"moveTrackerPatternToCategory"` } err := owner.Execute(query, map[string]any{ "input": map[string]any{ - "cookiePatternId": patternID, + "trackerPatternId": patternID, "targetCookieCategoryId": categoryB, }, }, &result) require.NoError(t, err) - assert.Equal(t, patternID, result.MoveCookiePatternToCategory.CookiePattern.ID) - assert.Equal(t, categoryB, result.MoveCookiePatternToCategory.CookiePattern.CookieCategory.ID) - assert.Equal(t, bannerID, result.MoveCookiePatternToCategory.CookieBanner.ID) + assert.Equal(t, patternID, result.MoveTrackerPatternToCategory.TrackerPattern.ID) + assert.Equal(t, categoryB, result.MoveTrackerPatternToCategory.TrackerPattern.CookieCategory.ID) + assert.Equal(t, bannerID, result.MoveTrackerPatternToCategory.CookieBanner.ID) }) t.Run("cross-banner mismatch error", func(t *testing.T) { @@ -525,18 +525,18 @@ func TestCookiePattern_MoveToCategory(t *testing.T) { banner2 := factory.CreateCookieBanner(owner) category1 := factory.CreateCookieCategory(owner, banner1, factory.Attrs{"slug": "cat-x-mismatch"}) category2 := factory.CreateCookieCategory(owner, banner2, factory.Attrs{"slug": "cat-y-mismatch"}) - patternID := factory.CreateCookiePattern(owner, category1) + patternID := factory.CreateTrackerPattern(owner, category1) _, err := owner.Do(` - mutation MoveCookiePatternToCategory($input: MoveCookiePatternToCategoryInput!) { - moveCookiePatternToCategory(input: $input) { - cookiePattern { id } + mutation MoveTrackerPatternToCategory($input: MoveTrackerPatternToCategoryInput!) { + moveTrackerPatternToCategory(input: $input) { + trackerPattern { id } cookieBanner { id } } } `, map[string]any{ "input": map[string]any{ - "cookiePatternId": patternID, + "trackerPatternId": patternID, "targetCookieCategoryId": category2, }, }) @@ -544,23 +544,23 @@ func TestCookiePattern_MoveToCategory(t *testing.T) { }) } -func TestCookiePattern_List(t *testing.T) { +func TestTrackerPattern_List(t *testing.T) { t.Parallel() - t.Run("via category cookiePatterns connection", func(t *testing.T) { + t.Run("via category trackerPatterns connection", func(t *testing.T) { t.Parallel() owner := testutil.NewClient(t, testutil.RoleOwner) bannerID := factory.CreateCookieBanner(owner) categoryID := factory.CreateCookieCategory(owner, bannerID) - factory.CreateCookiePattern(owner, categoryID) - factory.CreateCookiePattern(owner, categoryID) + factory.CreateTrackerPattern(owner, categoryID) + factory.CreateTrackerPattern(owner, categoryID) const query = ` query($id: ID!) { node(id: $id) { ... on CookieCategory { - cookiePatterns(first: 10) { + trackerPatterns(first: 10) { totalCount edges { node { @@ -581,7 +581,7 @@ func TestCookiePattern_List(t *testing.T) { var result struct { Node struct { - CookiePatterns struct { + TrackerPatterns struct { TotalCount int `json:"totalCount"` Edges []struct { Node struct { @@ -594,18 +594,18 @@ func TestCookiePattern_List(t *testing.T) { HasNextPage bool `json:"hasNextPage"` HasPreviousPage bool `json:"hasPreviousPage"` } `json:"pageInfo"` - } `json:"cookiePatterns"` + } `json:"trackerPatterns"` } `json:"node"` } err := owner.Execute(query, map[string]any{"id": categoryID}, &result) require.NoError(t, err) - assert.Equal(t, 2, result.Node.CookiePatterns.TotalCount) - assert.Len(t, result.Node.CookiePatterns.Edges, 2) + assert.Equal(t, 2, result.Node.TrackerPatterns.TotalCount) + assert.Len(t, result.Node.TrackerPatterns.Edges, 2) }) } -func TestCookiePattern_RBAC(t *testing.T) { +func TestTrackerPattern_RBAC(t *testing.T) { t.Parallel() t.Run("viewer cannot create pattern", func(t *testing.T) { @@ -617,9 +617,9 @@ func TestCookiePattern_RBAC(t *testing.T) { categoryID := factory.CreateCookieCategory(owner, bannerID) _, err := viewer.Do(` - mutation CreateCookiePattern($input: CreateCookiePatternInput!) { - createCookiePattern(input: $input) { - cookiePatternEdge { node { id } } + mutation CreateTrackerPattern($input: CreateTrackerPatternInput!) { + createTrackerPattern(input: $input) { + trackerPatternEdge { node { id } } cookieBanner { id } } } @@ -632,7 +632,7 @@ func TestCookiePattern_RBAC(t *testing.T) { "description": "Should fail", }, }) - testutil.RequireForbiddenError(t, err, "viewer should not be able to create cookie pattern") + testutil.RequireForbiddenError(t, err, "viewer should not be able to create tracker pattern") }) t.Run("viewer cannot update pattern", func(t *testing.T) { @@ -642,22 +642,22 @@ func TestCookiePattern_RBAC(t *testing.T) { bannerID := factory.CreateCookieBanner(owner) categoryID := factory.CreateCookieCategory(owner, bannerID) - patternID := factory.CreateCookiePattern(owner, categoryID) + patternID := factory.CreateTrackerPattern(owner, categoryID) _, err := viewer.Do(` - mutation UpdateCookiePattern($input: UpdateCookiePatternInput!) { - updateCookiePattern(input: $input) { - cookiePattern { id } + mutation UpdateTrackerPattern($input: UpdateTrackerPatternInput!) { + updateTrackerPattern(input: $input) { + trackerPattern { id } cookieBanner { id } } } `, map[string]any{ "input": map[string]any{ - "cookiePatternId": patternID, - "displayName": "Updated by Viewer", + "trackerPatternId": patternID, + "displayName": "Updated by Viewer", }, }) - testutil.RequireForbiddenError(t, err, "viewer should not be able to update cookie pattern") + testutil.RequireForbiddenError(t, err, "viewer should not be able to update tracker pattern") }) t.Run("viewer cannot delete pattern", func(t *testing.T) { @@ -667,19 +667,19 @@ func TestCookiePattern_RBAC(t *testing.T) { bannerID := factory.CreateCookieBanner(owner) categoryID := factory.CreateCookieCategory(owner, bannerID) - patternID := factory.CreateCookiePattern(owner, categoryID) + patternID := factory.CreateTrackerPattern(owner, categoryID) _, err := viewer.Do(` - mutation DeleteCookiePattern($input: DeleteCookiePatternInput!) { - deleteCookiePattern(input: $input) { - deletedCookiePatternId + mutation DeleteTrackerPattern($input: DeleteTrackerPatternInput!) { + deleteTrackerPattern(input: $input) { + deletedTrackerPatternId cookieBanner { id } } } `, map[string]any{ - "input": map[string]any{"cookiePatternId": patternID}, + "input": map[string]any{"trackerPatternId": patternID}, }) - testutil.RequireForbiddenError(t, err, "viewer should not be able to delete cookie pattern") + testutil.RequireForbiddenError(t, err, "viewer should not be able to delete tracker pattern") }) t.Run("viewer cannot move pattern", func(t *testing.T) { @@ -690,21 +690,21 @@ func TestCookiePattern_RBAC(t *testing.T) { bannerID := factory.CreateCookieBanner(owner) categoryA := factory.CreateCookieCategory(owner, bannerID, factory.Attrs{"slug": "rbac-move-a"}) categoryB := factory.CreateCookieCategory(owner, bannerID, factory.Attrs{"slug": "rbac-move-b"}) - patternID := factory.CreateCookiePattern(owner, categoryA) + patternID := factory.CreateTrackerPattern(owner, categoryA) _, err := viewer.Do(` - mutation MoveCookiePatternToCategory($input: MoveCookiePatternToCategoryInput!) { - moveCookiePatternToCategory(input: $input) { - cookiePattern { id } + mutation MoveTrackerPatternToCategory($input: MoveTrackerPatternToCategoryInput!) { + moveTrackerPatternToCategory(input: $input) { + trackerPattern { id } cookieBanner { id } } } `, map[string]any{ "input": map[string]any{ - "cookiePatternId": patternID, + "trackerPatternId": patternID, "targetCookieCategoryId": categoryB, }, }) - testutil.RequireForbiddenError(t, err, "viewer should not be able to move cookie pattern") + testutil.RequireForbiddenError(t, err, "viewer should not be able to move tracker pattern") }) } diff --git a/e2e/internal/factory/factory.go b/e2e/internal/factory/factory.go index 918ea2e55..0d8d3e72f 100644 --- a/e2e/internal/factory/factory.go +++ b/e2e/internal/factory/factory.go @@ -1343,7 +1343,7 @@ func CreateCookieCategory(c *testutil.Client, bannerID string, attrs ...Attrs) s return result.CreateCookieCategory.CookieCategoryEdge.Node.ID } -func CreateCookiePattern(c *testutil.Client, categoryID string, attrs ...Attrs) string { +func CreateTrackerPattern(c *testutil.Client, categoryID string, attrs ...Attrs) string { c.T.Helper() var a Attrs @@ -1352,9 +1352,9 @@ func CreateCookiePattern(c *testutil.Client, categoryID string, attrs ...Attrs) } const query = ` - mutation($input: CreateCookiePatternInput!) { - createCookiePattern(input: $input) { - cookiePatternEdge { + mutation($input: CreateTrackerPatternInput!) { + createTrackerPattern(input: $input) { + trackerPatternEdge { node { id } } } @@ -1363,27 +1363,28 @@ func CreateCookiePattern(c *testutil.Client, categoryID string, attrs ...Attrs) input := map[string]any{ "cookieCategoryId": categoryID, + "trackerType": a.getString("trackerType", "COOKIE"), "pattern": a.getString("pattern", gofakeit.LetterN(8)+"_cookie"), "matchType": a.getString("matchType", "EXACT"), "displayName": a.getString("displayName", SafeName("Pattern")), - "description": a.getString("description", "Test cookie pattern"), + "description": a.getString("description", "Test tracker pattern"), } if _, ok := a["maxAgeSeconds"]; ok { input["maxAgeSeconds"] = a.getInt("maxAgeSeconds", 0) } var result struct { - CreateCookiePattern struct { - CookiePatternEdge struct { + CreateTrackerPattern struct { + TrackerPatternEdge struct { Node struct { ID string `json:"id"` } `json:"node"` - } `json:"cookiePatternEdge"` - } `json:"createCookiePattern"` + } `json:"trackerPatternEdge"` + } `json:"createTrackerPattern"` } err := c.Execute(query, map[string]any{"input": input}, &result) - require.NoError(c.T, err, "createCookiePattern mutation failed") + require.NoError(c.T, err, "createTrackerPattern mutation failed") - return result.CreateCookiePattern.CookiePatternEdge.Node.ID + return result.CreateTrackerPattern.TrackerPatternEdge.Node.ID } diff --git a/packages/n8n-node/nodes/Probo/Probo.node.ts b/packages/n8n-node/nodes/Probo/Probo.node.ts index 7e5ea11b4..78d4b60bc 100644 --- a/packages/n8n-node/nodes/Probo/Probo.node.ts +++ b/packages/n8n-node/nodes/Probo/Probo.node.ts @@ -116,11 +116,6 @@ export class Probo implements INodeType { value: 'cookieConsentRecord', description: 'View cookie consent records', }, - { - name: 'Cookie Pattern', - value: 'cookiePattern', - description: 'Manage cookie patterns', - }, { name: 'Data', value: 'datum', @@ -206,6 +201,11 @@ export class Probo implements INodeType { value: 'tia', description: 'Manage transfer impact assessments', }, + { + name: 'Tracker Pattern', + value: 'trackerPattern', + description: 'Manage tracker patterns', + }, { name: 'Trust Center', value: 'trustCenter', diff --git a/packages/n8n-node/nodes/Probo/actions/index.ts b/packages/n8n-node/nodes/Probo/actions/index.ts index 6d80f6589..75dcc7a09 100644 --- a/packages/n8n-node/nodes/Probo/actions/index.ts +++ b/packages/n8n-node/nodes/Probo/actions/index.ts @@ -21,7 +21,7 @@ import * as control from './control'; import * as cookieBanner from './cookieBanner'; import * as cookieCategory from './cookieCategory'; import * as cookieConsentRecord from './cookieConsentRecord'; -import * as cookiePattern from './cookiePattern'; +import * as trackerPattern from './trackerPattern'; import * as datum from './datum'; import * as document from './document'; import * as dpia from './dpia'; @@ -63,7 +63,7 @@ export const resources: Record = { cookieBanner: cookieBanner as ResourceModule, cookieCategory: cookieCategory as ResourceModule, cookieConsentRecord: cookieConsentRecord as ResourceModule, - cookiePattern: cookiePattern as ResourceModule, + trackerPattern: trackerPattern as ResourceModule, datum: datum as ResourceModule, document: document as ResourceModule, dpia: dpia as ResourceModule, diff --git a/packages/n8n-node/nodes/Probo/actions/cookiePattern/create.operation.ts b/packages/n8n-node/nodes/Probo/actions/trackerPattern/create.operation.ts similarity index 85% rename from packages/n8n-node/nodes/Probo/actions/cookiePattern/create.operation.ts rename to packages/n8n-node/nodes/Probo/actions/trackerPattern/create.operation.ts index b6ac54a66..5eea17504 100644 --- a/packages/n8n-node/nodes/Probo/actions/cookiePattern/create.operation.ts +++ b/packages/n8n-node/nodes/Probo/actions/trackerPattern/create.operation.ts @@ -22,7 +22,7 @@ export const description: INodeProperties[] = [ type: 'string', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['create'], }, }, @@ -36,12 +36,12 @@ export const description: INodeProperties[] = [ type: 'string', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['create'], }, }, default: '', - description: 'The cookie name pattern to match', + description: 'The tracker name pattern to match', required: true, }, { @@ -50,7 +50,7 @@ export const description: INodeProperties[] = [ type: 'options', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['create'], }, }, @@ -65,7 +65,7 @@ export const description: INodeProperties[] = [ }, ], default: 'EXACT', - description: 'How the pattern should be matched against cookie names', + description: 'How the pattern should be matched against tracker names', required: true, }, { @@ -74,12 +74,12 @@ export const description: INodeProperties[] = [ type: 'string', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['create'], }, }, default: '', - description: 'The display name for the cookie pattern', + description: 'The display name for the tracker pattern', required: true, }, { @@ -88,12 +88,12 @@ export const description: INodeProperties[] = [ type: 'string', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['create'], }, }, default: '', - description: 'The description of the cookie pattern', + description: 'The description of the tracker pattern', required: true, }, { @@ -102,7 +102,7 @@ export const description: INodeProperties[] = [ type: 'number', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['create'], }, }, @@ -123,9 +123,9 @@ export async function execute( const maxAgeSeconds = this.getNodeParameter('maxAgeSeconds', itemIndex, 0) as number; const query = ` - mutation CreateCookiePattern($input: CreateCookiePatternInput!) { - createCookiePattern(input: $input) { - cookiePatternEdge { + mutation CreateTrackerPattern($input: CreateTrackerPatternInput!) { + createTrackerPattern(input: $input) { + trackerPatternEdge { node { id pattern diff --git a/packages/n8n-node/nodes/Probo/actions/cookiePattern/delete.operation.ts b/packages/n8n-node/nodes/Probo/actions/trackerPattern/delete.operation.ts similarity index 77% rename from packages/n8n-node/nodes/Probo/actions/cookiePattern/delete.operation.ts rename to packages/n8n-node/nodes/Probo/actions/trackerPattern/delete.operation.ts index 96968c084..cb0842a23 100644 --- a/packages/n8n-node/nodes/Probo/actions/cookiePattern/delete.operation.ts +++ b/packages/n8n-node/nodes/Probo/actions/trackerPattern/delete.operation.ts @@ -17,17 +17,17 @@ import { proboApiRequest } from '../../GenericFunctions'; export const description: INodeProperties[] = [ { - displayName: 'Cookie Pattern ID', - name: 'cookiePatternId', + displayName: 'Tracker Pattern ID', + name: 'trackerPatternId', type: 'string', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['delete'], }, }, default: '', - description: 'The ID of the cookie pattern to delete', + description: 'The ID of the tracker pattern to delete', required: true, }, ]; @@ -36,12 +36,12 @@ export async function execute( this: IExecuteFunctions, itemIndex: number, ): Promise { - const cookiePatternId = this.getNodeParameter('cookiePatternId', itemIndex) as string; + const trackerPatternId = this.getNodeParameter('trackerPatternId', itemIndex) as string; const query = ` - mutation DeleteCookiePattern($input: DeleteCookiePatternInput!) { - deleteCookiePattern(input: $input) { - deletedCookiePatternId + mutation DeleteTrackerPattern($input: DeleteTrackerPatternInput!) { + deleteTrackerPattern(input: $input) { + deletedTrackerPatternId cookieBanner { id name @@ -50,7 +50,7 @@ export async function execute( } `; - const responseData = await proboApiRequest.call(this, query, { input: { cookiePatternId } }); + const responseData = await proboApiRequest.call(this, query, { input: { trackerPatternId } }); return { json: responseData, diff --git a/packages/n8n-node/nodes/Probo/actions/cookiePattern/get.operation.ts b/packages/n8n-node/nodes/Probo/actions/trackerPattern/get.operation.ts similarity index 80% rename from packages/n8n-node/nodes/Probo/actions/cookiePattern/get.operation.ts rename to packages/n8n-node/nodes/Probo/actions/trackerPattern/get.operation.ts index b6aff69e2..f501f3559 100644 --- a/packages/n8n-node/nodes/Probo/actions/cookiePattern/get.operation.ts +++ b/packages/n8n-node/nodes/Probo/actions/trackerPattern/get.operation.ts @@ -17,17 +17,17 @@ import { proboApiRequest } from '../../GenericFunctions'; export const description: INodeProperties[] = [ { - displayName: 'Cookie Pattern ID', - name: 'cookiePatternId', + displayName: 'Tracker Pattern ID', + name: 'trackerPatternId', type: 'string', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['get'], }, }, default: '', - description: 'The ID of the cookie pattern', + description: 'The ID of the tracker pattern', required: true, }, ]; @@ -36,12 +36,12 @@ export async function execute( this: IExecuteFunctions, itemIndex: number, ): Promise { - const cookiePatternId = this.getNodeParameter('cookiePatternId', itemIndex) as string; + const trackerPatternId = this.getNodeParameter('trackerPatternId', itemIndex) as string; const query = ` - query GetCookiePattern($cookiePatternId: ID!) { - node(id: $cookiePatternId) { - ... on CookiePattern { + query GetTrackerPattern($trackerPatternId: ID!) { + node(id: $trackerPatternId) { + ... on TrackerPattern { id pattern matchType @@ -58,7 +58,7 @@ export async function execute( } `; - const responseData = await proboApiRequest.call(this, query, { cookiePatternId }); + const responseData = await proboApiRequest.call(this, query, { trackerPatternId }); return { json: responseData, diff --git a/packages/n8n-node/nodes/Probo/actions/cookiePattern/getAll.operation.ts b/packages/n8n-node/nodes/Probo/actions/trackerPattern/getAll.operation.ts similarity index 87% rename from packages/n8n-node/nodes/Probo/actions/cookiePattern/getAll.operation.ts rename to packages/n8n-node/nodes/Probo/actions/trackerPattern/getAll.operation.ts index c5d0ccac4..0d507ff9d 100644 --- a/packages/n8n-node/nodes/Probo/actions/cookiePattern/getAll.operation.ts +++ b/packages/n8n-node/nodes/Probo/actions/trackerPattern/getAll.operation.ts @@ -22,7 +22,7 @@ export const description: INodeProperties[] = [ type: 'string', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['getAll'], }, }, @@ -36,7 +36,7 @@ export const description: INodeProperties[] = [ type: 'boolean', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['getAll'], }, }, @@ -49,7 +49,7 @@ export const description: INodeProperties[] = [ type: 'number', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['getAll'], returnAll: [false], }, @@ -71,10 +71,10 @@ export async function execute( const limit = this.getNodeParameter('limit', itemIndex, 50) as number; const query = ` - query GetCookiePatterns($cookieCategoryId: ID!, $first: Int, $after: CursorKey) { + query GetTrackerPatterns($cookieCategoryId: ID!, $first: Int, $after: CursorKey) { node(id: $cookieCategoryId) { ... on CookieCategory { - cookiePatterns(first: $first, after: $after) { + trackerPatterns(first: $first, after: $after) { edges { node { id @@ -100,21 +100,21 @@ export async function execute( } `; - const cookiePatterns = await proboApiRequestAllItems.call( + const trackerPatterns = await proboApiRequestAllItems.call( this, query, { cookieCategoryId }, (response) => { const data = response?.data as IDataObject | undefined; const node = data?.node as IDataObject | undefined; - return node?.cookiePatterns as IDataObject | undefined; + return node?.trackerPatterns as IDataObject | undefined; }, returnAll, limit, ); return { - json: { cookiePatterns }, + json: { trackerPatterns }, pairedItem: { item: itemIndex }, }; } diff --git a/packages/n8n-node/nodes/Probo/actions/cookiePattern/index.ts b/packages/n8n-node/nodes/Probo/actions/trackerPattern/index.ts similarity index 77% rename from packages/n8n-node/nodes/Probo/actions/cookiePattern/index.ts rename to packages/n8n-node/nodes/Probo/actions/trackerPattern/index.ts index 0007db154..0b80fd54f 100644 --- a/packages/n8n-node/nodes/Probo/actions/cookiePattern/index.ts +++ b/packages/n8n-node/nodes/Probo/actions/trackerPattern/index.ts @@ -28,45 +28,45 @@ export const description: INodeProperties[] = [ noDataExpression: true, displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], }, }, options: [ { name: 'Create', value: 'create', - description: 'Create a new cookie pattern', - action: 'Create a cookie pattern', + description: 'Create a new tracker pattern', + action: 'Create a tracker pattern', }, { name: 'Delete', value: 'delete', - description: 'Delete a cookie pattern', - action: 'Delete a cookie pattern', + description: 'Delete a tracker pattern', + action: 'Delete a tracker pattern', }, { name: 'Get', value: 'get', - description: 'Get a cookie pattern', - action: 'Get a cookie pattern', + description: 'Get a tracker pattern', + action: 'Get a tracker pattern', }, { name: 'Get Many', value: 'getAll', - description: 'Get many cookie patterns', - action: 'Get many cookie patterns', + description: 'Get many tracker patterns', + action: 'Get many tracker patterns', }, { name: 'Move', value: 'move', - description: 'Move a cookie pattern to a different category', - action: 'Move a cookie pattern', + description: 'Move a tracker pattern to a different category', + action: 'Move a tracker pattern', }, { name: 'Update', value: 'update', - description: 'Update an existing cookie pattern', - action: 'Update a cookie pattern', + description: 'Update an existing tracker pattern', + action: 'Update a tracker pattern', }, ], default: 'create', diff --git a/packages/n8n-node/nodes/Probo/actions/cookiePattern/move.operation.ts b/packages/n8n-node/nodes/Probo/actions/trackerPattern/move.operation.ts similarity index 79% rename from packages/n8n-node/nodes/Probo/actions/cookiePattern/move.operation.ts rename to packages/n8n-node/nodes/Probo/actions/trackerPattern/move.operation.ts index 8763958ff..068f89160 100644 --- a/packages/n8n-node/nodes/Probo/actions/cookiePattern/move.operation.ts +++ b/packages/n8n-node/nodes/Probo/actions/trackerPattern/move.operation.ts @@ -17,17 +17,17 @@ import { proboApiRequest } from '../../GenericFunctions'; export const description: INodeProperties[] = [ { - displayName: 'Cookie Pattern ID', - name: 'cookiePatternId', + displayName: 'Tracker Pattern ID', + name: 'trackerPatternId', type: 'string', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['move'], }, }, default: '', - description: 'The ID of the cookie pattern to move', + description: 'The ID of the tracker pattern to move', required: true, }, { @@ -36,7 +36,7 @@ export const description: INodeProperties[] = [ type: 'string', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['move'], }, }, @@ -50,13 +50,13 @@ export async function execute( this: IExecuteFunctions, itemIndex: number, ): Promise { - const cookiePatternId = this.getNodeParameter('cookiePatternId', itemIndex) as string; + const trackerPatternId = this.getNodeParameter('trackerPatternId', itemIndex) as string; const targetCookieCategoryId = this.getNodeParameter('targetCookieCategoryId', itemIndex) as string; const query = ` - mutation MoveCookiePatternToCategory($input: MoveCookiePatternToCategoryInput!) { - moveCookiePatternToCategory(input: $input) { - cookiePattern { + mutation MoveTrackerPatternToCategory($input: MoveTrackerPatternToCategoryInput!) { + moveTrackerPatternToCategory(input: $input) { + trackerPattern { id pattern matchType @@ -77,7 +77,7 @@ export async function execute( `; const responseData = await proboApiRequest.call(this, query, { - input: { cookiePatternId, targetCookieCategoryId }, + input: { trackerPatternId, targetCookieCategoryId }, }); return { diff --git a/packages/n8n-node/nodes/Probo/actions/cookiePattern/update.operation.ts b/packages/n8n-node/nodes/Probo/actions/trackerPattern/update.operation.ts similarity index 81% rename from packages/n8n-node/nodes/Probo/actions/cookiePattern/update.operation.ts rename to packages/n8n-node/nodes/Probo/actions/trackerPattern/update.operation.ts index ac2a25c7c..cee2fb5b6 100644 --- a/packages/n8n-node/nodes/Probo/actions/cookiePattern/update.operation.ts +++ b/packages/n8n-node/nodes/Probo/actions/trackerPattern/update.operation.ts @@ -17,17 +17,17 @@ import { proboApiRequest } from '../../GenericFunctions'; export const description: INodeProperties[] = [ { - displayName: 'Cookie Pattern ID', - name: 'cookiePatternId', + displayName: 'Tracker Pattern ID', + name: 'trackerPatternId', type: 'string', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['update'], }, }, default: '', - description: 'The ID of the cookie pattern to update', + description: 'The ID of the tracker pattern to update', required: true, }, { @@ -36,12 +36,12 @@ export const description: INodeProperties[] = [ type: 'string', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['update'], }, }, default: '', - description: 'The display name for the cookie pattern', + description: 'The display name for the tracker pattern', }, { displayName: 'Excluded', @@ -49,7 +49,7 @@ export const description: INodeProperties[] = [ type: 'options', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['update'], }, }, @@ -68,7 +68,7 @@ export const description: INodeProperties[] = [ }, ], default: '', - description: 'Whether the cookie pattern is excluded from the banner', + description: 'Whether the tracker pattern is excluded from the banner', }, { displayName: 'Description', @@ -76,12 +76,12 @@ export const description: INodeProperties[] = [ type: 'string', displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['update'], }, }, default: '', - description: 'The description of the cookie pattern', + description: 'The description of the tracker pattern', }, { displayName: 'Additional Fields', @@ -91,7 +91,7 @@ export const description: INodeProperties[] = [ default: {}, displayOptions: { show: { - resource: ['cookiePattern'], + resource: ['trackerPattern'], operation: ['update'], }, }, @@ -114,7 +114,7 @@ export async function execute( this: IExecuteFunctions, itemIndex: number, ): Promise { - const cookiePatternId = this.getNodeParameter('cookiePatternId', itemIndex) as string; + const trackerPatternId = this.getNodeParameter('trackerPatternId', itemIndex) as string; const displayName = this.getNodeParameter('displayName', itemIndex, '') as string; const excluded = this.getNodeParameter('excluded', itemIndex, '') as string; const patternDescription = this.getNodeParameter('patternDescription', itemIndex, '') as string; @@ -123,9 +123,9 @@ export async function execute( }; const query = ` - mutation UpdateCookiePattern($input: UpdateCookiePatternInput!) { - updateCookiePattern(input: $input) { - cookiePattern { + mutation UpdateTrackerPattern($input: UpdateTrackerPatternInput!) { + updateTrackerPattern(input: $input) { + trackerPattern { id pattern matchType @@ -145,7 +145,7 @@ export async function execute( } `; - const input: Record = { cookiePatternId }; + const input: Record = { trackerPatternId }; if (displayName) input.displayName = displayName; if (excluded) input.excluded = excluded === 'true'; if (patternDescription) input.description = patternDescription; diff --git a/pkg/cmd/root/root.go b/pkg/cmd/root/root.go index a0584946d..8d6c9f084 100644 --- a/pkg/cmd/root/root.go +++ b/pkg/cmd/root/root.go @@ -31,7 +31,6 @@ import ( "go.probo.inc/probo/pkg/cmd/control" cookiebanner "go.probo.inc/probo/pkg/cmd/cookie-banner" cookiecategory "go.probo.inc/probo/pkg/cmd/cookie-category" - cookiepattern "go.probo.inc/probo/pkg/cmd/cookie-pattern" "go.probo.inc/probo/pkg/cmd/datum" "go.probo.inc/probo/pkg/cmd/document" "go.probo.inc/probo/pkg/cmd/dpia" @@ -48,6 +47,7 @@ import ( "go.probo.inc/probo/pkg/cmd/soa" "go.probo.inc/probo/pkg/cmd/task" "go.probo.inc/probo/pkg/cmd/tia" + trackerpattern "go.probo.inc/probo/pkg/cmd/tracker-pattern" trustcenter "go.probo.inc/probo/pkg/cmd/trust-center" "go.probo.inc/probo/pkg/cmd/user" "go.probo.inc/probo/pkg/cmd/vendormgmt" @@ -99,7 +99,7 @@ func NewCmdRoot(f *cmdutil.Factory) *cobra.Command { cmd.AddCommand(control.NewCmdControl(f)) cmd.AddCommand(cookiebanner.NewCmdCookieBanner(f)) cmd.AddCommand(cookiecategory.NewCmdCookieCategory(f)) - cmd.AddCommand(cookiepattern.NewCmdCookiePattern(f)) + cmd.AddCommand(trackerpattern.NewCmdTrackerPattern(f)) cmd.AddCommand(datum.NewCmdDatum(f)) cmd.AddCommand(document.NewCmdDocument(f)) cmd.AddCommand(dpia.NewCmdDPIA(f)) diff --git a/pkg/cmd/cookie-pattern/create/create.go b/pkg/cmd/tracker-pattern/create/create.go similarity index 86% rename from pkg/cmd/cookie-pattern/create/create.go rename to pkg/cmd/tracker-pattern/create/create.go index ca744e3e5..59aa8ae73 100644 --- a/pkg/cmd/cookie-pattern/create/create.go +++ b/pkg/cmd/tracker-pattern/create/create.go @@ -25,9 +25,9 @@ import ( ) const createMutation = ` -mutation($input: CreateCookiePatternInput!) { - createCookiePattern(input: $input) { - cookiePatternEdge { +mutation($input: CreateTrackerPatternInput!) { + createTrackerPattern(input: $input) { + trackerPatternEdge { node { id pattern @@ -42,15 +42,15 @@ mutation($input: CreateCookiePatternInput!) { ` type createResponse struct { - CreateCookiePattern struct { - CookiePatternEdge struct { + CreateTrackerPattern struct { + TrackerPatternEdge struct { Node struct { ID string `json:"id"` Pattern string `json:"pattern"` DisplayName string `json:"displayName"` } `json:"node"` - } `json:"cookiePatternEdge"` - } `json:"createCookiePattern"` + } `json:"trackerPatternEdge"` + } `json:"createTrackerPattern"` } func NewCmdCreate(f *cmdutil.Factory) *cobra.Command { @@ -65,7 +65,7 @@ func NewCmdCreate(f *cmdutil.Factory) *cobra.Command { cmd := &cobra.Command{ Use: "create", - Short: "Create a new cookie pattern", + Short: "Create a new tracker pattern", RunE: func(cmd *cobra.Command, args []string) error { cfg, err := f.Config() if err != nil { @@ -87,7 +87,7 @@ func NewCmdCreate(f *cmdutil.Factory) *cobra.Command { if f.IOStreams.IsInteractive() { if flagPattern == "" { - if err := huh.NewInput().Title("Cookie pattern").Value(&flagPattern).Run(); err != nil { + if err := huh.NewInput().Title("Tracker pattern").Value(&flagPattern).Run(); err != nil { return err } } @@ -142,8 +142,8 @@ func NewCmdCreate(f *cmdutil.Factory) *cobra.Command { return fmt.Errorf("cannot parse response: %w", err) } - p := resp.CreateCookiePattern.CookiePatternEdge.Node - _, _ = fmt.Fprintf(f.IOStreams.Out, "Created cookie pattern %s (%s)\n", p.ID, p.DisplayName) + p := resp.CreateTrackerPattern.TrackerPatternEdge.Node + _, _ = fmt.Fprintf(f.IOStreams.Out, "Created tracker pattern %s (%s)\n", p.ID, p.DisplayName) return nil }, @@ -151,7 +151,7 @@ func NewCmdCreate(f *cmdutil.Factory) *cobra.Command { cmd.Flags().StringVar(&flagCategoryID, "category-id", "", "Cookie category ID (required)") _ = cmd.MarkFlagRequired("category-id") - cmd.Flags().StringVar(&flagPattern, "pattern", "", "Cookie pattern (required)") + cmd.Flags().StringVar(&flagPattern, "pattern", "", "Tracker pattern (required)") cmd.Flags().StringVar(&flagMatchType, "match-type", "", "Match type: EXACT or PREFIX (required)") cmd.Flags().StringVar(&flagDisplayName, "display-name", "", "Display name (required)") cmd.Flags().StringVar(&flagDescription, "description", "", "Description") diff --git a/pkg/cmd/cookie-pattern/delete/delete.go b/pkg/cmd/tracker-pattern/delete/delete.go similarity index 78% rename from pkg/cmd/cookie-pattern/delete/delete.go rename to pkg/cmd/tracker-pattern/delete/delete.go index e0b456e82..d55e4ce7e 100644 --- a/pkg/cmd/cookie-pattern/delete/delete.go +++ b/pkg/cmd/tracker-pattern/delete/delete.go @@ -24,9 +24,9 @@ import ( ) const deleteMutation = ` -mutation($input: DeleteCookiePatternInput!) { - deleteCookiePattern(input: $input) { - deletedCookiePatternId +mutation($input: DeleteTrackerPatternInput!) { + deleteTrackerPattern(input: $input) { + deletedTrackerPatternId cookieBanner { id } @@ -39,15 +39,15 @@ func NewCmdDelete(f *cmdutil.Factory) *cobra.Command { cmd := &cobra.Command{ Use: "delete ", - Short: "Delete a cookie pattern", + Short: "Delete a tracker pattern", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if !flagYes { if !f.IOStreams.IsInteractive() { - return fmt.Errorf("cannot delete cookie pattern: confirmation required, use --yes to confirm") + return fmt.Errorf("cannot delete tracker pattern: confirmation required, use --yes to confirm") } var confirmed bool - if err := huh.NewConfirm().Title(fmt.Sprintf("Delete cookie pattern %s?", args[0])).Value(&confirmed).Run(); err != nil { + if err := huh.NewConfirm().Title(fmt.Sprintf("Delete tracker pattern %s?", args[0])).Value(&confirmed).Run(); err != nil { return err } if !confirmed { @@ -74,13 +74,13 @@ func NewCmdDelete(f *cmdutil.Factory) *cobra.Command { ) _, err = client.Do(deleteMutation, map[string]any{ - "input": map[string]any{"cookiePatternId": args[0]}, + "input": map[string]any{"trackerPatternId": args[0]}, }) if err != nil { return err } - _, _ = fmt.Fprintf(f.IOStreams.Out, "Deleted cookie pattern %s\n", args[0]) + _, _ = fmt.Fprintf(f.IOStreams.Out, "Deleted tracker pattern %s\n", args[0]) return nil }, diff --git a/pkg/cmd/cookie-pattern/list/list.go b/pkg/cmd/tracker-pattern/list/list.go similarity index 81% rename from pkg/cmd/cookie-pattern/list/list.go rename to pkg/cmd/tracker-pattern/list/list.go index ee2b15dd4..41f6885ac 100644 --- a/pkg/cmd/cookie-pattern/list/list.go +++ b/pkg/cmd/tracker-pattern/list/list.go @@ -28,13 +28,14 @@ query($id: ID!, $first: Int, $after: CursorKey) { node(id: $id) { __typename ... on CookieCategory { - cookiePatterns(first: $first, after: $after) { + trackerPatterns(first: $first, after: $after) { totalCount edges { node { id pattern matchType + trackerType displayName source excluded @@ -51,10 +52,11 @@ query($id: ID!, $first: Int, $after: CursorKey) { } ` -type cookiePattern struct { +type trackerPattern struct { ID string `json:"id"` Pattern string `json:"pattern"` MatchType string `json:"matchType"` + TrackerType string `json:"trackerType"` DisplayName string `json:"displayName"` Source string `json:"source"` Excluded bool `json:"excluded"` @@ -70,7 +72,7 @@ func NewCmdList(f *cmdutil.Factory) *cobra.Command { cmd := &cobra.Command{ Use: "list", - Short: "List cookie patterns in a category", + Short: "List tracker patterns in a category", Aliases: []string{"ls"}, Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, args []string) error { @@ -103,11 +105,11 @@ func NewCmdList(f *cmdutil.Factory) *cobra.Command { listQuery, variables, flagLimit, - func(data json.RawMessage) (*api.Connection[cookiePattern], error) { + func(data json.RawMessage) (*api.Connection[trackerPattern], error) { var resp struct { Node *struct { - Typename string `json:"__typename"` - CookiePatterns api.Connection[cookiePattern] `json:"cookiePatterns"` + Typename string `json:"__typename"` + TrackerPatterns api.Connection[trackerPattern] `json:"trackerPatterns"` } `json:"node"` } if err := json.Unmarshal(data, &resp); err != nil { @@ -119,7 +121,7 @@ func NewCmdList(f *cmdutil.Factory) *cobra.Command { if resp.Node.Typename != "CookieCategory" { return nil, fmt.Errorf("expected CookieCategory node, got %s", resp.Node.Typename) } - return &resp.Node.CookiePatterns, nil + return &resp.Node.TrackerPatterns, nil }, ) if err != nil { @@ -131,7 +133,7 @@ func NewCmdList(f *cmdutil.Factory) *cobra.Command { } if len(patterns) == 0 { - _, _ = fmt.Fprintln(f.IOStreams.Out, "No cookie patterns found.") + _, _ = fmt.Fprintln(f.IOStreams.Out, "No tracker patterns found.") return nil } @@ -145,14 +147,14 @@ func NewCmdList(f *cmdutil.Factory) *cobra.Command { if p.LastMatchedAt != nil { lastMatched = cmdutil.FormatTime(*p.LastMatchedAt) } - rows = append(rows, []string{p.ID, p.Pattern, p.MatchType, p.DisplayName, p.Source, excluded, lastMatched}) + rows = append(rows, []string{p.ID, p.Pattern, p.MatchType, p.TrackerType, p.DisplayName, p.Source, excluded, lastMatched}) } - t := cmdutil.NewTable("ID", "PATTERN", "MATCH TYPE", "DISPLAY NAME", "SOURCE", "EXCLUDED", "LAST MATCHED").Rows(rows...) + t := cmdutil.NewTable("ID", "PATTERN", "MATCH TYPE", "TRACKER TYPE", "DISPLAY NAME", "SOURCE", "EXCLUDED", "LAST MATCHED").Rows(rows...) _, _ = fmt.Fprintln(f.IOStreams.Out, t) if totalCount > len(patterns) { - _, _ = fmt.Fprintf(f.IOStreams.ErrOut, "\nShowing %d of %d cookie patterns\n", len(patterns), totalCount) + _, _ = fmt.Fprintf(f.IOStreams.ErrOut, "\nShowing %d of %d tracker patterns\n", len(patterns), totalCount) } return nil diff --git a/pkg/cmd/cookie-pattern/move/move.go b/pkg/cmd/tracker-pattern/move/move.go similarity index 81% rename from pkg/cmd/cookie-pattern/move/move.go rename to pkg/cmd/tracker-pattern/move/move.go index 9c19c4ad1..e55c8af0e 100644 --- a/pkg/cmd/cookie-pattern/move/move.go +++ b/pkg/cmd/tracker-pattern/move/move.go @@ -24,9 +24,9 @@ import ( ) const moveMutation = ` -mutation($input: MoveCookiePatternToCategoryInput!) { - moveCookiePatternToCategory(input: $input) { - cookiePattern { +mutation($input: MoveTrackerPatternToCategoryInput!) { + moveTrackerPatternToCategory(input: $input) { + trackerPattern { id cookieCategory { id @@ -41,15 +41,15 @@ mutation($input: MoveCookiePatternToCategoryInput!) { ` type moveResponse struct { - MoveCookiePatternToCategory struct { - CookiePattern struct { + MoveTrackerPatternToCategory struct { + TrackerPattern struct { ID string `json:"id"` CookieCategory struct { ID string `json:"id"` Name string `json:"name"` } `json:"cookieCategory"` - } `json:"cookiePattern"` - } `json:"moveCookiePatternToCategory"` + } `json:"trackerPattern"` + } `json:"moveTrackerPatternToCategory"` } func NewCmdMove(f *cmdutil.Factory) *cobra.Command { @@ -57,7 +57,7 @@ func NewCmdMove(f *cmdutil.Factory) *cobra.Command { cmd := &cobra.Command{ Use: "move ", - Short: "Move a cookie pattern to a different category", + Short: "Move a tracker pattern to a different category", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cfg, err := f.Config() @@ -80,7 +80,7 @@ func NewCmdMove(f *cmdutil.Factory) *cobra.Command { data, err := client.Do(moveMutation, map[string]any{ "input": map[string]any{ - "cookiePatternId": args[0], + "trackerPatternId": args[0], "targetCookieCategoryId": flagTargetCategoryID, }, }) @@ -93,8 +93,8 @@ func NewCmdMove(f *cmdutil.Factory) *cobra.Command { return fmt.Errorf("cannot parse response: %w", err) } - p := resp.MoveCookiePatternToCategory.CookiePattern - _, _ = fmt.Fprintf(f.IOStreams.Out, "Moved cookie pattern %s to category %s\n", p.ID, p.CookieCategory.Name) + p := resp.MoveTrackerPatternToCategory.TrackerPattern + _, _ = fmt.Fprintf(f.IOStreams.Out, "Moved tracker pattern %s to category %s\n", p.ID, p.CookieCategory.Name) return nil }, diff --git a/pkg/cmd/cookie-pattern/cookie_pattern.go b/pkg/cmd/tracker-pattern/tracker_pattern.go similarity index 70% rename from pkg/cmd/cookie-pattern/cookie_pattern.go rename to pkg/cmd/tracker-pattern/tracker_pattern.go index 8c145ca2b..17099cfa3 100644 --- a/pkg/cmd/cookie-pattern/cookie_pattern.go +++ b/pkg/cmd/tracker-pattern/tracker_pattern.go @@ -12,23 +12,23 @@ // OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR // PERFORMANCE OF THIS SOFTWARE. -package cookiepattern +package trackerpattern import ( "github.com/spf13/cobra" "go.probo.inc/probo/pkg/cmd/cmdutil" - "go.probo.inc/probo/pkg/cmd/cookie-pattern/create" - "go.probo.inc/probo/pkg/cmd/cookie-pattern/delete" - "go.probo.inc/probo/pkg/cmd/cookie-pattern/list" - "go.probo.inc/probo/pkg/cmd/cookie-pattern/move" - "go.probo.inc/probo/pkg/cmd/cookie-pattern/update" - "go.probo.inc/probo/pkg/cmd/cookie-pattern/view" + "go.probo.inc/probo/pkg/cmd/tracker-pattern/create" + "go.probo.inc/probo/pkg/cmd/tracker-pattern/delete" + "go.probo.inc/probo/pkg/cmd/tracker-pattern/list" + "go.probo.inc/probo/pkg/cmd/tracker-pattern/move" + "go.probo.inc/probo/pkg/cmd/tracker-pattern/update" + "go.probo.inc/probo/pkg/cmd/tracker-pattern/view" ) -func NewCmdCookiePattern(f *cmdutil.Factory) *cobra.Command { +func NewCmdTrackerPattern(f *cmdutil.Factory) *cobra.Command { cmd := &cobra.Command{ - Use: "cookie-pattern ", - Short: "Manage cookie patterns", + Use: "tracker-pattern ", + Short: "Manage tracker patterns", } cmd.AddCommand(list.NewCmdList(f)) diff --git a/pkg/cmd/cookie-pattern/update/update.go b/pkg/cmd/tracker-pattern/update/update.go similarity index 85% rename from pkg/cmd/cookie-pattern/update/update.go rename to pkg/cmd/tracker-pattern/update/update.go index 0fb0ea5c5..8e9a3c044 100644 --- a/pkg/cmd/cookie-pattern/update/update.go +++ b/pkg/cmd/tracker-pattern/update/update.go @@ -24,9 +24,9 @@ import ( ) const updateMutation = ` -mutation($input: UpdateCookiePatternInput!) { - updateCookiePattern(input: $input) { - cookiePattern { +mutation($input: UpdateTrackerPatternInput!) { + updateTrackerPattern(input: $input) { + trackerPattern { id displayName } @@ -38,12 +38,12 @@ mutation($input: UpdateCookiePatternInput!) { ` type updateResponse struct { - UpdateCookiePattern struct { - CookiePattern struct { + UpdateTrackerPattern struct { + TrackerPattern struct { ID string `json:"id"` DisplayName string `json:"displayName"` - } `json:"cookiePattern"` - } `json:"updateCookiePattern"` + } `json:"trackerPattern"` + } `json:"updateTrackerPattern"` } func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command { @@ -56,7 +56,7 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command { cmd := &cobra.Command{ Use: "update ", - Short: "Update a cookie pattern", + Short: "Update a tracker pattern", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { cfg, err := f.Config() @@ -77,7 +77,7 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command { cmdutil.TokenRefreshOption(cfg, host, hc), ) - input := map[string]any{"cookiePatternId": args[0]} + input := map[string]any{"trackerPatternId": args[0]} if cmd.Flags().Changed("display-name") { input["displayName"] = flagDisplayName @@ -106,8 +106,8 @@ func NewCmdUpdate(f *cmdutil.Factory) *cobra.Command { return fmt.Errorf("cannot parse response: %w", err) } - p := resp.UpdateCookiePattern.CookiePattern - _, _ = fmt.Fprintf(f.IOStreams.Out, "Updated cookie pattern %s (%s)\n", p.ID, p.DisplayName) + p := resp.UpdateTrackerPattern.TrackerPattern + _, _ = fmt.Fprintf(f.IOStreams.Out, "Updated tracker pattern %s (%s)\n", p.ID, p.DisplayName) return nil }, diff --git a/pkg/cmd/cookie-pattern/view/view.go b/pkg/cmd/tracker-pattern/view/view.go similarity index 92% rename from pkg/cmd/cookie-pattern/view/view.go rename to pkg/cmd/tracker-pattern/view/view.go index 8d874a08e..f58e7f07e 100644 --- a/pkg/cmd/cookie-pattern/view/view.go +++ b/pkg/cmd/tracker-pattern/view/view.go @@ -28,10 +28,11 @@ const viewQuery = ` query($id: ID!) { node(id: $id) { __typename - ... on CookiePattern { + ... on TrackerPattern { id pattern matchType + trackerType displayName maxAgeSeconds description @@ -51,6 +52,7 @@ type viewResponse struct { ID string `json:"id"` Pattern string `json:"pattern"` MatchType string `json:"matchType"` + TrackerType string `json:"trackerType"` DisplayName string `json:"displayName"` MaxAgeSeconds *int `json:"maxAgeSeconds"` Description *string `json:"description"` @@ -67,7 +69,7 @@ func NewCmdView(f *cmdutil.Factory) *cobra.Command { cmd := &cobra.Command{ Use: "view ", - Short: "View a cookie pattern", + Short: "View a tracker pattern", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if err := cmdutil.ValidateOutputFlag(flagOutput); err != nil { @@ -102,8 +104,8 @@ func NewCmdView(f *cmdutil.Factory) *cobra.Command { return fmt.Errorf("cannot parse response: %w", err) } - if resp.Node == nil || resp.Node.Typename != "CookiePattern" { - return fmt.Errorf("cookie pattern %s not found", args[0]) + if resp.Node == nil || resp.Node.Typename != "TrackerPattern" { + return fmt.Errorf("tracker pattern %s not found", args[0]) } if *flagOutput == cmdutil.OutputJSON { @@ -120,6 +122,7 @@ func NewCmdView(f *cmdutil.Factory) *cobra.Command { _, _ = fmt.Fprintf(out, "%s%s\n", label.Render("ID:"), v.ID) _, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Pattern:"), v.Pattern) _, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Match Type:"), v.MatchType) + _, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Tracker Type:"), v.TrackerType) _, _ = fmt.Fprintf(out, "%s%s\n", label.Render("Source:"), v.Source) _, _ = fmt.Fprintf(out, "%s%v\n", label.Render("Excluded:"), v.Excluded) if v.MaxAgeSeconds != nil { diff --git a/pkg/cookiebanner/errors.go b/pkg/cookiebanner/errors.go index 5f39c24af..cfa6e0d94 100644 --- a/pkg/cookiebanner/errors.go +++ b/pkg/cookiebanner/errors.go @@ -32,7 +32,7 @@ var ( ErrCookieNotFound = errors.New("cookie not found") ErrCategoriesBannerMismatch = errors.New("source and target categories belong to different banners") ErrPostHogConsentKindInvalid = errors.New("PostHog consent can only be enabled on normal categories") - ErrCookiePatternNotFound = errors.New("cookie pattern not found") + ErrTrackerPatternNotFound = errors.New("tracker pattern not found") ErrPatternAlreadyExists = errors.New("a pattern with this name already exists in this banner") ErrSamePatternCategoryMove = errors.New("source and target cookie categories must be different") ) diff --git a/pkg/cookiebanner/service.go b/pkg/cookiebanner/service.go index d58014e12..c6ea569b6 100644 --- a/pkg/cookiebanner/service.go +++ b/pkg/cookiebanner/service.go @@ -84,33 +84,6 @@ type ( Rank int } - CreateCookiePatternRequest struct { - CookieCategoryID gid.GID - Pattern string - MatchType coredata.CookiePatternMatchType - DisplayName string - MaxAgeSeconds *int - Description string - } - - UpdateCookiePatternRequest struct { - CookiePatternID gid.GID - DisplayName *string - MaxAgeSeconds **int - Description *string - Excluded *bool - } - - MoveCookiePatternToCategoryRequest struct { - CookiePatternID gid.GID - TargetCookieCategoryID gid.GID - } - - MoveCookiePatternToCategoryResult struct { - CookiePattern *coredata.CookiePattern - Banner *coredata.CookieBanner - } - CreateCookieConsentRecordRequest struct { CookieBannerID gid.GID Version int @@ -159,6 +132,17 @@ type ( Resources []DetectedResourceItem } + CreateTrackerPatternRequest struct { + CookieCategoryID gid.GID + TrackerType coredata.TrackerType + Pattern string + MatchType coredata.TrackerPatternMatchType + DisplayName string + MaxAgeSeconds *int + Description string + Source *coredata.CookieSource + } + UpdateTrackerPatternRequest struct { TrackerPatternID gid.GID DisplayName *string @@ -266,46 +250,6 @@ func (r *ReorderCookieCategoryRequest) Validate() error { return v.Error() } -func (r *CreateCookiePatternRequest) Validate() error { - v := validator.New() - - v.Check(r.CookieCategoryID, "cookie_category_id", validator.Required(), validator.GID(coredata.CookieCategoryEntityType)) - v.Check(r.Pattern, "pattern", validator.Required(), validator.SafeTextNoNewLine(255)) - v.Check(string(r.MatchType), "match_type", validator.Required(), validator.OneOfSlice( - func() []string { - types := coredata.CookiePatternMatchTypes() - s := make([]string, len(types)) - for i, t := range types { - s[i] = string(t) - } - return s - }(), - )) - v.Check(r.DisplayName, "display_name", validator.Required(), validator.SafeTextNoNewLine(255)) - v.Check(r.Description, "description", validator.SafeText(1000)) - - return v.Error() -} - -func (r *UpdateCookiePatternRequest) Validate() error { - v := validator.New() - - v.Check(r.CookiePatternID, "cookie_pattern_id", validator.Required(), validator.GID(coredata.CookiePatternEntityType)) - v.Check(r.DisplayName, "display_name", validator.SafeTextNoNewLine(255)) - v.Check(r.Description, "description", validator.SafeText(1000)) - - return v.Error() -} - -func (r *MoveCookiePatternToCategoryRequest) Validate() error { - v := validator.New() - - v.Check(r.CookiePatternID, "cookie_pattern_id", validator.Required(), validator.GID(coredata.CookiePatternEntityType)) - v.Check(r.TargetCookieCategoryID, "target_cookie_category_id", validator.Required(), validator.GID(coredata.CookieCategoryEntityType)) - - return v.Error() -} - func (r *CreateCookieConsentRecordRequest) Validate() error { v := validator.New() @@ -373,6 +317,27 @@ func (r *UpsertCookieBannerTranslationRequest) Validate() error { return v.Error() } +func (r *CreateTrackerPatternRequest) Validate() error { + v := validator.New() + + v.Check(r.CookieCategoryID, "cookie_category_id", validator.Required(), validator.GID(coredata.CookieCategoryEntityType)) + v.Check(r.Pattern, "pattern", validator.Required(), validator.SafeTextNoNewLine(255)) + v.Check(string(r.MatchType), "match_type", validator.Required(), validator.OneOfSlice( + func() []string { + types := coredata.TrackerPatternMatchTypes() + s := make([]string, len(types)) + for i, t := range types { + s[i] = string(t) + } + return s + }(), + )) + v.Check(r.DisplayName, "display_name", validator.Required(), validator.SafeTextNoNewLine(255)) + v.Check(r.Description, "description", validator.SafeText(1000)) + + return v.Error() +} + func CanonicalizeOrigin(raw string) string { u, err := url.Parse(raw) if err != nil { @@ -473,7 +438,7 @@ func (s *Service) ensureDraftVersionForBanner( tx, scope, bannerID, - coredata.NewCookiePatternFilter(nil, nil, new(false)), + coredata.NewTrackerPatternFilter(nil, nil, new(false)), nil, ); err != nil { return nil, fmt.Errorf("cannot load tracker patterns: %w", err) @@ -550,17 +515,18 @@ func (s *Service) CreateCookieBanner( if dc.Kind == coredata.CookieCategoryKindNecessary { consentMaxAge := req.ConsentExpiryDays * 86400 - consentPattern := &coredata.CookiePattern{ - ID: gid.New(scope.GetTenantID(), coredata.CookiePatternEntityType), + consentPattern := &coredata.TrackerPattern{ + ID: gid.New(scope.GetTenantID(), coredata.TrackerPatternEntityType), OrganizationID: banner.OrganizationID, CookieBannerID: banner.ID, CookieCategoryID: category.ID, + TrackerType: coredata.TrackerTypeCookie, Pattern: "probo_consent", - MatchType: coredata.CookiePatternMatchTypeExact, + MatchType: coredata.TrackerPatternMatchTypeExact, DisplayName: "probo_consent", MaxAgeSeconds: &consentMaxAge, Description: "Stores your cookie consent preferences for this website.", - Source: coredata.CookieSourceScript, + Source: new(coredata.CookieSourceScript), CreatedAt: now, UpdatedAt: now, } @@ -1156,442 +1122,6 @@ func (s *Service) CountCookieCategoriesForBanner( return count, nil } -func (s *Service) GetCookiePattern( - ctx context.Context, - scope coredata.Scoper, - cookiePatternID gid.GID, -) (*coredata.CookiePattern, error) { - var pattern coredata.CookiePattern - - err := s.pg.WithConn( - ctx, - func(ctx context.Context, conn pg.Querier) error { - if err := pattern.LoadByID(ctx, conn, scope, cookiePatternID); err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return ErrCookiePatternNotFound - } - return fmt.Errorf("cannot load cookie pattern: %w", err) - } - - return nil - }, - ) - if err != nil { - return nil, err - } - - return &pattern, nil -} - -func (s *Service) CreateCookiePattern( - ctx context.Context, - scope coredata.Scoper, - req CreateCookiePatternRequest, -) (*coredata.CookiePattern, error) { - if err := req.Validate(); err != nil { - return nil, fmt.Errorf("invalid request: %w", err) - } - - var pattern *coredata.CookiePattern - - err := s.pg.WithTx( - ctx, - func(ctx context.Context, tx pg.Tx) error { - var category coredata.CookieCategory - if err := category.LoadByID(ctx, tx, scope, req.CookieCategoryID); err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return ErrCategoryNotFound - } - return fmt.Errorf("cannot load cookie category: %w", err) - } - - now := time.Now() - - pattern = &coredata.CookiePattern{ - ID: gid.New(scope.GetTenantID(), coredata.CookiePatternEntityType), - OrganizationID: category.OrganizationID, - CookieBannerID: category.CookieBannerID, - CookieCategoryID: category.ID, - Pattern: req.Pattern, - MatchType: req.MatchType, - DisplayName: req.DisplayName, - MaxAgeSeconds: req.MaxAgeSeconds, - Description: req.Description, - Source: coredata.CookieSourceScript, - CreatedAt: now, - UpdatedAt: now, - } - - if err := pattern.Insert(ctx, tx, scope); err != nil { - if errors.Is(err, coredata.ErrResourceAlreadyExists) { - return ErrPatternAlreadyExists - } - return fmt.Errorf("cannot insert cookie pattern: %w", err) - } - - tp := &coredata.TrackerPattern{ - ID: gid.New(scope.GetTenantID(), coredata.TrackerPatternEntityType), - OrganizationID: category.OrganizationID, - CookieBannerID: category.CookieBannerID, - CookieCategoryID: category.ID, - TrackerType: coredata.TrackerTypeCookie, - Pattern: req.Pattern, - MatchType: req.MatchType, - DisplayName: req.DisplayName, - MaxAgeSeconds: req.MaxAgeSeconds, - Description: req.Description, - Source: new(coredata.CookieSourceScript), - CreatedAt: now, - UpdatedAt: now, - } - if _, err := tp.InsertIfNotExists(ctx, tx, scope); err != nil { - return fmt.Errorf("cannot insert tracker pattern: %w", err) - } - - if _, err := s.ensureDraftVersionForBanner(ctx, tx, scope, category.CookieBannerID); err != nil { - return fmt.Errorf("cannot ensure draft version: %w", err) - } - - return nil - }, - ) - if err != nil { - return nil, err - } - - return pattern, nil -} - -func (s *Service) UpdateCookiePattern( - ctx context.Context, - scope coredata.Scoper, - req UpdateCookiePatternRequest, -) (*coredata.CookiePattern, error) { - if err := req.Validate(); err != nil { - return nil, fmt.Errorf("invalid request: %w", err) - } - - var pattern coredata.CookiePattern - - err := s.pg.WithTx( - ctx, - func(ctx context.Context, tx pg.Tx) error { - if err := pattern.LoadByID(ctx, tx, scope, req.CookiePatternID); err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return ErrCookiePatternNotFound - } - return fmt.Errorf("cannot load cookie pattern: %w", err) - } - - displayNameChanged := req.DisplayName != nil && *req.DisplayName != pattern.DisplayName - maxAgeChanged := req.MaxAgeSeconds != nil && !ptrEqual(*req.MaxAgeSeconds, pattern.MaxAgeSeconds) - descChanged := req.Description != nil && *req.Description != pattern.Description - excludedChanged := req.Excluded != nil && *req.Excluded != pattern.Excluded - - if !displayNameChanged && !maxAgeChanged && !descChanged && !excludedChanged { - return nil - } - - // A pattern that was excluded and stays excluded is invisible to visitors, - // so any field updates do not affect the published snapshot. We persist - // the row but skip the version bump. - staysExcluded := pattern.Excluded && (req.Excluded == nil || *req.Excluded) - - if req.DisplayName != nil { - pattern.DisplayName = *req.DisplayName - } - if req.MaxAgeSeconds != nil { - pattern.MaxAgeSeconds = *req.MaxAgeSeconds - } - if req.Description != nil { - pattern.Description = *req.Description - } - if req.Excluded != nil { - pattern.Excluded = *req.Excluded - } - - pattern.UpdatedAt = time.Now() - - if err := pattern.Update(ctx, tx, scope); err != nil { - return fmt.Errorf("cannot update cookie pattern: %w", err) - } - - var tp coredata.TrackerPattern - if err := tp.LoadByBannerIDTypeAndPattern(ctx, tx, scope, pattern.CookieBannerID, coredata.TrackerTypeCookie, pattern.Pattern); err != nil { - if !errors.Is(err, coredata.ErrResourceNotFound) { - return fmt.Errorf("cannot load tracker pattern: %w", err) - } - } else { - if req.DisplayName != nil { - tp.DisplayName = *req.DisplayName - } - if req.MaxAgeSeconds != nil { - tp.MaxAgeSeconds = *req.MaxAgeSeconds - } - if req.Description != nil { - tp.Description = *req.Description - } - if req.Excluded != nil { - tp.Excluded = *req.Excluded - } - tp.UpdatedAt = time.Now() - if err := tp.Update(ctx, tx, scope); err != nil { - return fmt.Errorf("cannot update tracker pattern: %w", err) - } - } - - if !staysExcluded { - if _, err := s.ensureDraftVersionForBanner(ctx, tx, scope, pattern.CookieBannerID); err != nil { - return fmt.Errorf("cannot ensure draft version: %w", err) - } - } - - return nil - }, - ) - if err != nil { - return nil, err - } - - return &pattern, nil -} - -func (s *Service) DeleteCookiePattern( - ctx context.Context, - scope coredata.Scoper, - cookiePatternID gid.GID, -) error { - return s.pg.WithTx( - ctx, - func(ctx context.Context, tx pg.Tx) error { - var pattern coredata.CookiePattern - if err := pattern.LoadByID(ctx, tx, scope, cookiePatternID); err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return ErrCookiePatternNotFound - } - return fmt.Errorf("cannot load cookie pattern: %w", err) - } - - wasExcluded := pattern.Excluded - - if err := pattern.Delete(ctx, tx, scope); err != nil { - return fmt.Errorf("cannot delete cookie pattern: %w", err) - } - - var tp coredata.TrackerPattern - if err := tp.LoadByBannerIDTypeAndPattern(ctx, tx, scope, pattern.CookieBannerID, coredata.TrackerTypeCookie, pattern.Pattern); err != nil { - if !errors.Is(err, coredata.ErrResourceNotFound) { - return fmt.Errorf("cannot load tracker pattern: %w", err) - } - } else { - if err := tp.Delete(ctx, tx, scope); err != nil { - return fmt.Errorf("cannot delete tracker pattern: %w", err) - } - } - - if !wasExcluded { - if _, err := s.ensureDraftVersionForBanner(ctx, tx, scope, pattern.CookieBannerID); err != nil { - return fmt.Errorf("cannot ensure draft version: %w", err) - } - } - - return nil - }, - ) -} - -func (s *Service) MoveCookiePatternToCategory( - ctx context.Context, - scope coredata.Scoper, - req MoveCookiePatternToCategoryRequest, -) (*MoveCookiePatternToCategoryResult, error) { - if err := req.Validate(); err != nil { - return nil, fmt.Errorf("invalid request: %w", err) - } - - var result MoveCookiePatternToCategoryResult - - err := s.pg.WithTx( - ctx, - func(ctx context.Context, tx pg.Tx) error { - var pattern coredata.CookiePattern - if err := pattern.LoadByID(ctx, tx, scope, req.CookiePatternID); err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return ErrCookiePatternNotFound - } - return fmt.Errorf("cannot load cookie pattern: %w", err) - } - - var target coredata.CookieCategory - if err := target.LoadByID(ctx, tx, scope, req.TargetCookieCategoryID); err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) { - return ErrCategoryNotFound - } - return fmt.Errorf("cannot load target cookie category: %w", err) - } - - if pattern.CookieCategoryID == target.ID { - return ErrSamePatternCategoryMove - } - - if pattern.CookieBannerID != target.CookieBannerID { - return ErrCategoriesBannerMismatch - } - - wasExcluded := pattern.Excluded - - pattern.CookieCategoryID = target.ID - pattern.UpdatedAt = time.Now() - - if err := pattern.Update(ctx, tx, scope); err != nil { - return fmt.Errorf("cannot update cookie pattern: %w", err) - } - - var tp coredata.TrackerPattern - if err := tp.LoadByBannerIDTypeAndPattern(ctx, tx, scope, pattern.CookieBannerID, coredata.TrackerTypeCookie, pattern.Pattern); err != nil { - if !errors.Is(err, coredata.ErrResourceNotFound) { - return fmt.Errorf("cannot load tracker pattern: %w", err) - } - } else { - tp.CookieCategoryID = target.ID - tp.UpdatedAt = time.Now() - if err := tp.Update(ctx, tx, scope); err != nil { - return fmt.Errorf("cannot update tracker pattern: %w", err) - } - } - - var banner coredata.CookieBanner - if err := banner.LoadByID(ctx, tx, scope, pattern.CookieBannerID); err != nil { - return fmt.Errorf("cannot load cookie banner: %w", err) - } - - if !wasExcluded { - if _, err := s.ensureDraftVersionForBanner(ctx, tx, scope, pattern.CookieBannerID); err != nil { - return fmt.Errorf("cannot ensure draft version: %w", err) - } - } - - result.CookiePattern = &pattern - result.Banner = &banner - - return nil - }, - ) - if err != nil { - return nil, err - } - - return &result, nil -} - -func (s *Service) ListCookiePatternsForCategory( - ctx context.Context, - scope coredata.Scoper, - categoryID gid.GID, - cursor *page.Cursor[coredata.CookiePatternOrderField], -) (coredata.CookiePatterns, error) { - var patterns coredata.CookiePatterns - - err := s.pg.WithConn( - ctx, - func(ctx context.Context, conn pg.Querier) error { - if err := patterns.LoadByCookieCategoryID(ctx, conn, scope, categoryID, cursor); err != nil { - return fmt.Errorf("cannot list cookie patterns: %w", err) - } - - return nil - }, - ) - if err != nil { - return nil, err - } - - return patterns, nil -} - -func (s *Service) CountCookiePatternsForCategory( - ctx context.Context, - scope coredata.Scoper, - categoryID gid.GID, -) (int, error) { - var count int - - err := s.pg.WithConn( - ctx, - func(ctx context.Context, conn pg.Querier) error { - var patterns coredata.CookiePatterns - var err error - - count, err = patterns.CountByCookieCategoryID(ctx, conn, scope, categoryID) - if err != nil { - return fmt.Errorf("cannot count cookie patterns: %w", err) - } - - return nil - }, - ) - if err != nil { - return 0, err - } - - return count, nil -} - -func (s *Service) ListUncategorisedCookiePatterns( - ctx context.Context, - scope coredata.Scoper, - bannerID gid.GID, - cursor *page.Cursor[coredata.CookiePatternOrderField], - filter *coredata.CookiePatternFilter, -) (coredata.CookiePatterns, error) { - var patterns coredata.CookiePatterns - - err := s.pg.WithConn( - ctx, - func(ctx context.Context, conn pg.Querier) error { - if err := patterns.LoadUncategorisedByCookieBannerID(ctx, conn, scope, bannerID, cursor, filter); err != nil { - return fmt.Errorf("cannot list uncategorised cookie patterns: %w", err) - } - - return nil - }, - ) - if err != nil { - return nil, err - } - - return patterns, nil -} - -func (s *Service) CountUncategorisedCookiePatterns( - ctx context.Context, - scope coredata.Scoper, - bannerID gid.GID, - filter *coredata.CookiePatternFilter, -) (int, error) { - var count int - - err := s.pg.WithConn( - ctx, - func(ctx context.Context, conn pg.Querier) error { - var patterns coredata.CookiePatterns - var err error - - count, err = patterns.CountUncategorisedByCookieBannerID(ctx, conn, scope, bannerID, filter) - if err != nil { - return fmt.Errorf("cannot count uncategorised cookie patterns: %w", err) - } - - return nil - }, - ) - if err != nil { - return 0, err - } - - return count, nil -} - func (s *Service) CountCookiesForPattern( ctx context.Context, scope coredata.Scoper, @@ -1777,9 +1307,9 @@ func (s *Service) DeleteCookieCategory( return fmt.Errorf("cannot load uncategorised cookie category: %w", err) } - var patterns coredata.CookiePatterns + var patterns coredata.TrackerPatterns if err := patterns.MoveToCategoryByCookieCategoryID(ctx, tx, scope, category.ID, uncategorised.ID); err != nil { - return fmt.Errorf("cannot move cookie patterns to uncategorised: %w", err) + return fmt.Errorf("cannot move tracker patterns to uncategorised: %w", err) } if err := category.Delete(ctx, tx, scope); err != nil { @@ -2513,7 +2043,7 @@ func (s *Service) reportDetectedTracker( CookieCategoryID: uncategorisedID, TrackerType: info.TrackerType, Pattern: info.Identifier, - MatchType: coredata.CookiePatternMatchTypeExact, + MatchType: coredata.TrackerPatternMatchTypeExact, DisplayName: info.Identifier, Description: "", MaxAgeSeconds: info.MaxAgeSeconds, @@ -2558,6 +2088,114 @@ func (s *Service) reportDetectedTracker( return nil } +func (s *Service) CreateTrackerPattern( + ctx context.Context, + scope coredata.Scoper, + req CreateTrackerPatternRequest, +) (*coredata.TrackerPattern, error) { + if err := req.Validate(); err != nil { + return nil, fmt.Errorf("invalid request: %w", err) + } + + var pattern *coredata.TrackerPattern + + err := s.pg.WithTx( + ctx, + func(ctx context.Context, tx pg.Tx) error { + var category coredata.CookieCategory + if err := category.LoadByID(ctx, tx, scope, req.CookieCategoryID); err != nil { + if errors.Is(err, coredata.ErrResourceNotFound) { + return ErrCategoryNotFound + } + return fmt.Errorf("cannot load cookie category: %w", err) + } + + now := time.Now() + + pattern = &coredata.TrackerPattern{ + ID: gid.New(scope.GetTenantID(), coredata.TrackerPatternEntityType), + OrganizationID: category.OrganizationID, + CookieBannerID: category.CookieBannerID, + CookieCategoryID: category.ID, + TrackerType: req.TrackerType, + Pattern: req.Pattern, + MatchType: req.MatchType, + DisplayName: req.DisplayName, + MaxAgeSeconds: req.MaxAgeSeconds, + Description: req.Description, + Source: req.Source, + CreatedAt: now, + UpdatedAt: now, + } + + if err := pattern.Insert(ctx, tx, scope); err != nil { + if errors.Is(err, coredata.ErrResourceAlreadyExists) { + return ErrPatternAlreadyExists + } + return fmt.Errorf("cannot insert tracker pattern: %w", err) + } + + if !pattern.Excluded && pattern.TrackerType == coredata.TrackerTypeCookie { + if _, err := s.ensureDraftVersionForBanner(ctx, tx, scope, pattern.CookieBannerID); err != nil { + return fmt.Errorf("cannot ensure draft version: %w", err) + } + } + + return nil + }, + ) + if err != nil { + return nil, err + } + + return pattern, nil +} + +func (s *Service) ListTrackerPatternsForCategory( + ctx context.Context, + scope coredata.Scoper, + categoryID gid.GID, + cursor *page.Cursor[coredata.TrackerPatternOrderField], +) (coredata.TrackerPatterns, error) { + var patterns coredata.TrackerPatterns + + err := s.pg.WithConn( + ctx, + func(ctx context.Context, conn pg.Querier) error { + return patterns.LoadByCookieCategoryID(ctx, conn, scope, categoryID, cursor) + }, + ) + if err != nil { + return nil, fmt.Errorf("cannot list tracker patterns for category: %w", err) + } + + return patterns, nil +} + +func (s *Service) CountTrackerPatternsForCategory( + ctx context.Context, + scope coredata.Scoper, + categoryID gid.GID, +) (int, error) { + var count int + + err := s.pg.WithConn( + ctx, + func(ctx context.Context, conn pg.Querier) error { + var patterns coredata.TrackerPatterns + var err error + + count, err = patterns.CountByCookieCategoryID(ctx, conn, scope, categoryID) + return err + }, + ) + if err != nil { + return 0, fmt.Errorf("cannot count tracker patterns for category: %w", err) + } + + return count, nil +} + func (s *Service) GetTrackerPattern( ctx context.Context, scope coredata.Scoper, @@ -2570,7 +2208,7 @@ func (s *Service) GetTrackerPattern( func(ctx context.Context, conn pg.Querier) error { if err := pattern.LoadByID(ctx, conn, scope, trackerPatternID); err != nil { if errors.Is(err, coredata.ErrResourceNotFound) { - return ErrCookiePatternNotFound + return ErrTrackerPatternNotFound } return fmt.Errorf("cannot load tracker pattern: %w", err) } @@ -2597,7 +2235,7 @@ func (s *Service) UpdateTrackerPattern( func(ctx context.Context, tx pg.Tx) error { if err := pattern.LoadByID(ctx, tx, scope, req.TrackerPatternID); err != nil { if errors.Is(err, coredata.ErrResourceNotFound) { - return ErrCookiePatternNotFound + return ErrTrackerPatternNotFound } return fmt.Errorf("cannot load tracker pattern: %w", err) } @@ -2659,7 +2297,7 @@ func (s *Service) DeleteTrackerPattern( var pattern coredata.TrackerPattern if err := pattern.LoadByID(ctx, tx, scope, trackerPatternID); err != nil { if errors.Is(err, coredata.ErrResourceNotFound) { - return ErrCookiePatternNotFound + return ErrTrackerPatternNotFound } return fmt.Errorf("cannot load tracker pattern: %w", err) } @@ -2694,7 +2332,7 @@ func (s *Service) MoveTrackerPatternToCategory( var pattern coredata.TrackerPattern if err := pattern.LoadByID(ctx, tx, scope, req.TrackerPatternID); err != nil { if errors.Is(err, coredata.ErrResourceNotFound) { - return ErrCookiePatternNotFound + return ErrTrackerPatternNotFound } return fmt.Errorf("cannot load tracker pattern: %w", err) } @@ -2752,8 +2390,8 @@ func (s *Service) ListUncategorisedTrackerPatterns( ctx context.Context, scope coredata.Scoper, bannerID gid.GID, - cursor *page.Cursor[coredata.CookiePatternOrderField], - filter *coredata.CookiePatternFilter, + cursor *page.Cursor[coredata.TrackerPatternOrderField], + filter *coredata.TrackerPatternFilter, ) (coredata.TrackerPatterns, error) { var patterns coredata.TrackerPatterns @@ -2778,7 +2416,7 @@ func (s *Service) CountUncategorisedTrackerPatterns( ctx context.Context, scope coredata.Scoper, bannerID gid.GID, - filter *coredata.CookiePatternFilter, + filter *coredata.TrackerPatternFilter, ) (int, error) { var count int diff --git a/pkg/cookiebanner/worker.go b/pkg/cookiebanner/worker.go index b6d098fc8..b77ceb352 100644 --- a/pkg/cookiebanner/worker.go +++ b/pkg/cookiebanner/worker.go @@ -100,7 +100,7 @@ func (h *patternAnalysisHandler) Process(ctx context.Context, banner coredata.Co tx, scope, banner.ID, - coredata.NewCookiePatternFilter(new(coredata.CookiePatternMatchTypeExact), nil, new(false)), + coredata.NewTrackerPatternFilter(new(coredata.TrackerPatternMatchTypeExact), nil, new(false)), nil, ); err != nil { return fmt.Errorf("cannot load exact patterns: %w", err) @@ -121,7 +121,7 @@ func (h *patternAnalysisHandler) Process(ctx context.Context, banner coredata.Co CookieCategoryID: key.categoryID, TrackerType: key.trackerType, Pattern: key.prefix, - MatchType: coredata.CookiePatternMatchTypePrefix, + MatchType: coredata.TrackerPatternMatchTypePrefix, DisplayName: key.prefix + "*", MaxAgeSeconds: maxAge, Description: "", @@ -139,7 +139,7 @@ func (h *patternAnalysisHandler) Process(ctx context.Context, banner coredata.Co return fmt.Errorf("cannot load existing prefix pattern %q: %w", key.prefix, err) } - if prefixPattern.CookieCategoryID != key.categoryID || prefixPattern.MatchType != coredata.CookiePatternMatchTypePrefix { + if prefixPattern.CookieCategoryID != key.categoryID || prefixPattern.MatchType != coredata.TrackerPatternMatchTypePrefix { continue } } @@ -319,7 +319,7 @@ func (h *patternAnalysisHandler) adoptUncategorisedPatterns( tx, scope, banner.ID, - coredata.NewCookiePatternFilter(new(coredata.CookiePatternMatchTypePrefix), nil, new(false)), + coredata.NewTrackerPatternFilter(new(coredata.TrackerPatternMatchTypePrefix), nil, new(false)), nil, ); err != nil { return false, fmt.Errorf("cannot load prefix patterns: %w", err) @@ -333,14 +333,14 @@ func (h *patternAnalysisHandler) adoptUncategorisedPatterns( return len(prefixPatterns[i].Pattern) > len(prefixPatterns[j].Pattern) }) - exactMatchType := coredata.CookiePatternMatchTypeExact + exactMatchType := coredata.TrackerPatternMatchTypeExact var uncategorisedExact coredata.TrackerPatterns if err := uncategorisedExact.LoadAllByCookieBannerID( ctx, tx, scope, banner.ID, - coredata.NewCookiePatternFilter(&exactMatchType, &uncategorised.ID, new(false)), + coredata.NewTrackerPatternFilter(&exactMatchType, &uncategorised.ID, new(false)), nil, ); err != nil { return false, fmt.Errorf("cannot load uncategorised exact patterns: %w", err) diff --git a/pkg/cookiebanner/worker_test.go b/pkg/cookiebanner/worker_test.go index 6c4b51678..ba82256bb 100644 --- a/pkg/cookiebanner/worker_test.go +++ b/pkg/cookiebanner/worker_test.go @@ -92,7 +92,7 @@ func TestFindMergeGroups(t *testing.T) { return &coredata.TrackerPattern{ Pattern: name, TrackerType: coredata.TrackerTypeCookie, - MatchType: coredata.CookiePatternMatchTypeExact, + MatchType: coredata.TrackerPatternMatchTypeExact, } } diff --git a/pkg/coredata/cookie_pattern.go b/pkg/coredata/cookie_pattern.go deleted file mode 100644 index efce37607..000000000 --- a/pkg/coredata/cookie_pattern.go +++ /dev/null @@ -1,794 +0,0 @@ -// Copyright (c) 2026 Probo Inc . -// -// 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 - -import ( - "context" - "errors" - "fmt" - "maps" - "time" - - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" - "go.gearno.de/kit/pg" - "go.probo.inc/probo/pkg/gid" - "go.probo.inc/probo/pkg/page" -) - -type ( - CookiePattern struct { - ID gid.GID `db:"id"` - OrganizationID gid.GID `db:"organization_id"` - CookieBannerID gid.GID `db:"cookie_banner_id"` - CookieCategoryID gid.GID `db:"cookie_category_id"` - Pattern string `db:"pattern"` - MatchType CookiePatternMatchType `db:"match_type"` - DisplayName string `db:"display_name"` - MaxAgeSeconds *int `db:"max_age_seconds"` - Description string `db:"description"` - Source CookieSource `db:"source"` - Excluded bool `db:"excluded"` - LastMatchedAt *time.Time `db:"last_matched_at"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` - } - - CookiePatterns []*CookiePattern -) - -func (cp *CookiePattern) CursorKey(field CookiePatternOrderField) page.CursorKey { - switch field { - case CookiePatternOrderFieldCreatedAt: - return page.NewCursorKey(cp.ID, cp.CreatedAt) - case CookiePatternOrderFieldName: - return page.NewCursorKey(cp.ID, cp.DisplayName) - case CookiePatternOrderFieldLastMatchedAt: - if cp.LastMatchedAt == nil { - return page.NewCursorKey(cp.ID, time.Time{}) - } - return page.NewCursorKey(cp.ID, *cp.LastMatchedAt) - case CookiePatternOrderFieldUpdatedAt: - return page.NewCursorKey(cp.ID, cp.UpdatedAt) - case CookiePatternOrderFieldSource: - return page.NewCursorKey(cp.ID, string(cp.Source)) - } - - panic(fmt.Sprintf("unsupported order by: %s", field)) -} - -func (cp *CookiePattern) AuthorizationAttributes(ctx context.Context, conn pg.Querier) (map[string]string, error) { - q := `SELECT organization_id FROM cookie_patterns WHERE id = $1 LIMIT 1;` - - var organizationID gid.GID - if err := conn.QueryRow(ctx, q, cp.ID).Scan(&organizationID); err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return nil, ErrResourceNotFound - } - - return nil, fmt.Errorf("cannot query cookie pattern authorization attributes: %w", err) - } - - return map[string]string{"organization_id": organizationID.String()}, nil -} - -func (cp *CookiePattern) LoadByID( - ctx context.Context, - conn pg.Querier, - scope Scoper, - cookiePatternID gid.GID, -) error { - q := ` -SELECT - id, - organization_id, - cookie_banner_id, - cookie_category_id, - pattern, - match_type, - display_name, - max_age_seconds, - description, - source, - excluded, - last_matched_at, - created_at, - updated_at -FROM - cookie_patterns -WHERE - %s - AND id = @cookie_pattern_id -LIMIT 1; -` - - q = fmt.Sprintf(q, scope.SQLFragment()) - - args := pgx.StrictNamedArgs{"cookie_pattern_id": cookiePatternID} - maps.Copy(args, scope.SQLArguments()) - - rows, err := conn.Query(ctx, q, args) - if err != nil { - return fmt.Errorf("cannot query cookie patterns: %w", err) - } - - pattern, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CookiePattern]) - if err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return ErrResourceNotFound - } - return fmt.Errorf("cannot collect cookie pattern: %w", err) - } - - *cp = pattern - - return nil -} - -func (cp *CookiePattern) LoadByBannerIDAndPattern( - ctx context.Context, - conn pg.Querier, - scope Scoper, - cookieBannerID gid.GID, - pattern string, -) error { - q := ` -SELECT - id, - organization_id, - cookie_banner_id, - cookie_category_id, - pattern, - match_type, - display_name, - max_age_seconds, - description, - source, - excluded, - last_matched_at, - created_at, - updated_at -FROM - cookie_patterns -WHERE - %s - AND cookie_banner_id = @cookie_banner_id - AND pattern = @pattern -LIMIT 1; -` - - q = fmt.Sprintf(q, scope.SQLFragment()) - - args := pgx.StrictNamedArgs{ - "cookie_banner_id": cookieBannerID, - "pattern": pattern, - } - maps.Copy(args, scope.SQLArguments()) - - rows, err := conn.Query(ctx, q, args) - if err != nil { - return fmt.Errorf("cannot query cookie patterns: %w", err) - } - - p, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CookiePattern]) - if err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return ErrResourceNotFound - } - return fmt.Errorf("cannot collect cookie pattern: %w", err) - } - - *cp = p - - return nil -} - -func (cp *CookiePattern) FindMatchingPattern( - ctx context.Context, - conn pg.Querier, - scope Scoper, - cookieBannerID gid.GID, - cookieName string, -) error { - q := ` -SELECT - id, - organization_id, - cookie_banner_id, - cookie_category_id, - pattern, - match_type, - display_name, - max_age_seconds, - description, - source, - excluded, - last_matched_at, - created_at, - updated_at -FROM - cookie_patterns -WHERE - %s - AND cookie_banner_id = @cookie_banner_id - AND ( - (match_type = @match_type_prefix AND starts_with(@cookie_name, pattern)) - OR (match_type = @match_type_exact AND pattern = @cookie_name) - ) -ORDER BY - CASE WHEN match_type = @match_type_exact AND pattern = @cookie_name THEN 0 - WHEN match_type = @match_type_prefix THEN 1 - ELSE 2 - END, - LENGTH(pattern) DESC -LIMIT 1; -` - - q = fmt.Sprintf(q, scope.SQLFragment()) - - args := pgx.StrictNamedArgs{ - "cookie_banner_id": cookieBannerID, - "cookie_name": cookieName, - "match_type_prefix": CookiePatternMatchTypePrefix, - "match_type_exact": CookiePatternMatchTypeExact, - } - maps.Copy(args, scope.SQLArguments()) - - rows, err := conn.Query(ctx, q, args) - if err != nil { - return fmt.Errorf("cannot query cookie patterns: %w", err) - } - - pattern, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CookiePattern]) - if err != nil { - if errors.Is(err, pgx.ErrNoRows) { - return ErrResourceNotFound - } - return fmt.Errorf("cannot collect cookie pattern: %w", err) - } - - *cp = pattern - - return nil -} - -func (cps *CookiePatterns) LoadByCookieCategoryID( - ctx context.Context, - conn pg.Querier, - scope Scoper, - cookieCategoryID gid.GID, - cursor *page.Cursor[CookiePatternOrderField], -) error { - q := ` -SELECT - id, - organization_id, - cookie_banner_id, - cookie_category_id, - pattern, - match_type, - display_name, - max_age_seconds, - description, - source, - excluded, - last_matched_at, - created_at, - updated_at -FROM - cookie_patterns -WHERE - %s - AND cookie_category_id = @cookie_category_id - AND %s -` - - q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) - - args := pgx.StrictNamedArgs{"cookie_category_id": cookieCategoryID} - maps.Copy(args, scope.SQLArguments()) - maps.Copy(args, cursor.SQLArguments()) - - rows, err := conn.Query(ctx, q, args) - if err != nil { - return fmt.Errorf("cannot query cookie patterns: %w", err) - } - - patterns, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[CookiePattern]) - if err != nil { - return fmt.Errorf("cannot collect cookie patterns: %w", err) - } - - *cps = patterns - - return nil -} - -func (cps *CookiePatterns) CountByCookieCategoryID( - ctx context.Context, - conn pg.Querier, - scope Scoper, - cookieCategoryID gid.GID, -) (int, error) { - q := ` -SELECT - COUNT(id) -FROM - cookie_patterns -WHERE - %s - AND cookie_category_id = @cookie_category_id -` - - q = fmt.Sprintf(q, scope.SQLFragment()) - - args := pgx.StrictNamedArgs{"cookie_category_id": cookieCategoryID} - maps.Copy(args, scope.SQLArguments()) - - row := conn.QueryRow(ctx, q, args) - - var count int - if err := row.Scan(&count); err != nil { - return 0, fmt.Errorf("cannot scan count: %w", err) - } - - return count, nil -} - -func (cps *CookiePatterns) LoadAllByCookieBannerID( - ctx context.Context, - conn pg.Querier, - scope Scoper, - cookieBannerID gid.GID, - filter *CookiePatternFilter, -) error { - q := ` -SELECT - id, - organization_id, - cookie_banner_id, - cookie_category_id, - pattern, - match_type, - display_name, - max_age_seconds, - description, - source, - excluded, - last_matched_at, - created_at, - updated_at -FROM - cookie_patterns -WHERE - %s - AND cookie_banner_id = @cookie_banner_id - AND %s -ORDER BY - created_at ASC, id ASC; -` - - q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment()) - - args := pgx.StrictNamedArgs{"cookie_banner_id": cookieBannerID} - maps.Copy(args, scope.SQLArguments()) - maps.Copy(args, filter.SQLArguments()) - - rows, err := conn.Query(ctx, q, args) - if err != nil { - return fmt.Errorf("cannot query cookie patterns: %w", err) - } - - patterns, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[CookiePattern]) - if err != nil { - return fmt.Errorf("cannot collect cookie patterns: %w", err) - } - - *cps = patterns - - return nil -} - -func (cp *CookiePattern) Insert( - ctx context.Context, - tx pg.Tx, - scope Scoper, -) error { - q := ` -INSERT INTO cookie_patterns ( - id, - tenant_id, - organization_id, - cookie_banner_id, - cookie_category_id, - pattern, - match_type, - display_name, - max_age_seconds, - description, - source, - excluded, - last_matched_at, - created_at, - updated_at -) VALUES ( - @id, - @tenant_id, - @organization_id, - @cookie_banner_id, - @cookie_category_id, - @pattern, - @match_type, - @display_name, - @max_age_seconds, - @description, - @source, - @excluded, - @last_matched_at, - @created_at, - @updated_at -) -` - - args := pgx.StrictNamedArgs{ - "id": cp.ID, - "tenant_id": scope.GetTenantID(), - "organization_id": cp.OrganizationID, - "cookie_banner_id": cp.CookieBannerID, - "cookie_category_id": cp.CookieCategoryID, - "pattern": cp.Pattern, - "match_type": cp.MatchType, - "display_name": cp.DisplayName, - "max_age_seconds": cp.MaxAgeSeconds, - "description": cp.Description, - "source": cp.Source, - "excluded": cp.Excluded, - "last_matched_at": cp.LastMatchedAt, - "created_at": cp.CreatedAt, - "updated_at": cp.UpdatedAt, - } - - _, err := tx.Exec(ctx, q, args) - if err != nil { - if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { - if pgErr.Code == "23505" && pgErr.ConstraintName == "idx_cookie_patterns_unique_pattern_per_banner" { - return ErrResourceAlreadyExists - } - } - return fmt.Errorf("cannot insert cookie pattern: %w", err) - } - - return nil -} - -func (cp *CookiePattern) InsertIfNotExists( - ctx context.Context, - tx pg.Tx, - scope Scoper, -) (bool, error) { - q := ` -INSERT INTO cookie_patterns ( - id, - tenant_id, - organization_id, - cookie_banner_id, - cookie_category_id, - pattern, - match_type, - display_name, - max_age_seconds, - description, - source, - excluded, - last_matched_at, - created_at, - updated_at -) VALUES ( - @id, - @tenant_id, - @organization_id, - @cookie_banner_id, - @cookie_category_id, - @pattern, - @match_type, - @display_name, - @max_age_seconds, - @description, - @source, - @excluded, - @last_matched_at, - @created_at, - @updated_at -) -ON CONFLICT (cookie_banner_id, pattern) DO NOTHING -` - - args := pgx.StrictNamedArgs{ - "id": cp.ID, - "tenant_id": scope.GetTenantID(), - "organization_id": cp.OrganizationID, - "cookie_banner_id": cp.CookieBannerID, - "cookie_category_id": cp.CookieCategoryID, - "pattern": cp.Pattern, - "match_type": cp.MatchType, - "display_name": cp.DisplayName, - "max_age_seconds": cp.MaxAgeSeconds, - "description": cp.Description, - "source": cp.Source, - "excluded": cp.Excluded, - "last_matched_at": cp.LastMatchedAt, - "created_at": cp.CreatedAt, - "updated_at": cp.UpdatedAt, - } - - result, err := tx.Exec(ctx, q, args) - if err != nil { - return false, fmt.Errorf("cannot insert cookie pattern: %w", err) - } - - return result.RowsAffected() > 0, nil -} - -func (cp *CookiePattern) Update( - ctx context.Context, - tx pg.Tx, - scope Scoper, -) error { - q := ` -UPDATE cookie_patterns -SET - cookie_category_id = @cookie_category_id, - display_name = @display_name, - max_age_seconds = @max_age_seconds, - description = @description, - excluded = @excluded, - updated_at = @updated_at -WHERE - %s - AND id = @id -` - - q = fmt.Sprintf(q, scope.SQLFragment()) - - args := pgx.StrictNamedArgs{ - "id": cp.ID, - "cookie_category_id": cp.CookieCategoryID, - "display_name": cp.DisplayName, - "max_age_seconds": cp.MaxAgeSeconds, - "description": cp.Description, - "excluded": cp.Excluded, - "updated_at": cp.UpdatedAt, - } - maps.Copy(args, scope.SQLArguments()) - - result, err := tx.Exec(ctx, q, args) - if err != nil { - if pgErr, ok := errors.AsType[*pgconn.PgError](err); ok { - if pgErr.Code == "23505" && pgErr.ConstraintName == "idx_cookie_patterns_unique_pattern_per_banner" { - return ErrResourceAlreadyExists - } - } - return fmt.Errorf("cannot update cookie pattern: %w", err) - } - - if result.RowsAffected() == 0 { - return ErrResourceNotFound - } - - return nil -} - -func (cp *CookiePattern) Delete( - ctx context.Context, - tx pg.Tx, - scope Scoper, -) error { - q := ` -DELETE FROM cookie_patterns -WHERE - %s - AND id = @id -` - - q = fmt.Sprintf(q, scope.SQLFragment()) - - args := pgx.StrictNamedArgs{"id": cp.ID} - maps.Copy(args, scope.SQLArguments()) - - _, err := tx.Exec(ctx, q, args) - if err != nil { - return fmt.Errorf("cannot delete cookie pattern: %w", err) - } - - return nil -} - -func (cps *CookiePatterns) RefreshLastMatchedAtByCookieBannerID( - ctx context.Context, - tx pg.Tx, - scope Scoper, - cookieBannerID gid.GID, -) error { - q := ` -UPDATE cookie_patterns -SET - last_matched_at = sub.max_detected -FROM ( - SELECT cookie_pattern_id, MAX(last_detected_at) AS max_detected - FROM cookies - WHERE %[1]s AND cookie_banner_id = @cookie_banner_id - GROUP BY cookie_pattern_id -) sub -WHERE - cookie_patterns.id = sub.cookie_pattern_id - AND %[1]s - AND cookie_patterns.cookie_banner_id = @cookie_banner_id -` - - q = fmt.Sprintf(q, scope.SQLFragment()) - - args := pgx.StrictNamedArgs{"cookie_banner_id": cookieBannerID} - maps.Copy(args, scope.SQLArguments()) - - _, err := tx.Exec(ctx, q, args) - if err != nil { - return fmt.Errorf("cannot refresh last_matched_at for banner patterns: %w", err) - } - - return nil -} - -func (cps *CookiePatterns) LoadUncategorisedByCookieBannerID( - ctx context.Context, - conn pg.Querier, - scope Scoper, - cookieBannerID gid.GID, - cursor *page.Cursor[CookiePatternOrderField], - filter *CookiePatternFilter, -) error { - q := ` -SELECT - id, - organization_id, - cookie_banner_id, - cookie_category_id, - pattern, - match_type, - display_name, - max_age_seconds, - description, - source, - excluded, - last_matched_at, - created_at, - updated_at -FROM - cookie_patterns -WHERE - %s - AND cookie_banner_id = @cookie_banner_id - AND cookie_category_id = ( - SELECT id FROM cookie_categories - WHERE cookie_banner_id = @cookie_banner_id - AND kind = @category_kind - AND %s - LIMIT 1 - ) - AND %s - AND %s -` - - q = fmt.Sprintf(q, scope.SQLFragment(), scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment()) - - args := pgx.StrictNamedArgs{ - "cookie_banner_id": cookieBannerID, - "category_kind": CookieCategoryKindUncategorised, - } - maps.Copy(args, scope.SQLArguments()) - maps.Copy(args, filter.SQLArguments()) - maps.Copy(args, cursor.SQLArguments()) - - rows, err := conn.Query(ctx, q, args) - if err != nil { - return fmt.Errorf("cannot query uncategorised cookie patterns: %w", err) - } - - patterns, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[CookiePattern]) - if err != nil { - return fmt.Errorf("cannot collect uncategorised cookie patterns: %w", err) - } - - *cps = patterns - - return nil -} - -func (cps *CookiePatterns) CountUncategorisedByCookieBannerID( - ctx context.Context, - conn pg.Querier, - scope Scoper, - cookieBannerID gid.GID, - filter *CookiePatternFilter, -) (int, error) { - q := ` -SELECT - COUNT(id) -FROM - cookie_patterns -WHERE - %s - AND cookie_banner_id = @cookie_banner_id - AND cookie_category_id = ( - SELECT id FROM cookie_categories - WHERE cookie_banner_id = @cookie_banner_id - AND kind = @category_kind - AND %s - LIMIT 1 - ) - AND %s -` - - q = fmt.Sprintf(q, scope.SQLFragment(), scope.SQLFragment(), filter.SQLFragment()) - - args := pgx.StrictNamedArgs{ - "cookie_banner_id": cookieBannerID, - "category_kind": CookieCategoryKindUncategorised, - } - maps.Copy(args, scope.SQLArguments()) - maps.Copy(args, filter.SQLArguments()) - - row := conn.QueryRow(ctx, q, args) - - var count int - if err := row.Scan(&count); err != nil { - return 0, fmt.Errorf("cannot scan count: %w", err) - } - - return count, nil -} - -func (cps *CookiePatterns) MoveToCategoryByCookieCategoryID( - ctx context.Context, - tx pg.Tx, - scope Scoper, - sourceCategoryID gid.GID, - targetCategoryID gid.GID, -) error { - q := ` -UPDATE cookie_patterns -SET - cookie_category_id = @target_category_id, - updated_at = @updated_at -WHERE - %s - AND cookie_category_id = @source_category_id -` - - q = fmt.Sprintf(q, scope.SQLFragment()) - - args := pgx.StrictNamedArgs{ - "source_category_id": sourceCategoryID, - "target_category_id": targetCategoryID, - "updated_at": time.Now(), - } - maps.Copy(args, scope.SQLArguments()) - - _, err := tx.Exec(ctx, q, args) - if err != nil { - return fmt.Errorf("cannot move cookie patterns to category: %w", err) - } - - return nil -} diff --git a/pkg/coredata/cookie_pattern_order_field.go b/pkg/coredata/cookie_pattern_order_field.go deleted file mode 100644 index 2117aad26..000000000 --- a/pkg/coredata/cookie_pattern_order_field.go +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) 2026 Probo Inc . -// -// 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 - -import "fmt" - -type CookiePatternOrderField string - -const ( - CookiePatternOrderFieldCreatedAt CookiePatternOrderField = "CREATED_AT" - CookiePatternOrderFieldName CookiePatternOrderField = "NAME" - CookiePatternOrderFieldLastMatchedAt CookiePatternOrderField = "LAST_MATCHED_AT" - CookiePatternOrderFieldUpdatedAt CookiePatternOrderField = "UPDATED_AT" - CookiePatternOrderFieldSource CookiePatternOrderField = "SOURCE" -) - -func (p CookiePatternOrderField) Column() string { - switch p { - case CookiePatternOrderFieldCreatedAt: - return "created_at" - case CookiePatternOrderFieldName: - return "display_name" - case CookiePatternOrderFieldLastMatchedAt: - return "COALESCE(last_matched_at, '0001-01-01T00:00:00Z'::timestamptz)" - case CookiePatternOrderFieldUpdatedAt: - return "updated_at" - case CookiePatternOrderFieldSource: - return "source" - } - panic(fmt.Sprintf("unsupported order by: %s", p)) -} - -func (p CookiePatternOrderField) IsValid() bool { - switch p { - case CookiePatternOrderFieldCreatedAt, - CookiePatternOrderFieldName, - CookiePatternOrderFieldLastMatchedAt, - CookiePatternOrderFieldUpdatedAt, - CookiePatternOrderFieldSource: - return true - } - return false -} - -func (p CookiePatternOrderField) String() string { - return string(p) -} - -func (p *CookiePatternOrderField) UnmarshalText(text []byte) error { - *p = CookiePatternOrderField(text) - if !p.IsValid() { - return fmt.Errorf("%s is not a valid CookiePatternOrderField", string(text)) - } - return nil -} - -func (p CookiePatternOrderField) MarshalText() ([]byte, error) { - return []byte(p.String()), nil -} diff --git a/pkg/coredata/entity_type_reg.go b/pkg/coredata/entity_type_reg.go index 973e173dc..aa41c361f 100644 --- a/pkg/coredata/entity_type_reg.go +++ b/pkg/coredata/entity_type_reg.go @@ -111,7 +111,7 @@ const ( CookieEntityType uint16 = 85 CookieBannerTranslationEntityType uint16 = 86 AgentRunEntityType uint16 = 87 - CookiePatternEntityType uint16 = 88 + _ uint16 = 88 // CookiePatternEntityType - removed TrackerPatternEntityType uint16 = 89 DetectedTrackerEntityType uint16 = 90 ) @@ -282,8 +282,6 @@ func NewEntityFromID(id gid.GID) (any, bool) { return &CookieBannerTranslation{ID: id}, true case AgentRunEntityType: return &AgentRun{ID: id}, true - case CookiePatternEntityType: - return &CookiePattern{ID: id}, true case TrackerPatternEntityType: return &TrackerPattern{ID: id}, true case DetectedTrackerEntityType: diff --git a/pkg/coredata/tracker_pattern.go b/pkg/coredata/tracker_pattern.go index 7bf86c395..ae214f5e5 100644 --- a/pkg/coredata/tracker_pattern.go +++ b/pkg/coredata/tracker_pattern.go @@ -30,40 +30,40 @@ import ( type ( TrackerPattern struct { - ID gid.GID `db:"id"` - OrganizationID gid.GID `db:"organization_id"` - CookieBannerID gid.GID `db:"cookie_banner_id"` - CookieCategoryID gid.GID `db:"cookie_category_id"` - TrackerType TrackerType `db:"tracker_type"` - Pattern string `db:"pattern"` - MatchType CookiePatternMatchType `db:"match_type"` - DisplayName string `db:"display_name"` - Description string `db:"description"` - Excluded bool `db:"excluded"` - MaxAgeSeconds *int `db:"max_age_seconds"` - Source *CookieSource `db:"source"` - LastMatchedAt *time.Time `db:"last_matched_at"` - 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"` + CookieCategoryID gid.GID `db:"cookie_category_id"` + TrackerType TrackerType `db:"tracker_type"` + Pattern string `db:"pattern"` + MatchType TrackerPatternMatchType `db:"match_type"` + DisplayName string `db:"display_name"` + Description string `db:"description"` + Excluded bool `db:"excluded"` + MaxAgeSeconds *int `db:"max_age_seconds"` + Source *CookieSource `db:"source"` + LastMatchedAt *time.Time `db:"last_matched_at"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` } TrackerPatterns []*TrackerPattern ) -func (tp *TrackerPattern) CursorKey(field CookiePatternOrderField) page.CursorKey { +func (tp *TrackerPattern) CursorKey(field TrackerPatternOrderField) page.CursorKey { switch field { - case CookiePatternOrderFieldCreatedAt: + case TrackerPatternOrderFieldCreatedAt: return page.NewCursorKey(tp.ID, tp.CreatedAt) - case CookiePatternOrderFieldName: + case TrackerPatternOrderFieldName: return page.NewCursorKey(tp.ID, tp.DisplayName) - case CookiePatternOrderFieldLastMatchedAt: + case TrackerPatternOrderFieldLastMatchedAt: if tp.LastMatchedAt == nil { return page.NewCursorKey(tp.ID, time.Time{}) } return page.NewCursorKey(tp.ID, *tp.LastMatchedAt) - case CookiePatternOrderFieldUpdatedAt: + case TrackerPatternOrderFieldUpdatedAt: return page.NewCursorKey(tp.ID, tp.UpdatedAt) - case CookiePatternOrderFieldSource: + case TrackerPatternOrderFieldSource: if tp.Source == nil { return page.NewCursorKey(tp.ID, "") } @@ -254,8 +254,8 @@ LIMIT 1; "cookie_banner_id": cookieBannerID, "tracker_type": trackerType, "identifier": identifier, - "match_type_prefix": CookiePatternMatchTypePrefix, - "match_type_exact": CookiePatternMatchTypeExact, + "match_type_prefix": TrackerPatternMatchTypePrefix, + "match_type_exact": TrackerPatternMatchTypeExact, } maps.Copy(args, scope.SQLArguments()) @@ -504,7 +504,7 @@ func (tps *TrackerPatterns) LoadAllByCookieBannerID( conn pg.Querier, scope Scoper, cookieBannerID gid.GID, - filter *CookiePatternFilter, + filter *TrackerPatternFilter, trackerType *TrackerType, ) error { trackerTypeFragment := "TRUE" @@ -605,8 +605,8 @@ func (tps *TrackerPatterns) LoadUncategorisedByCookieBannerID( conn pg.Querier, scope Scoper, cookieBannerID gid.GID, - cursor *page.Cursor[CookiePatternOrderField], - filter *CookiePatternFilter, + cursor *page.Cursor[TrackerPatternOrderField], + filter *TrackerPatternFilter, ) error { q := ` SELECT @@ -671,7 +671,7 @@ func (tps *TrackerPatterns) CountUncategorisedByCookieBannerID( conn pg.Querier, scope Scoper, cookieBannerID gid.GID, - filter *CookiePatternFilter, + filter *TrackerPatternFilter, ) (int, error) { q := ` SELECT @@ -709,3 +709,120 @@ WHERE return count, nil } + +func (tps *TrackerPatterns) LoadByCookieCategoryID( + ctx context.Context, + conn pg.Querier, + scope Scoper, + cookieCategoryID gid.GID, + cursor *page.Cursor[TrackerPatternOrderField], +) error { + q := ` +SELECT + id, + organization_id, + cookie_banner_id, + cookie_category_id, + tracker_type, + pattern, + match_type, + display_name, + description, + excluded, + max_age_seconds, + source, + last_matched_at, + created_at, + updated_at +FROM + tracker_patterns +WHERE + %s + AND cookie_category_id = @cookie_category_id + AND %s +` + + q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) + + args := pgx.StrictNamedArgs{"cookie_category_id": cookieCategoryID} + maps.Copy(args, scope.SQLArguments()) + maps.Copy(args, cursor.SQLArguments()) + + rows, err := conn.Query(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot query tracker patterns: %w", err) + } + + patterns, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[TrackerPattern]) + if err != nil { + return fmt.Errorf("cannot collect tracker patterns: %w", err) + } + + *tps = patterns + + return nil +} + +func (tps *TrackerPatterns) CountByCookieCategoryID( + ctx context.Context, + conn pg.Querier, + scope Scoper, + cookieCategoryID gid.GID, +) (int, error) { + q := ` +SELECT + COUNT(id) +FROM + tracker_patterns +WHERE + %s + AND cookie_category_id = @cookie_category_id +` + + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{"cookie_category_id": cookieCategoryID} + maps.Copy(args, scope.SQLArguments()) + + row := conn.QueryRow(ctx, q, args) + + var count int + if err := row.Scan(&count); err != nil { + return 0, fmt.Errorf("cannot scan count: %w", err) + } + + return count, nil +} + +func (tps *TrackerPatterns) MoveToCategoryByCookieCategoryID( + ctx context.Context, + tx pg.Tx, + scope Scoper, + sourceCategoryID gid.GID, + targetCategoryID gid.GID, +) error { + q := ` +UPDATE tracker_patterns +SET + cookie_category_id = @target_category_id, + updated_at = @updated_at +WHERE + %s + AND cookie_category_id = @source_category_id +` + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{ + "source_category_id": sourceCategoryID, + "target_category_id": targetCategoryID, + "updated_at": time.Now(), + } + maps.Copy(args, scope.SQLArguments()) + + _, err := tx.Exec(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot move tracker patterns to category: %w", err) + } + + return nil +} diff --git a/pkg/coredata/cookie_pattern_filter.go b/pkg/coredata/tracker_pattern_filter.go similarity index 86% rename from pkg/coredata/cookie_pattern_filter.go rename to pkg/coredata/tracker_pattern_filter.go index 49fc77d26..e6054c84b 100644 --- a/pkg/coredata/cookie_pattern_filter.go +++ b/pkg/coredata/tracker_pattern_filter.go @@ -19,37 +19,37 @@ import ( "go.probo.inc/probo/pkg/gid" ) -type CookiePatternFilter struct { - matchType *CookiePatternMatchType +type TrackerPatternFilter struct { + matchType *TrackerPatternMatchType cookieCategoryID *gid.GID excluded *bool query *string source *CookieSource } -func NewCookiePatternFilter( - matchType *CookiePatternMatchType, +func NewTrackerPatternFilter( + matchType *TrackerPatternMatchType, cookieCategoryID *gid.GID, excluded *bool, -) *CookiePatternFilter { - return &CookiePatternFilter{ +) *TrackerPatternFilter { + return &TrackerPatternFilter{ matchType: matchType, cookieCategoryID: cookieCategoryID, excluded: excluded, } } -func (f *CookiePatternFilter) WithQuery(query *string) *CookiePatternFilter { +func (f *TrackerPatternFilter) WithQuery(query *string) *TrackerPatternFilter { f.query = query return f } -func (f *CookiePatternFilter) WithSource(source *CookieSource) *CookiePatternFilter { +func (f *TrackerPatternFilter) WithSource(source *CookieSource) *TrackerPatternFilter { f.source = source return f } -func (f *CookiePatternFilter) SQLFragment() string { +func (f *TrackerPatternFilter) SQLFragment() string { if f == nil { return "TRUE" } @@ -93,7 +93,7 @@ func (f *CookiePatternFilter) SQLFragment() string { )` } -func (f *CookiePatternFilter) SQLArguments() pgx.StrictNamedArgs { +func (f *TrackerPatternFilter) SQLArguments() pgx.StrictNamedArgs { if f == nil { return pgx.StrictNamedArgs{} } diff --git a/pkg/coredata/cookie_pattern_match_type.go b/pkg/coredata/tracker_pattern_match_type.go similarity index 52% rename from pkg/coredata/cookie_pattern_match_type.go rename to pkg/coredata/tracker_pattern_match_type.go index 7e5cf321c..67dd21f88 100644 --- a/pkg/coredata/cookie_pattern_match_type.go +++ b/pkg/coredata/tracker_pattern_match_type.go @@ -19,25 +19,25 @@ import ( "fmt" ) -type CookiePatternMatchType string +type TrackerPatternMatchType string const ( - CookiePatternMatchTypeExact CookiePatternMatchType = "EXACT" - CookiePatternMatchTypePrefix CookiePatternMatchType = "PREFIX" + TrackerPatternMatchTypeExact TrackerPatternMatchType = "EXACT" + TrackerPatternMatchTypePrefix TrackerPatternMatchType = "PREFIX" ) -func CookiePatternMatchTypes() []CookiePatternMatchType { - return []CookiePatternMatchType{ - CookiePatternMatchTypeExact, - CookiePatternMatchTypePrefix, +func TrackerPatternMatchTypes() []TrackerPatternMatchType { + return []TrackerPatternMatchType{ + TrackerPatternMatchTypeExact, + TrackerPatternMatchTypePrefix, } } -func (m CookiePatternMatchType) String() string { +func (m TrackerPatternMatchType) String() string { return string(m) } -func (m *CookiePatternMatchType) Scan(value any) error { +func (m *TrackerPatternMatchType) Scan(value any) error { var v string switch val := value.(type) { case string: @@ -45,26 +45,26 @@ func (m *CookiePatternMatchType) Scan(value any) error { case []byte: v = string(val) default: - return fmt.Errorf("unsupported type for CookiePatternMatchType: %T", value) + return fmt.Errorf("unsupported type for TrackerPatternMatchType: %T", value) } - switch CookiePatternMatchType(v) { - case CookiePatternMatchTypeExact: - *m = CookiePatternMatchTypeExact - case CookiePatternMatchTypePrefix: - *m = CookiePatternMatchTypePrefix + switch TrackerPatternMatchType(v) { + case TrackerPatternMatchTypeExact: + *m = TrackerPatternMatchTypeExact + case TrackerPatternMatchTypePrefix: + *m = TrackerPatternMatchTypePrefix default: - return fmt.Errorf("invalid CookiePatternMatchType value: %q", v) + return fmt.Errorf("invalid TrackerPatternMatchType value: %q", v) } return nil } -func (m CookiePatternMatchType) Value() (driver.Value, error) { +func (m TrackerPatternMatchType) Value() (driver.Value, error) { switch m { - case CookiePatternMatchTypeExact, - CookiePatternMatchTypePrefix: + case TrackerPatternMatchTypeExact, + TrackerPatternMatchTypePrefix: return string(m), nil default: - return nil, fmt.Errorf("invalid CookiePatternMatchType: %s", m) + return nil, fmt.Errorf("invalid TrackerPatternMatchType: %s", m) } } diff --git a/pkg/coredata/tracker_pattern_order_field.go b/pkg/coredata/tracker_pattern_order_field.go new file mode 100644 index 000000000..74af57984 --- /dev/null +++ b/pkg/coredata/tracker_pattern_order_field.go @@ -0,0 +1,71 @@ +// Copyright (c) 2026 Probo Inc . +// +// 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 + +import "fmt" + +type TrackerPatternOrderField string + +const ( + TrackerPatternOrderFieldCreatedAt TrackerPatternOrderField = "CREATED_AT" + TrackerPatternOrderFieldName TrackerPatternOrderField = "NAME" + TrackerPatternOrderFieldLastMatchedAt TrackerPatternOrderField = "LAST_MATCHED_AT" + TrackerPatternOrderFieldUpdatedAt TrackerPatternOrderField = "UPDATED_AT" + TrackerPatternOrderFieldSource TrackerPatternOrderField = "SOURCE" +) + +func (p TrackerPatternOrderField) Column() string { + switch p { + case TrackerPatternOrderFieldCreatedAt: + return "created_at" + case TrackerPatternOrderFieldName: + return "display_name" + case TrackerPatternOrderFieldLastMatchedAt: + return "COALESCE(last_matched_at, '0001-01-01T00:00:00Z'::timestamptz)" + case TrackerPatternOrderFieldUpdatedAt: + return "updated_at" + case TrackerPatternOrderFieldSource: + return "source" + } + panic(fmt.Sprintf("unsupported order by: %s", p)) +} + +func (p TrackerPatternOrderField) IsValid() bool { + switch p { + case TrackerPatternOrderFieldCreatedAt, + TrackerPatternOrderFieldName, + TrackerPatternOrderFieldLastMatchedAt, + TrackerPatternOrderFieldUpdatedAt, + TrackerPatternOrderFieldSource: + return true + } + return false +} + +func (p TrackerPatternOrderField) String() string { + return string(p) +} + +func (p *TrackerPatternOrderField) UnmarshalText(text []byte) error { + *p = TrackerPatternOrderField(text) + if !p.IsValid() { + return fmt.Errorf("%s is not a valid TrackerPatternOrderField", string(text)) + } + return nil +} + +func (p TrackerPatternOrderField) MarshalText() ([]byte, error) { + return []byte(p.String()), nil +} diff --git a/pkg/probo/actions.go b/pkg/probo/actions.go index 1d7a94403..e002bf241 100644 --- a/pkg/probo/actions.go +++ b/pkg/probo/actions.go @@ -403,12 +403,12 @@ const ( ActionCookieUpdate = "core:cookie:update" ActionCookieDelete = "core:cookie:delete" - // CookiePattern actions - ActionCookiePatternGet = "core:cookie-pattern:get" - ActionCookiePatternList = "core:cookie-pattern:list" - ActionCookiePatternCreate = "core:cookie-pattern:create" - ActionCookiePatternUpdate = "core:cookie-pattern:update" - ActionCookiePatternDelete = "core:cookie-pattern:delete" + // TrackerPattern actions + ActionTrackerPatternGet = "core:tracker-pattern:get" + ActionTrackerPatternList = "core:tracker-pattern:list" + ActionTrackerPatternCreate = "core:tracker-pattern:create" + ActionTrackerPatternUpdate = "core:tracker-pattern:update" + ActionTrackerPatternDelete = "core:tracker-pattern:delete" // CookieConsentRecord actions ActionCookieConsentRecordList = "core:cookie-consent-record:list" diff --git a/pkg/server/api/console/v1/cookie_banner_resolvers.go b/pkg/server/api/console/v1/cookie_banner_resolvers.go index 10a5dce46..819544351 100644 --- a/pkg/server/api/console/v1/cookie_banner_resolvers.go +++ b/pkg/server/api/console/v1/cookie_banner_resolvers.go @@ -177,54 +177,18 @@ func (r *cookieBannerResolver) ConsentRecords(ctx context.Context, obj *types.Co return types.NewCookieConsentRecordConnection(p, r, obj.ID, coredataFilter), nil } -// UncategorisedPatterns is the resolver for the uncategorisedPatterns field. -func (r *cookieBannerResolver) UncategorisedPatterns(ctx context.Context, obj *types.CookieBanner, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.CookiePatternOrderBy, filter *types.CookiePatternFilter) (*types.CookiePatternConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionCookiePatternList); err != nil { - return nil, err - } - - pageOrderBy := page.OrderBy[coredata.CookiePatternOrderField]{ - Field: coredata.CookiePatternOrderFieldName, - Direction: page.OrderDirectionAsc, - } - if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.CookiePatternOrderField]{ - Field: orderBy.Field, - Direction: orderBy.Direction, - } - } - - cursor := types.NewCursor(first, after, last, before, pageOrderBy) - scope := coredata.NewScopeFromObjectID(obj.ID) - - coredataFilter := coredata.NewCookiePatternFilter(nil, nil, nil) - if filter != nil { - coredataFilter = coredataFilter.WithQuery(filter.Query).WithSource(filter.Source) - } - - patterns, err := r.cookieBanner.ListUncategorisedCookiePatterns(ctx, scope, obj.ID, cursor, coredataFilter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot list uncategorised cookie patterns", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - p := page.NewPage(patterns, cursor) - - return types.NewCookiePatternConnectionWithFilter(p, r, obj.ID, coredataFilter), nil -} - // UncategorisedTrackerPatterns is the resolver for the uncategorisedTrackerPatterns field. func (r *cookieBannerResolver) UncategorisedTrackerPatterns(ctx context.Context, obj *types.CookieBanner, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TrackerPatternOrderBy, filter *types.TrackerPatternFilter) (*types.TrackerPatternConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionCookiePatternList); err != nil { + if err := r.authorize(ctx, obj.ID, probo.ActionTrackerPatternList); err != nil { return nil, err } - pageOrderBy := page.OrderBy[coredata.CookiePatternOrderField]{ - Field: coredata.CookiePatternOrderFieldName, + pageOrderBy := page.OrderBy[coredata.TrackerPatternOrderField]{ + Field: coredata.TrackerPatternOrderFieldName, Direction: page.OrderDirectionAsc, } if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.CookiePatternOrderField]{ + pageOrderBy = page.OrderBy[coredata.TrackerPatternOrderField]{ Field: orderBy.Field, Direction: orderBy.Direction, } @@ -233,7 +197,7 @@ func (r *cookieBannerResolver) UncategorisedTrackerPatterns(ctx context.Context, cursor := types.NewCursor(first, after, last, before, pageOrderBy) scope := coredata.NewScopeFromObjectID(obj.ID) - coredataFilter := coredata.NewCookiePatternFilter(nil, nil, nil) + coredataFilter := coredata.NewTrackerPatternFilter(nil, nil, nil) if filter != nil { coredataFilter = coredataFilter.WithQuery(filter.Query).WithSource(filter.Source) } @@ -338,18 +302,18 @@ func (r *cookieCategoryResolver) CookieBanner(ctx context.Context, obj *types.Co return types.NewCookieBanner(banner), nil } -// CookiePatterns is the resolver for the cookiePatterns field. -func (r *cookieCategoryResolver) CookiePatterns(ctx context.Context, obj *types.CookieCategory, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.CookiePatternOrderBy) (*types.CookiePatternConnection, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionCookiePatternList); err != nil { +// TrackerPatterns is the resolver for the trackerPatterns field. +func (r *cookieCategoryResolver) TrackerPatterns(ctx context.Context, obj *types.CookieCategory, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.TrackerPatternOrderBy) (*types.TrackerPatternConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionTrackerPatternList); err != nil { return nil, err } - pageOrderBy := page.OrderBy[coredata.CookiePatternOrderField]{ - Field: coredata.CookiePatternOrderFieldCreatedAt, + pageOrderBy := page.OrderBy[coredata.TrackerPatternOrderField]{ + Field: coredata.TrackerPatternOrderFieldCreatedAt, Direction: page.OrderDirectionAsc, } if orderBy != nil { - pageOrderBy = page.OrderBy[coredata.CookiePatternOrderField]{ + pageOrderBy = page.OrderBy[coredata.TrackerPatternOrderField]{ Field: orderBy.Field, Direction: orderBy.Direction, } @@ -358,15 +322,15 @@ func (r *cookieCategoryResolver) CookiePatterns(ctx context.Context, obj *types. cursor := types.NewCursor(first, after, last, before, pageOrderBy) scope := coredata.NewScopeFromObjectID(obj.ID) - patterns, err := r.cookieBanner.ListCookiePatternsForCategory(ctx, scope, obj.ID, cursor) + patterns, err := r.cookieBanner.ListTrackerPatternsForCategory(ctx, scope, obj.ID, cursor) if err != nil { - r.logger.ErrorCtx(ctx, "cannot list cookie patterns", log.Error(err)) + r.logger.ErrorCtx(ctx, "cannot list tracker patterns", log.Error(err)) return nil, gqlutils.Internal(ctx) } p := page.NewPage(patterns, cursor) - return types.NewCookiePatternConnection(p, r, obj.ID), nil + return types.NewTrackerPatternConnection(p, r, obj.ID), nil } // Permission is the resolver for the permission field. @@ -391,74 +355,6 @@ func (r *cookieCategoryConnectionResolver) TotalCount(ctx context.Context, obj * return count, nil } -// CookieCategory is the resolver for the cookieCategory field. -func (r *cookiePatternResolver) CookieCategory(ctx context.Context, obj *types.CookiePattern) (*types.CookieCategory, error) { - if err := r.authorize(ctx, obj.CookieCategory.ID, probo.ActionCookieCategoryGet); err != nil { - return nil, err - } - - loaders := dataloader.FromContext(ctx) - - category, err := loaders.CookieCategory.Load(ctx, obj.CookieCategory.ID) - if err != nil { - if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) { - return nil, nil - } - r.logger.ErrorCtx(ctx, "cannot get cookie category", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return types.NewCookieCategory(category), nil -} - -// CookieCount is the resolver for the cookieCount field. -func (r *cookiePatternResolver) CookieCount(ctx context.Context, obj *types.CookiePattern) (int, error) { - if err := r.authorize(ctx, obj.ID, probo.ActionCookiePatternGet); err != nil { - return 0, err - } - - scope := coredata.NewScopeFromObjectID(obj.ID) - - count, err := r.cookieBanner.CountCookiesForPattern(ctx, scope, obj.ID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count cookies for pattern", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - - return count, nil -} - -// Permission is the resolver for the permission field. -func (r *cookiePatternResolver) Permission(ctx context.Context, obj *types.CookiePattern, action string) (bool, error) { - return r.Resolver.Permission(ctx, obj, action) -} - -// TotalCount is the resolver for the totalCount field. -func (r *cookiePatternConnectionResolver) TotalCount(ctx context.Context, obj *types.CookiePatternConnection) (int, error) { - if err := r.authorize(ctx, obj.ParentID, probo.ActionCookiePatternList); err != nil { - return 0, err - } - - scope := coredata.NewScopeFromObjectID(obj.ParentID) - - switch obj.Resolver.(type) { - case *cookieBannerResolver: - count, err := r.cookieBanner.CountUncategorisedCookiePatterns(ctx, scope, obj.ParentID, obj.Filter) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count uncategorised cookie patterns", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - default: - count, err := r.cookieBanner.CountCookiePatternsForCategory(ctx, scope, obj.ParentID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot count cookie patterns", log.Error(err)) - return 0, gqlutils.Internal(ctx) - } - return count, nil - } -} - // CreateCookieBanner is the resolver for the createCookieBanner field. func (r *mutationResolver) CreateCookieBanner(ctx context.Context, input types.CreateCookieBannerInput) (*types.CreateCookieBannerPayload, error) { if err := r.authorize(ctx, input.OrganizationID, probo.ActionCookieBannerCreate); err != nil { @@ -819,180 +715,6 @@ func (r *mutationResolver) ReorderCookieCategory(ctx context.Context, input type }, nil } -// CreateCookiePattern is the resolver for the createCookiePattern field. -func (r *mutationResolver) CreateCookiePattern(ctx context.Context, input types.CreateCookiePatternInput) (*types.CreateCookiePatternPayload, error) { - if err := r.authorize(ctx, input.CookieCategoryID, probo.ActionCookiePatternCreate); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.CookieCategoryID) - - pattern, err := r.cookieBanner.CreateCookiePattern( - ctx, - scope, - cookiebanner.CreateCookiePatternRequest{ - CookieCategoryID: input.CookieCategoryID, - Pattern: input.Pattern, - MatchType: input.MatchType, - DisplayName: input.DisplayName, - MaxAgeSeconds: input.MaxAgeSeconds, - Description: input.Description, - }, - ) - if err != nil { - if errors.Is(err, cookiebanner.ErrPatternAlreadyExists) { - return nil, gqlutils.Conflict(ctx, err) - } - if errors.Is(err, cookiebanner.ErrCategoryNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot create cookie pattern", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - bannerScope := coredata.NewScopeFromObjectID(pattern.CookieBannerID) - banner, err := r.cookieBanner.GetCookieBanner(ctx, bannerScope, pattern.CookieBannerID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get cookie banner", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.CreateCookiePatternPayload{ - CookiePatternEdge: types.NewCookiePatternEdge(pattern, coredata.CookiePatternOrderFieldCreatedAt), - CookieBanner: types.NewCookieBanner(banner), - }, nil -} - -// UpdateCookiePattern is the resolver for the updateCookiePattern field. -func (r *mutationResolver) UpdateCookiePattern(ctx context.Context, input types.UpdateCookiePatternInput) (*types.UpdateCookiePatternPayload, error) { - if err := r.authorize(ctx, input.CookiePatternID, probo.ActionCookiePatternUpdate); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.CookiePatternID) - - pattern, err := r.cookieBanner.UpdateCookiePattern( - ctx, - scope, - cookiebanner.UpdateCookiePatternRequest{ - CookiePatternID: input.CookiePatternID, - DisplayName: input.DisplayName, - MaxAgeSeconds: gqlutils.UnwrapOmittable(input.MaxAgeSeconds), - Description: input.Description, - Excluded: input.Excluded, - }, - ) - if err != nil { - if errors.Is(err, cookiebanner.ErrCookiePatternNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot update cookie pattern", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - bannerScope := coredata.NewScopeFromObjectID(pattern.CookieBannerID) - banner, err := r.cookieBanner.GetCookieBanner(ctx, bannerScope, pattern.CookieBannerID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get cookie banner", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.UpdateCookiePatternPayload{ - CookiePattern: types.NewCookiePattern(pattern), - CookieBanner: types.NewCookieBanner(banner), - }, nil -} - -// DeleteCookiePattern is the resolver for the deleteCookiePattern field. -func (r *mutationResolver) DeleteCookiePattern(ctx context.Context, input types.DeleteCookiePatternInput) (*types.DeleteCookiePatternPayload, error) { - if err := r.authorize(ctx, input.CookiePatternID, probo.ActionCookiePatternDelete); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.CookiePatternID) - - pattern, err := r.cookieBanner.GetCookiePattern(ctx, scope, input.CookiePatternID) - if err != nil { - if errors.Is(err, cookiebanner.ErrCookieNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - r.logger.ErrorCtx(ctx, "cannot get cookie pattern", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - bannerID := pattern.CookieBannerID - - err = r.cookieBanner.DeleteCookiePattern(ctx, scope, input.CookiePatternID) - if err != nil { - if errors.Is(err, cookiebanner.ErrCookiePatternNotFound) { - return nil, gqlutils.NotFound(ctx, err) - } - r.logger.ErrorCtx(ctx, "cannot delete cookie pattern", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - bannerScope := coredata.NewScopeFromObjectID(bannerID) - banner, err := r.cookieBanner.GetCookieBanner(ctx, bannerScope, bannerID) - if err != nil { - r.logger.ErrorCtx(ctx, "cannot get cookie banner", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - - return &types.DeleteCookiePatternPayload{ - DeletedCookiePatternID: input.CookiePatternID, - CookieBanner: types.NewCookieBanner(banner), - }, nil -} - -// MoveCookiePatternToCategory is the resolver for the moveCookiePatternToCategory field. -func (r *mutationResolver) MoveCookiePatternToCategory(ctx context.Context, input types.MoveCookiePatternToCategoryInput) (*types.MoveCookiePatternToCategoryPayload, error) { - if err := r.authorize(ctx, input.CookiePatternID, probo.ActionCookiePatternUpdate); err != nil { - return nil, err - } - - if err := r.authorize(ctx, input.TargetCookieCategoryID, probo.ActionCookieCategoryUpdate); err != nil { - return nil, err - } - - scope := coredata.NewScopeFromObjectID(input.CookiePatternID) - - result, err := r.cookieBanner.MoveCookiePatternToCategory( - ctx, - scope, - cookiebanner.MoveCookiePatternToCategoryRequest{ - CookiePatternID: input.CookiePatternID, - TargetCookieCategoryID: input.TargetCookieCategoryID, - }, - ) - if err != nil { - switch { - case errors.Is(err, cookiebanner.ErrCategoryNotFound): - return nil, gqlutils.NotFound(ctx, err) - case errors.Is(err, cookiebanner.ErrCookiePatternNotFound): - return nil, gqlutils.NotFound(ctx, err) - case errors.Is(err, cookiebanner.ErrCategoriesBannerMismatch): - return nil, gqlutils.NotFoundf(ctx, "cookie pattern or target category not found") - default: - if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { - return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) - } - r.logger.ErrorCtx(ctx, "cannot move cookie pattern to category", log.Error(err)) - return nil, gqlutils.Internal(ctx) - } - } - - return &types.MoveCookiePatternToCategoryPayload{ - CookiePattern: types.NewCookiePattern(result.CookiePattern), - CookieBanner: types.NewCookieBanner(result.Banner), - }, nil -} - // UpsertCookieBannerTranslation is the resolver for the upsertCookieBannerTranslation field. func (r *mutationResolver) UpsertCookieBannerTranslation(ctx context.Context, input types.UpsertCookieBannerTranslationInput) (*types.UpsertCookieBannerTranslationPayload, error) { if err := r.authorize(ctx, input.CookieBannerID, probo.ActionCookieBannerUpdate); err != nil { @@ -1033,9 +755,64 @@ func (r *mutationResolver) UpsertCookieBannerTranslation(ctx context.Context, in }, nil } +// CreateTrackerPattern is the resolver for the createTrackerPattern field. +func (r *mutationResolver) CreateTrackerPattern(ctx context.Context, input types.CreateTrackerPatternInput) (*types.CreateTrackerPatternPayload, error) { + if err := r.authorize(ctx, input.CookieCategoryID, probo.ActionTrackerPatternCreate); err != nil { + return nil, err + } + + scope := coredata.NewScopeFromObjectID(input.CookieCategoryID) + + trackerType := coredata.TrackerTypeCookie + if input.TrackerType != nil { + trackerType = *input.TrackerType + } + + var description string + if input.Description != nil { + description = *input.Description + } + + pattern, err := r.cookieBanner.CreateTrackerPattern( + ctx, + scope, + cookiebanner.CreateTrackerPatternRequest{ + CookieCategoryID: input.CookieCategoryID, + TrackerType: trackerType, + Pattern: input.Pattern, + MatchType: input.MatchType, + DisplayName: input.DisplayName, + MaxAgeSeconds: input.MaxAgeSeconds, + Description: description, + }, + ) + if err != nil { + if errors.Is(err, cookiebanner.ErrPatternAlreadyExists) { + return nil, gqlutils.Conflictf(ctx, "a pattern with this name already exists in this banner") + } + if validationErrors, ok := errors.AsType[validator.ValidationErrors](err); ok { + return nil, gqlutils.InvalidValidationErrors(ctx, validationErrors) + } + r.logger.ErrorCtx(ctx, "cannot create tracker pattern", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + bannerScope := coredata.NewScopeFromObjectID(pattern.CookieBannerID) + banner, err := r.cookieBanner.GetCookieBanner(ctx, bannerScope, pattern.CookieBannerID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot get cookie banner", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return &types.CreateTrackerPatternPayload{ + TrackerPatternEdge: types.NewTrackerPatternEdge(pattern, coredata.TrackerPatternOrderFieldCreatedAt), + CookieBanner: types.NewCookieBanner(banner), + }, nil +} + // UpdateTrackerPattern is the resolver for the updateTrackerPattern field. func (r *mutationResolver) UpdateTrackerPattern(ctx context.Context, input types.UpdateTrackerPatternInput) (*types.UpdateTrackerPatternPayload, error) { - if err := r.authorize(ctx, input.TrackerPatternID, probo.ActionCookiePatternUpdate); err != nil { + if err := r.authorize(ctx, input.TrackerPatternID, probo.ActionTrackerPatternUpdate); err != nil { return nil, err } @@ -1053,7 +830,7 @@ func (r *mutationResolver) UpdateTrackerPattern(ctx context.Context, input types }, ) if err != nil { - if errors.Is(err, cookiebanner.ErrCookiePatternNotFound) { + if errors.Is(err, cookiebanner.ErrTrackerPatternNotFound) { return nil, gqlutils.NotFound(ctx, err) } r.logger.ErrorCtx(ctx, "cannot update tracker pattern", log.Error(err)) @@ -1075,7 +852,7 @@ func (r *mutationResolver) UpdateTrackerPattern(ctx context.Context, input types // DeleteTrackerPattern is the resolver for the deleteTrackerPattern field. func (r *mutationResolver) DeleteTrackerPattern(ctx context.Context, input types.DeleteTrackerPatternInput) (*types.DeleteTrackerPatternPayload, error) { - if err := r.authorize(ctx, input.TrackerPatternID, probo.ActionCookiePatternDelete); err != nil { + if err := r.authorize(ctx, input.TrackerPatternID, probo.ActionTrackerPatternDelete); err != nil { return nil, err } @@ -1083,7 +860,7 @@ func (r *mutationResolver) DeleteTrackerPattern(ctx context.Context, input types pattern, err := r.cookieBanner.GetTrackerPattern(ctx, scope, input.TrackerPatternID) if err != nil { - if errors.Is(err, cookiebanner.ErrCookiePatternNotFound) { + if errors.Is(err, cookiebanner.ErrTrackerPatternNotFound) { return nil, gqlutils.NotFound(ctx, err) } r.logger.ErrorCtx(ctx, "cannot get tracker pattern", log.Error(err)) @@ -1093,7 +870,7 @@ func (r *mutationResolver) DeleteTrackerPattern(ctx context.Context, input types bannerID := pattern.CookieBannerID if err := r.cookieBanner.DeleteTrackerPattern(ctx, scope, input.TrackerPatternID); err != nil { - if errors.Is(err, cookiebanner.ErrCookiePatternNotFound) { + if errors.Is(err, cookiebanner.ErrTrackerPatternNotFound) { return nil, gqlutils.NotFound(ctx, err) } r.logger.ErrorCtx(ctx, "cannot delete tracker pattern", log.Error(err)) @@ -1115,7 +892,7 @@ func (r *mutationResolver) DeleteTrackerPattern(ctx context.Context, input types // MoveTrackerPatternToCategory is the resolver for the moveTrackerPatternToCategory field. func (r *mutationResolver) MoveTrackerPatternToCategory(ctx context.Context, input types.MoveTrackerPatternToCategoryInput) (*types.MoveTrackerPatternToCategoryPayload, error) { - if err := r.authorize(ctx, input.TrackerPatternID, probo.ActionCookiePatternUpdate); err != nil { + if err := r.authorize(ctx, input.TrackerPatternID, probo.ActionTrackerPatternUpdate); err != nil { return nil, err } @@ -1137,7 +914,7 @@ func (r *mutationResolver) MoveTrackerPatternToCategory(ctx context.Context, inp switch { case errors.Is(err, cookiebanner.ErrCategoryNotFound): return nil, gqlutils.NotFound(ctx, err) - case errors.Is(err, cookiebanner.ErrCookiePatternNotFound): + case errors.Is(err, cookiebanner.ErrTrackerPatternNotFound): return nil, gqlutils.NotFound(ctx, err) case errors.Is(err, cookiebanner.ErrCategoriesBannerMismatch): return nil, gqlutils.NotFoundf(ctx, "tracker pattern or target category not found") @@ -1195,7 +972,7 @@ func (r *trackerPatternResolver) Permission(ctx context.Context, obj *types.Trac func (r *trackerPatternConnectionResolver) TotalCount(ctx context.Context, obj *types.TrackerPatternConnection) (int, error) { scope := coredata.NewScopeFromObjectID(obj.ParentID) - filter := coredata.NewCookiePatternFilter(nil, nil, nil) + filter := coredata.NewTrackerPatternFilter(nil, nil, nil) if obj.Filter != nil { filter = filter.WithQuery(obj.Filter.Query).WithSource(obj.Filter.Source) } @@ -1230,14 +1007,6 @@ func (r *Resolver) CookieCategoryConnection() schema.CookieCategoryConnectionRes return &cookieCategoryConnectionResolver{r} } -// CookiePattern returns schema.CookiePatternResolver implementation. -func (r *Resolver) CookiePattern() schema.CookiePatternResolver { return &cookiePatternResolver{r} } - -// CookiePatternConnection returns schema.CookiePatternConnectionResolver implementation. -func (r *Resolver) CookiePatternConnection() schema.CookiePatternConnectionResolver { - return &cookiePatternConnectionResolver{r} -} - // TrackerPattern returns schema.TrackerPatternResolver implementation. func (r *Resolver) TrackerPattern() schema.TrackerPatternResolver { return &trackerPatternResolver{r} } @@ -1251,7 +1020,5 @@ type cookieBannerConnectionResolver struct{ *Resolver } type cookieBannerVersionResolver struct{ *Resolver } type cookieCategoryResolver struct{ *Resolver } type cookieCategoryConnectionResolver struct{ *Resolver } -type cookiePatternResolver struct{ *Resolver } -type cookiePatternConnectionResolver struct{ *Resolver } type trackerPatternResolver struct{ *Resolver } type trackerPatternConnectionResolver struct{ *Resolver } diff --git a/pkg/server/api/console/v1/graphql/cookie_banner.graphql b/pkg/server/api/console/v1/graphql/cookie_banner.graphql index adae3e563..64916c896 100644 --- a/pkg/server/api/console/v1/graphql/cookie_banner.graphql +++ b/pkg/server/api/console/v1/graphql/cookie_banner.graphql @@ -151,15 +151,6 @@ type CookieBanner implements Node { filter: CookieConsentRecordFilter ): CookieConsentRecordConnection @goField(forceResolver: true) - uncategorisedPatterns( - first: Int - after: CursorKey - last: Int - before: CursorKey - orderBy: CookiePatternOrder - filter: CookiePatternFilter - ): CookiePatternConnection @goField(forceResolver: true) - uncategorisedTrackerPatterns( first: Int after: CursorKey @@ -194,13 +185,13 @@ type CookieCategory implements Node { gcmConsentTypes: [String!]! posthogConsent: Boolean! - cookiePatterns( + trackerPatterns( first: Int after: CursorKey last: Int before: CursorKey - orderBy: CookiePatternOrder - ): CookiePatternConnection @goField(forceResolver: true) + orderBy: TrackerPatternOrder + ): TrackerPatternConnection @goField(forceResolver: true) createdAt: Datetime! updatedAt: Datetime! @@ -208,98 +199,52 @@ type CookieCategory implements Node { permission(action: String!): Boolean! @goField(forceResolver: true) } -enum CookiePatternMatchType +enum TrackerPatternMatchType @goModel( - model: "go.probo.inc/probo/pkg/coredata.CookiePatternMatchType" + model: "go.probo.inc/probo/pkg/coredata.TrackerPatternMatchType" ) { EXACT @goEnum( - value: "go.probo.inc/probo/pkg/coredata.CookiePatternMatchTypeExact" + value: "go.probo.inc/probo/pkg/coredata.TrackerPatternMatchTypeExact" ) PREFIX @goEnum( - value: "go.probo.inc/probo/pkg/coredata.CookiePatternMatchTypePrefix" + value: "go.probo.inc/probo/pkg/coredata.TrackerPatternMatchTypePrefix" ) } -enum CookiePatternOrderField +enum TrackerPatternOrderField @goModel( - model: "go.probo.inc/probo/pkg/coredata.CookiePatternOrderField" + model: "go.probo.inc/probo/pkg/coredata.TrackerPatternOrderField" ) { CREATED_AT @goEnum( - value: "go.probo.inc/probo/pkg/coredata.CookiePatternOrderFieldCreatedAt" + value: "go.probo.inc/probo/pkg/coredata.TrackerPatternOrderFieldCreatedAt" ) NAME @goEnum( - value: "go.probo.inc/probo/pkg/coredata.CookiePatternOrderFieldName" + value: "go.probo.inc/probo/pkg/coredata.TrackerPatternOrderFieldName" ) LAST_MATCHED_AT @goEnum( - value: "go.probo.inc/probo/pkg/coredata.CookiePatternOrderFieldLastMatchedAt" + value: "go.probo.inc/probo/pkg/coredata.TrackerPatternOrderFieldLastMatchedAt" ) UPDATED_AT @goEnum( - value: "go.probo.inc/probo/pkg/coredata.CookiePatternOrderFieldUpdatedAt" + value: "go.probo.inc/probo/pkg/coredata.TrackerPatternOrderFieldUpdatedAt" ) SOURCE @goEnum( - value: "go.probo.inc/probo/pkg/coredata.CookiePatternOrderFieldSource" + value: "go.probo.inc/probo/pkg/coredata.TrackerPatternOrderFieldSource" ) } -input CookiePatternOrder - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.CookiePatternOrderBy" - ) { - direction: OrderDirection! - field: CookiePatternOrderField! -} - -input CookiePatternFilter { - query: String - source: CookieSource -} - -type CookiePattern implements Node { - id: ID! - cookieCategory: CookieCategory @goField(forceResolver: true) - trackerType: TrackerType! - pattern: String! - matchType: CookiePatternMatchType! - displayName: String! - maxAgeSeconds: Int - description: String! - source: CookieSource - excluded: Boolean! - cookieCount: Int! @goField(forceResolver: true) - lastMatchedAt: Datetime - createdAt: Datetime! - updatedAt: Datetime! - - permission(action: String!): Boolean! @goField(forceResolver: true) -} - -type CookiePatternConnection - @goModel( - model: "go.probo.inc/probo/pkg/server/api/console/v1/types.CookiePatternConnection" - ) { - totalCount: Int! @goField(forceResolver: true) - edges: [CookiePatternEdge!]! - pageInfo: PageInfo! -} - -type CookiePatternEdge { - cursor: CursorKey! - node: CookiePattern! -} - type TrackerPattern implements Node { id: ID! cookieCategory: CookieCategory @goField(forceResolver: true) trackerType: TrackerType! pattern: String! - matchType: CookiePatternMatchType! + matchType: TrackerPatternMatchType! displayName: String! maxAgeSeconds: Int description: String! @@ -332,7 +277,7 @@ input TrackerPatternOrder model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrackerPatternOrderBy" ) { direction: OrderDirection! - field: CookiePatternOrderField! + field: TrackerPatternOrderField! } input TrackerPatternFilter @@ -426,21 +371,12 @@ extend type Mutation { reorderCookieCategory( input: ReorderCookieCategoryInput! ): ReorderCookieCategoryPayload! - createCookiePattern( - input: CreateCookiePatternInput! - ): CreateCookiePatternPayload! - updateCookiePattern( - input: UpdateCookiePatternInput! - ): UpdateCookiePatternPayload! - deleteCookiePattern( - input: DeleteCookiePatternInput! - ): DeleteCookiePatternPayload! - moveCookiePatternToCategory( - input: MoveCookiePatternToCategoryInput! - ): MoveCookiePatternToCategoryPayload! upsertCookieBannerTranslation( input: UpsertCookieBannerTranslationInput! ): UpsertCookieBannerTranslationPayload! + createTrackerPattern( + input: CreateTrackerPatternInput! + ): CreateTrackerPatternPayload! updateTrackerPattern( input: UpdateTrackerPatternInput! ): UpdateTrackerPatternPayload! @@ -558,53 +494,6 @@ type ReorderCookieCategoryPayload { cookieBanner: CookieBanner! } -input CreateCookiePatternInput { - cookieCategoryId: ID! - trackerType: TrackerType - pattern: String! - matchType: CookiePatternMatchType! - displayName: String! - maxAgeSeconds: Int - description: String! -} - -input UpdateCookiePatternInput { - cookiePatternId: ID! - displayName: String - maxAgeSeconds: Int @goField(omittable: true) - description: String - excluded: Boolean -} - -input DeleteCookiePatternInput { - cookiePatternId: ID! -} - -input MoveCookiePatternToCategoryInput { - cookiePatternId: ID! - targetCookieCategoryId: ID! -} - -type CreateCookiePatternPayload { - cookiePatternEdge: CookiePatternEdge! - cookieBanner: CookieBanner! -} - -type UpdateCookiePatternPayload { - cookiePattern: CookiePattern! - cookieBanner: CookieBanner! -} - -type DeleteCookiePatternPayload { - deletedCookiePatternId: ID! - cookieBanner: CookieBanner! -} - -type MoveCookiePatternToCategoryPayload { - cookiePattern: CookiePattern! - cookieBanner: CookieBanner! -} - input UpsertCookieBannerTranslationInput { cookieBannerId: ID! language: String! @@ -616,6 +505,16 @@ type UpsertCookieBannerTranslationPayload { cookieBanner: CookieBanner! } +input CreateTrackerPatternInput { + cookieCategoryId: ID! + trackerType: TrackerType + pattern: String! + matchType: TrackerPatternMatchType! + displayName: String! + maxAgeSeconds: Int + description: String +} + input UpdateTrackerPatternInput { trackerPatternId: ID! displayName: String @@ -633,6 +532,11 @@ input MoveTrackerPatternToCategoryInput { targetCookieCategoryId: ID! } +type CreateTrackerPatternPayload { + trackerPatternEdge: TrackerPatternEdge! + cookieBanner: CookieBanner! +} + type UpdateTrackerPatternPayload { trackerPattern: TrackerPattern! cookieBanner: CookieBanner! diff --git a/pkg/server/api/console/v1/types/cookie_pattern.go b/pkg/server/api/console/v1/types/cookie_pattern.go deleted file mode 100644 index caace87ae..000000000 --- a/pkg/server/api/console/v1/types/cookie_pattern.go +++ /dev/null @@ -1,119 +0,0 @@ -// Copyright (c) 2026 Probo Inc . -// -// 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 types - -import ( - "go.probo.inc/probo/pkg/coredata" - "go.probo.inc/probo/pkg/gid" - "go.probo.inc/probo/pkg/page" -) - -type ( - CookiePatternOrderBy OrderBy[coredata.CookiePatternOrderField] - - CookiePatternConnection struct { - TotalCount int - Edges []*CookiePatternEdge - PageInfo PageInfo - - Resolver any - ParentID gid.GID - Filter *coredata.CookiePatternFilter - } -) - -func NewCookiePatternConnection( - p *page.Page[*coredata.CookiePattern, coredata.CookiePatternOrderField], - parentType any, - parentID gid.GID, -) *CookiePatternConnection { - edges := make([]*CookiePatternEdge, len(p.Data)) - - for i := range edges { - edges[i] = NewCookiePatternEdge(p.Data[i], p.Cursor.OrderBy.Field) - } - - return &CookiePatternConnection{ - Edges: edges, - PageInfo: *NewPageInfo(p), - - Resolver: parentType, - ParentID: parentID, - } -} - -func NewCookiePatternConnectionWithFilter( - p *page.Page[*coredata.CookiePattern, coredata.CookiePatternOrderField], - parentType any, - parentID gid.GID, - filter *coredata.CookiePatternFilter, -) *CookiePatternConnection { - conn := NewCookiePatternConnection(p, parentType, parentID) - conn.Filter = filter - return conn -} - -func NewCookiePatternEdge(cp *coredata.CookiePattern, orderBy coredata.CookiePatternOrderField) *CookiePatternEdge { - return &CookiePatternEdge{ - Cursor: cp.CursorKey(orderBy), - Node: NewCookiePattern(cp), - } -} - -func NewCookiePattern(cp *coredata.CookiePattern) *CookiePattern { - return &CookiePattern{ - ID: cp.ID, - CookieCategory: &CookieCategory{ - ID: cp.CookieCategoryID, - CookieBanner: &CookieBanner{ - ID: cp.CookieBannerID, - }, - }, - TrackerType: coredata.TrackerTypeCookie, - Pattern: cp.Pattern, - MatchType: cp.MatchType, - DisplayName: cp.DisplayName, - MaxAgeSeconds: cp.MaxAgeSeconds, - Description: cp.Description, - Source: &cp.Source, - Excluded: cp.Excluded, - LastMatchedAt: cp.LastMatchedAt, - CreatedAt: cp.CreatedAt, - UpdatedAt: cp.UpdatedAt, - } -} - -func NewTrackerPattern(tp *coredata.TrackerPattern) *CookiePattern { - return &CookiePattern{ - ID: tp.ID, - CookieCategory: &CookieCategory{ - ID: tp.CookieCategoryID, - CookieBanner: &CookieBanner{ - ID: tp.CookieBannerID, - }, - }, - TrackerType: tp.TrackerType, - Pattern: tp.Pattern, - MatchType: tp.MatchType, - DisplayName: tp.DisplayName, - MaxAgeSeconds: tp.MaxAgeSeconds, - Description: tp.Description, - Source: tp.Source, - Excluded: tp.Excluded, - LastMatchedAt: tp.LastMatchedAt, - CreatedAt: tp.CreatedAt, - UpdatedAt: tp.UpdatedAt, - } -} diff --git a/pkg/server/api/console/v1/types/tracker_pattern.go b/pkg/server/api/console/v1/types/tracker_pattern.go index 26d625662..82b9e04f1 100644 --- a/pkg/server/api/console/v1/types/tracker_pattern.go +++ b/pkg/server/api/console/v1/types/tracker_pattern.go @@ -21,7 +21,7 @@ import ( ) type ( - TrackerPatternOrderBy OrderBy[coredata.CookiePatternOrderField] + TrackerPatternOrderBy OrderBy[coredata.TrackerPatternOrderField] TrackerPatternConnection struct { TotalCount int @@ -41,7 +41,7 @@ type ( ) func NewTrackerPatternConnection( - p *page.Page[*coredata.TrackerPattern, coredata.CookiePatternOrderField], + p *page.Page[*coredata.TrackerPattern, coredata.TrackerPatternOrderField], parentType any, parentID gid.GID, ) *TrackerPatternConnection { @@ -61,7 +61,7 @@ func NewTrackerPatternConnection( } func NewTrackerPatternConnectionWithFilter( - p *page.Page[*coredata.TrackerPattern, coredata.CookiePatternOrderField], + p *page.Page[*coredata.TrackerPattern, coredata.TrackerPatternOrderField], parentType any, parentID gid.GID, filter *TrackerPatternFilter, @@ -71,7 +71,7 @@ func NewTrackerPatternConnectionWithFilter( return conn } -func NewTrackerPatternEdge(tp *coredata.TrackerPattern, orderBy coredata.CookiePatternOrderField) *TrackerPatternEdge { +func NewTrackerPatternEdge(tp *coredata.TrackerPattern, orderBy coredata.TrackerPatternOrderField) *TrackerPatternEdge { return &TrackerPatternEdge{ Cursor: tp.CursorKey(orderBy), Node: NewTrackerPatternNode(tp), diff --git a/pkg/server/api/mcp/v1/schema.resolvers.go b/pkg/server/api/mcp/v1/schema.resolvers.go index 5cb9c1001..0ce25cf3d 100644 --- a/pkg/server/api/mcp/v1/schema.resolvers.go +++ b/pkg/server/api/mcp/v1/schema.resolvers.go @@ -4941,49 +4941,50 @@ func (r *Resolver) ReorderCookieCategoryTool(ctx context.Context, req *mcp.CallT return nil, types.ReorderCookieCategoryOutput{CookieCategory: types.NewCookieCategory(category)}, nil } -func (r *Resolver) ListCookiePatternsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListCookiePatternsInput) (*mcp.CallToolResult, types.ListCookiePatternsOutput, error) { - r.MustAuthorize(ctx, input.CookieCategoryID, probo.ActionCookiePatternList) +func (r *Resolver) ListTrackerPatternsTool(ctx context.Context, req *mcp.CallToolRequest, input *types.ListTrackerPatternsInput) (*mcp.CallToolResult, types.ListTrackerPatternsOutput, error) { + r.MustAuthorize(ctx, input.CookieCategoryID, probo.ActionTrackerPatternList) scope := coredata.NewScopeFromObjectID(input.CookieCategoryID) - cursor := types.NewCursor(input.Size, input.Cursor, page.OrderBy[coredata.CookiePatternOrderField]{Field: coredata.CookiePatternOrderFieldCreatedAt, Direction: page.OrderDirectionAsc}) - patterns, err := r.cookieBanner.ListCookiePatternsForCategory(ctx, scope, input.CookieCategoryID, cursor) + cursor := types.NewCursor(input.Size, input.Cursor, page.OrderBy[coredata.TrackerPatternOrderField]{Field: coredata.TrackerPatternOrderFieldCreatedAt, Direction: page.OrderDirectionAsc}) + patterns, err := r.cookieBanner.ListTrackerPatternsForCategory(ctx, scope, input.CookieCategoryID, cursor) if err != nil { - panic(fmt.Errorf("cannot list cookie patterns: %w", err)) + panic(fmt.Errorf("cannot list tracker patterns: %w", err)) } p := page.NewPage(patterns, cursor) - return nil, types.NewListCookiePatternsOutput(p), nil + return nil, types.NewListTrackerPatternsOutput(p), nil } -func (r *Resolver) GetCookiePatternTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetCookiePatternInput) (*mcp.CallToolResult, types.GetCookiePatternOutput, error) { - r.MustAuthorize(ctx, input.ID, probo.ActionCookiePatternGet) +func (r *Resolver) GetTrackerPatternTool(ctx context.Context, req *mcp.CallToolRequest, input *types.GetTrackerPatternInput) (*mcp.CallToolResult, types.GetTrackerPatternOutput, error) { + r.MustAuthorize(ctx, input.ID, probo.ActionTrackerPatternGet) scope := coredata.NewScopeFromObjectID(input.ID) - pattern, err := r.cookieBanner.GetCookiePattern(ctx, scope, input.ID) + pattern, err := r.cookieBanner.GetTrackerPattern(ctx, scope, input.ID) if err != nil { - return nil, types.GetCookiePatternOutput{}, fmt.Errorf("cannot get cookie pattern: %w", err) + return nil, types.GetTrackerPatternOutput{}, fmt.Errorf("cannot get tracker pattern: %w", err) } - return nil, types.GetCookiePatternOutput{CookiePattern: types.NewCookiePattern(pattern)}, nil + return nil, types.GetTrackerPatternOutput{TrackerPattern: types.NewTrackerPattern(pattern)}, nil } -func (r *Resolver) AddCookiePatternTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddCookiePatternInput) (*mcp.CallToolResult, types.AddCookiePatternOutput, error) { - r.MustAuthorize(ctx, input.CookieCategoryID, probo.ActionCookiePatternCreate) +func (r *Resolver) AddTrackerPatternTool(ctx context.Context, req *mcp.CallToolRequest, input *types.AddTrackerPatternInput) (*mcp.CallToolResult, types.AddTrackerPatternOutput, error) { + r.MustAuthorize(ctx, input.CookieCategoryID, probo.ActionTrackerPatternCreate) scope := coredata.NewScopeFromObjectID(input.CookieCategoryID) - pattern, err := r.cookieBanner.CreateCookiePattern(ctx, scope, cookiebanner.CreateCookiePatternRequest{ + pattern, err := r.cookieBanner.CreateTrackerPattern(ctx, scope, cookiebanner.CreateTrackerPatternRequest{ CookieCategoryID: input.CookieCategoryID, + TrackerType: coredata.TrackerType(input.TrackerType), Pattern: input.Pattern, - MatchType: coredata.CookiePatternMatchType(input.MatchType), + MatchType: coredata.TrackerPatternMatchType(input.MatchType), DisplayName: input.DisplayName, MaxAgeSeconds: input.MaxAgeSeconds, Description: input.Description, }) if err != nil { - return nil, types.AddCookiePatternOutput{}, fmt.Errorf("cannot create cookie pattern: %w", err) + return nil, types.AddTrackerPatternOutput{}, fmt.Errorf("cannot create tracker pattern: %w", err) } - return nil, types.AddCookiePatternOutput{CookiePattern: types.NewCookiePattern(pattern)}, nil + return nil, types.AddTrackerPatternOutput{TrackerPattern: types.NewTrackerPattern(pattern)}, nil } -func (r *Resolver) UpdateCookiePatternTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateCookiePatternInput) (*mcp.CallToolResult, types.UpdateCookiePatternOutput, error) { - r.MustAuthorize(ctx, input.ID, probo.ActionCookiePatternUpdate) +func (r *Resolver) UpdateTrackerPatternTool(ctx context.Context, req *mcp.CallToolRequest, input *types.UpdateTrackerPatternInput) (*mcp.CallToolResult, types.UpdateTrackerPatternOutput, error) { + r.MustAuthorize(ctx, input.ID, probo.ActionTrackerPatternUpdate) scope := coredata.NewScopeFromObjectID(input.ID) - updateReq := cookiebanner.UpdateCookiePatternRequest{CookiePatternID: input.ID} + updateReq := cookiebanner.UpdateTrackerPatternRequest{TrackerPatternID: input.ID} if v := UnwrapOmittable(input.DisplayName); v != nil && *v != nil { updateReq.DisplayName = *v } @@ -4997,33 +4998,33 @@ func (r *Resolver) UpdateCookiePatternTool(ctx context.Context, req *mcp.CallToo if v := UnwrapOmittable(input.Excluded); v != nil && *v != nil { updateReq.Excluded = *v } - pattern, err := r.cookieBanner.UpdateCookiePattern(ctx, scope, updateReq) + pattern, err := r.cookieBanner.UpdateTrackerPattern(ctx, scope, updateReq) if err != nil { - return nil, types.UpdateCookiePatternOutput{}, fmt.Errorf("cannot update cookie pattern: %w", err) + return nil, types.UpdateTrackerPatternOutput{}, fmt.Errorf("cannot update tracker pattern: %w", err) } - return nil, types.UpdateCookiePatternOutput{CookiePattern: types.NewCookiePattern(pattern)}, nil + return nil, types.UpdateTrackerPatternOutput{TrackerPattern: types.NewTrackerPattern(pattern)}, nil } -func (r *Resolver) DeleteCookiePatternTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteCookiePatternInput) (*mcp.CallToolResult, types.DeleteCookiePatternOutput, error) { - r.MustAuthorize(ctx, input.ID, probo.ActionCookiePatternDelete) +func (r *Resolver) DeleteTrackerPatternTool(ctx context.Context, req *mcp.CallToolRequest, input *types.DeleteTrackerPatternInput) (*mcp.CallToolResult, types.DeleteTrackerPatternOutput, error) { + r.MustAuthorize(ctx, input.ID, probo.ActionTrackerPatternDelete) scope := coredata.NewScopeFromObjectID(input.ID) - if err := r.cookieBanner.DeleteCookiePattern(ctx, scope, input.ID); err != nil { - return nil, types.DeleteCookiePatternOutput{}, fmt.Errorf("cannot delete cookie pattern: %w", err) + if err := r.cookieBanner.DeleteTrackerPattern(ctx, scope, input.ID); err != nil { + return nil, types.DeleteTrackerPatternOutput{}, fmt.Errorf("cannot delete tracker pattern: %w", err) } - return nil, types.DeleteCookiePatternOutput{DeletedID: input.ID}, nil + return nil, types.DeleteTrackerPatternOutput{DeletedID: input.ID}, nil } -func (r *Resolver) MoveCookiePatternToCategoryTool(ctx context.Context, req *mcp.CallToolRequest, input *types.MoveCookiePatternToCategoryInput) (*mcp.CallToolResult, types.MoveCookiePatternToCategoryOutput, error) { - r.MustAuthorize(ctx, input.CookiePatternID, probo.ActionCookiePatternUpdate) - scope := coredata.NewScopeFromObjectID(input.CookiePatternID) - result, err := r.cookieBanner.MoveCookiePatternToCategory(ctx, scope, cookiebanner.MoveCookiePatternToCategoryRequest{ - CookiePatternID: input.CookiePatternID, +func (r *Resolver) MoveTrackerPatternToCategoryTool(ctx context.Context, req *mcp.CallToolRequest, input *types.MoveTrackerPatternToCategoryInput) (*mcp.CallToolResult, types.MoveTrackerPatternToCategoryOutput, error) { + r.MustAuthorize(ctx, input.TrackerPatternID, probo.ActionTrackerPatternUpdate) + scope := coredata.NewScopeFromObjectID(input.TrackerPatternID) + result, err := r.cookieBanner.MoveTrackerPatternToCategory(ctx, scope, cookiebanner.MoveTrackerPatternToCategoryRequest{ + TrackerPatternID: input.TrackerPatternID, TargetCookieCategoryID: input.TargetCookieCategoryID, }) if err != nil { - return nil, types.MoveCookiePatternToCategoryOutput{}, fmt.Errorf("cannot move cookie pattern: %w", err) + return nil, types.MoveTrackerPatternToCategoryOutput{}, fmt.Errorf("cannot move tracker pattern: %w", err) } - return nil, types.MoveCookiePatternToCategoryOutput{CookiePattern: types.NewCookiePattern(result.CookiePattern)}, nil + return nil, types.MoveTrackerPatternToCategoryOutput{TrackerPattern: types.NewTrackerPattern(result.TrackerPattern)}, nil } func (r *Resolver) PublishCookieBannerVersionTool(ctx context.Context, req *mcp.CallToolRequest, input *types.PublishCookieBannerVersionInput) (*mcp.CallToolResult, types.PublishCookieBannerVersionOutput, error) { diff --git a/pkg/server/api/mcp/v1/specification.yaml b/pkg/server/api/mcp/v1/specification.yaml index 2119fc85c..edeee02a2 100644 --- a/pkg/server/api/mcp/v1/specification.yaml +++ b/pkg/server/api/mcp/v1/specification.yaml @@ -9132,13 +9132,14 @@ components: format: date-time description: Last update timestamp - CookiePattern: + TrackerPattern: type: object required: - id - organization_id - cookie_banner_id - cookie_category_id + - tracker_type - pattern - match_type - display_name @@ -9150,7 +9151,7 @@ components: properties: id: $ref: "#/components/schemas/GID" - description: Cookie pattern ID + description: Tracker pattern ID organization_id: $ref: "#/components/schemas/GID" description: Organization ID @@ -9160,9 +9161,13 @@ components: cookie_category_id: $ref: "#/components/schemas/GID" description: Cookie category ID + tracker_type: + type: string + enum: [COOKIE, LOCAL_STORAGE, SESSION_STORAGE, PIXEL] + description: Type of tracker pattern: type: string - description: Cookie name pattern + description: Tracker name pattern match_type: type: string enum: [EXACT, PREFIX] @@ -9630,7 +9635,7 @@ components: cookie_category: $ref: "#/components/schemas/CookieCategory" - ListCookiePatternsInput: + ListTrackerPatternsInput: type: object required: - cookie_category_id @@ -9642,19 +9647,19 @@ components: cursor: $ref: "#/components/schemas/CursorKey" - ListCookiePatternsOutput: + ListTrackerPatternsOutput: type: object required: - - cookie_patterns + - tracker_patterns properties: next_cursor: $ref: "#/components/schemas/CursorKey" - cookie_patterns: + tracker_patterns: type: array items: - $ref: "#/components/schemas/CookiePattern" + $ref: "#/components/schemas/TrackerPattern" - GetCookiePatternInput: + GetTrackerPatternInput: type: object required: - id @@ -9662,18 +9667,19 @@ components: id: $ref: "#/components/schemas/GID" - GetCookiePatternOutput: + GetTrackerPatternOutput: type: object required: - - cookie_pattern + - tracker_pattern properties: - cookie_pattern: - $ref: "#/components/schemas/CookiePattern" + tracker_pattern: + $ref: "#/components/schemas/TrackerPattern" - AddCookiePatternInput: + AddTrackerPatternInput: type: object required: - cookie_category_id + - tracker_type - pattern - match_type - display_name @@ -9681,6 +9687,9 @@ components: properties: cookie_category_id: $ref: "#/components/schemas/GID" + tracker_type: + type: string + enum: [COOKIE, LOCAL_STORAGE, SESSION_STORAGE, PIXEL] pattern: type: string match_type: @@ -9693,15 +9702,15 @@ components: description: type: string - AddCookiePatternOutput: + AddTrackerPatternOutput: type: object required: - - cookie_pattern + - tracker_pattern properties: - cookie_pattern: - $ref: "#/components/schemas/CookiePattern" + tracker_pattern: + $ref: "#/components/schemas/TrackerPattern" - UpdateCookiePatternInput: + UpdateTrackerPatternInput: type: object required: - id @@ -9729,15 +9738,15 @@ components: - "null" go.probo.inc/mcpgen/omittable: true - UpdateCookiePatternOutput: + UpdateTrackerPatternOutput: type: object required: - - cookie_pattern + - tracker_pattern properties: - cookie_pattern: - $ref: "#/components/schemas/CookiePattern" + tracker_pattern: + $ref: "#/components/schemas/TrackerPattern" - DeleteCookiePatternInput: + DeleteTrackerPatternInput: type: object required: - id @@ -9745,7 +9754,7 @@ components: id: $ref: "#/components/schemas/GID" - DeleteCookiePatternOutput: + DeleteTrackerPatternOutput: type: object required: - deleted_id @@ -9753,24 +9762,24 @@ components: deleted_id: $ref: "#/components/schemas/GID" - MoveCookiePatternToCategoryInput: + MoveTrackerPatternToCategoryInput: type: object required: - - cookie_pattern_id + - tracker_pattern_id - target_cookie_category_id properties: - cookie_pattern_id: + tracker_pattern_id: $ref: "#/components/schemas/GID" target_cookie_category_id: $ref: "#/components/schemas/GID" - MoveCookiePatternToCategoryOutput: + MoveTrackerPatternToCategoryOutput: type: object required: - - cookie_pattern + - tracker_pattern properties: - cookie_pattern: - $ref: "#/components/schemas/CookiePattern" + tracker_pattern: + $ref: "#/components/schemas/TrackerPattern" PublishCookieBannerVersionInput: type: object @@ -12019,57 +12028,57 @@ tools: $ref: "#/components/schemas/ReorderCookieCategoryInput" outputSchema: $ref: "#/components/schemas/ReorderCookieCategoryOutput" - - name: listCookiePatterns - description: List all cookie patterns for a category + - name: listTrackerPatterns + description: List all tracker patterns for a category hints: readonly: true idempotent: true inputSchema: - $ref: "#/components/schemas/ListCookiePatternsInput" + $ref: "#/components/schemas/ListTrackerPatternsInput" outputSchema: - $ref: "#/components/schemas/ListCookiePatternsOutput" - - name: getCookiePattern - description: Get a cookie pattern by ID + $ref: "#/components/schemas/ListTrackerPatternsOutput" + - name: getTrackerPattern + description: Get a tracker pattern by ID hints: readonly: true idempotent: true inputSchema: - $ref: "#/components/schemas/GetCookiePatternInput" + $ref: "#/components/schemas/GetTrackerPatternInput" outputSchema: - $ref: "#/components/schemas/GetCookiePatternOutput" - - name: addCookiePattern - description: Create a new cookie pattern for a category + $ref: "#/components/schemas/GetTrackerPatternOutput" + - name: addTrackerPattern + description: Create a new tracker pattern for a category hints: readonly: false inputSchema: - $ref: "#/components/schemas/AddCookiePatternInput" + $ref: "#/components/schemas/AddTrackerPatternInput" outputSchema: - $ref: "#/components/schemas/AddCookiePatternOutput" - - name: updateCookiePattern - description: Update an existing cookie pattern + $ref: "#/components/schemas/AddTrackerPatternOutput" + - name: updateTrackerPattern + description: Update an existing tracker pattern hints: readonly: false inputSchema: - $ref: "#/components/schemas/UpdateCookiePatternInput" + $ref: "#/components/schemas/UpdateTrackerPatternInput" outputSchema: - $ref: "#/components/schemas/UpdateCookiePatternOutput" - - name: deleteCookiePattern - description: Delete a cookie pattern + $ref: "#/components/schemas/UpdateTrackerPatternOutput" + - name: deleteTrackerPattern + description: Delete a tracker pattern hints: readonly: false destructive: true inputSchema: - $ref: "#/components/schemas/DeleteCookiePatternInput" + $ref: "#/components/schemas/DeleteTrackerPatternInput" outputSchema: - $ref: "#/components/schemas/DeleteCookiePatternOutput" - - name: moveCookiePatternToCategory - description: Move a cookie pattern to a different category + $ref: "#/components/schemas/DeleteTrackerPatternOutput" + - name: moveTrackerPatternToCategory + description: Move a tracker pattern to a different category hints: readonly: false inputSchema: - $ref: "#/components/schemas/MoveCookiePatternToCategoryInput" + $ref: "#/components/schemas/MoveTrackerPatternToCategoryInput" outputSchema: - $ref: "#/components/schemas/MoveCookiePatternToCategoryOutput" + $ref: "#/components/schemas/MoveTrackerPatternToCategoryOutput" - name: publishCookieBannerVersion description: Publish the current draft version of a cookie banner hints: diff --git a/pkg/server/api/mcp/v1/types/cookie_pattern.go b/pkg/server/api/mcp/v1/types/tracker_pattern.go similarity index 68% rename from pkg/server/api/mcp/v1/types/cookie_pattern.go rename to pkg/server/api/mcp/v1/types/tracker_pattern.go index 9fb4d8934..a7baf90f3 100644 --- a/pkg/server/api/mcp/v1/types/cookie_pattern.go +++ b/pkg/server/api/mcp/v1/types/tracker_pattern.go @@ -19,18 +19,24 @@ import ( "go.probo.inc/probo/pkg/page" ) -func NewCookiePattern(p *coredata.CookiePattern) *CookiePattern { - return &CookiePattern{ +func NewTrackerPattern(p *coredata.TrackerPattern) *TrackerPattern { + var source TrackerPatternSource + if p.Source != nil { + source = TrackerPatternSource(*p.Source) + } + + return &TrackerPattern{ ID: p.ID, OrganizationID: p.OrganizationID, CookieBannerID: p.CookieBannerID, CookieCategoryID: p.CookieCategoryID, + TrackerType: TrackerPatternTrackerType(p.TrackerType), Pattern: p.Pattern, - MatchType: CookiePatternMatchType(p.MatchType), + MatchType: TrackerPatternMatchType(p.MatchType), DisplayName: p.DisplayName, MaxAgeSeconds: p.MaxAgeSeconds, Description: p.Description, - Source: CookiePatternSource(p.Source), + Source: source, Excluded: p.Excluded, LastMatchedAt: p.LastMatchedAt, CreatedAt: p.CreatedAt, @@ -38,10 +44,10 @@ func NewCookiePattern(p *coredata.CookiePattern) *CookiePattern { } } -func NewListCookiePatternsOutput(pg *page.Page[*coredata.CookiePattern, coredata.CookiePatternOrderField]) ListCookiePatternsOutput { - patterns := make([]*CookiePattern, 0, len(pg.Data)) +func NewListTrackerPatternsOutput(pg *page.Page[*coredata.TrackerPattern, coredata.TrackerPatternOrderField]) ListTrackerPatternsOutput { + patterns := make([]*TrackerPattern, 0, len(pg.Data)) for _, p := range pg.Data { - patterns = append(patterns, NewCookiePattern(p)) + patterns = append(patterns, NewTrackerPattern(p)) } var nextCursor *page.CursorKey @@ -50,8 +56,8 @@ func NewListCookiePatternsOutput(pg *page.Page[*coredata.CookiePattern, coredata nextCursor = &cursorKey } - return ListCookiePatternsOutput{ - NextCursor: nextCursor, - CookiePatterns: patterns, + return ListTrackerPatternsOutput{ + NextCursor: nextCursor, + TrackerPatterns: patterns, } }