Migrate detection page to TrackerPattern GraphQL types
- Add TrackerPattern type, connection, and filter to GraphQL schema - Add uncategorisedTrackerPatterns field on CookieBanner - Add updateTrackerPattern, deleteTrackerPattern, moveTrackerPatternToCategory mutations - Implement all resolvers backed by tracker_patterns table - Add TrackerPattern service methods (CRUD + list/count) - Add LoadUncategorisedByCookieBannerID on TrackerPatterns coredata - Update detection page to use TrackerPattern fragment, queries, and mutations - CookieCategory resolver uses dataloader (not just struct ID) Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -58,14 +58,14 @@ const detectionFragment = graphql`
|
|||||||
@refetchable(queryName: "CookieBannerDetectionPageRefetchQuery")
|
@refetchable(queryName: "CookieBannerDetectionPageRefetchQuery")
|
||||||
@argumentDefinitions(
|
@argumentDefinitions(
|
||||||
first: { type: "Int", defaultValue: 50 }
|
first: { type: "Int", defaultValue: 50 }
|
||||||
order: { type: "CookiePatternOrder", defaultValue: { field: NAME, direction: ASC } }
|
order: { type: "TrackerPatternOrder", defaultValue: { field: NAME, direction: ASC } }
|
||||||
after: { type: "CursorKey", defaultValue: null }
|
after: { type: "CursorKey", defaultValue: null }
|
||||||
before: { type: "CursorKey", defaultValue: null }
|
before: { type: "CursorKey", defaultValue: null }
|
||||||
last: { type: "Int", defaultValue: null }
|
last: { type: "Int", defaultValue: null }
|
||||||
query: { type: "String", defaultValue: null }
|
query: { type: "String", defaultValue: null }
|
||||||
source: { type: "CookieSource", defaultValue: null }
|
source: { type: "CookieSource", defaultValue: null }
|
||||||
) {
|
) {
|
||||||
uncategorisedPatterns(
|
uncategorisedTrackerPatterns(
|
||||||
first: $first
|
first: $first
|
||||||
after: $after
|
after: $after
|
||||||
last: $last
|
last: $last
|
||||||
@@ -74,7 +74,7 @@ const detectionFragment = graphql`
|
|||||||
filter: { query: $query, source: $source }
|
filter: { query: $query, source: $source }
|
||||||
)
|
)
|
||||||
@connection(
|
@connection(
|
||||||
key: "CookieBannerDetectionPage_uncategorisedPatterns"
|
key: "CookieBannerDetectionPage_uncategorisedTrackerPatterns"
|
||||||
filters: ["filter", "orderBy"]
|
filters: ["filter", "orderBy"]
|
||||||
)
|
)
|
||||||
@required(action: THROW) {
|
@required(action: THROW) {
|
||||||
@@ -112,8 +112,8 @@ export default function CookieBannerDetectionPage({
|
|||||||
CookieBannerDetectionPageFragment$key
|
CookieBannerDetectionPageFragment$key
|
||||||
>(detectionFragment, data.node);
|
>(detectionFragment, data.node);
|
||||||
|
|
||||||
const connectionId = fragmentData.uncategorisedPatterns.__id;
|
const connectionId = fragmentData.uncategorisedTrackerPatterns.__id;
|
||||||
const patterns = fragmentData.uncategorisedPatterns.edges.map(edge => edge.node) ?? [];
|
const patterns = fragmentData.uncategorisedTrackerPatterns.edges.map(edge => edge.node) ?? [];
|
||||||
|
|
||||||
const refetchFilters = (overrides: Record<string, unknown> = {}) => {
|
const refetchFilters = (overrides: Record<string, unknown> = {}) => {
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ import {
|
|||||||
} from "./MoveToCategoryDropdown";
|
} from "./MoveToCategoryDropdown";
|
||||||
|
|
||||||
const detectionPatternFragment = graphql`
|
const detectionPatternFragment = graphql`
|
||||||
fragment DetectionPatternRowFragment on CookiePattern {
|
fragment DetectionPatternRowFragment on TrackerPattern {
|
||||||
id
|
id
|
||||||
trackerType
|
trackerType
|
||||||
displayName
|
displayName
|
||||||
@@ -59,11 +59,11 @@ const detectionPatternFragment = graphql`
|
|||||||
|
|
||||||
const deletePatternMutation = graphql`
|
const deletePatternMutation = graphql`
|
||||||
mutation DetectionPatternRowDeleteMutation(
|
mutation DetectionPatternRowDeleteMutation(
|
||||||
$input: DeleteCookiePatternInput!
|
$input: DeleteTrackerPatternInput!
|
||||||
$connections: [ID!]!
|
$connections: [ID!]!
|
||||||
) {
|
) {
|
||||||
deleteCookiePattern(input: $input) {
|
deleteTrackerPattern(input: $input) {
|
||||||
deletedCookiePatternId @deleteEdge(connections: $connections)
|
deletedTrackerPatternId @deleteEdge(connections: $connections)
|
||||||
cookieBanner {
|
cookieBanner {
|
||||||
id
|
id
|
||||||
latestVersion {
|
latestVersion {
|
||||||
@@ -78,10 +78,10 @@ const deletePatternMutation = graphql`
|
|||||||
|
|
||||||
const movePatternMutation = graphql`
|
const movePatternMutation = graphql`
|
||||||
mutation DetectionPatternRowMoveMutation(
|
mutation DetectionPatternRowMoveMutation(
|
||||||
$input: MoveCookiePatternToCategoryInput!
|
$input: MoveTrackerPatternToCategoryInput!
|
||||||
) {
|
) {
|
||||||
moveCookiePatternToCategory(input: $input) {
|
moveTrackerPatternToCategory(input: $input) {
|
||||||
cookiePattern {
|
trackerPattern {
|
||||||
id
|
id
|
||||||
cookieCategory {
|
cookieCategory {
|
||||||
id
|
id
|
||||||
@@ -101,10 +101,10 @@ const movePatternMutation = graphql`
|
|||||||
|
|
||||||
const updatePatternMutation = graphql`
|
const updatePatternMutation = graphql`
|
||||||
mutation DetectionPatternRowUpdateMutation(
|
mutation DetectionPatternRowUpdateMutation(
|
||||||
$input: UpdateCookiePatternInput!
|
$input: UpdateTrackerPatternInput!
|
||||||
) {
|
) {
|
||||||
updateCookiePattern(input: $input) {
|
updateTrackerPattern(input: $input) {
|
||||||
cookiePattern {
|
trackerPattern {
|
||||||
id
|
id
|
||||||
displayName
|
displayName
|
||||||
maxAgeSeconds
|
maxAgeSeconds
|
||||||
@@ -174,7 +174,7 @@ export function DetectionPatternRow({ patternKey, connectionId }: DetectionPatte
|
|||||||
new Promise<void>((resolve) => {
|
new Promise<void>((resolve) => {
|
||||||
deletePattern({
|
deletePattern({
|
||||||
variables: {
|
variables: {
|
||||||
input: { cookiePatternId: pattern.id },
|
input: { trackerPatternId: pattern.id },
|
||||||
connections: [connectionId],
|
connections: [connectionId],
|
||||||
},
|
},
|
||||||
onCompleted(_, errors) {
|
onCompleted(_, errors) {
|
||||||
@@ -203,13 +203,13 @@ export function DetectionPatternRow({ patternKey, connectionId }: DetectionPatte
|
|||||||
movePattern({
|
movePattern({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
cookiePatternId: pattern.id,
|
trackerPatternId: pattern.id,
|
||||||
targetCookieCategoryId: targetCategoryId,
|
targetCookieCategoryId: targetCategoryId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
updater(store) {
|
updater(store) {
|
||||||
const payload = store.getRootField("moveCookiePatternToCategory");
|
const payload = store.getRootField("moveTrackerPatternToCategory");
|
||||||
if (!payload?.getLinkedRecord("cookiePattern")) {
|
if (!payload?.getLinkedRecord("trackerPattern")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -235,7 +235,7 @@ export function DetectionPatternRow({ patternKey, connectionId }: DetectionPatte
|
|||||||
updatePattern({
|
updatePattern({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
cookiePatternId: pattern.id,
|
trackerPatternId: pattern.id,
|
||||||
excluded: !pattern.excluded,
|
excluded: !pattern.excluded,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -255,7 +255,7 @@ export function DetectionPatternRow({ patternKey, connectionId }: DetectionPatte
|
|||||||
updatePattern({
|
updatePattern({
|
||||||
variables: {
|
variables: {
|
||||||
input: {
|
input: {
|
||||||
cookiePatternId: pattern.id,
|
trackerPatternId: pattern.id,
|
||||||
displayName: data.displayName,
|
displayName: data.displayName,
|
||||||
description: data.description,
|
description: data.description,
|
||||||
maxAgeSeconds: data.maxAgeSeconds,
|
maxAgeSeconds: data.maxAgeSeconds,
|
||||||
|
|||||||
@@ -159,6 +159,24 @@ type (
|
|||||||
Resources []DetectedResourceItem
|
Resources []DetectedResourceItem
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateTrackerPatternRequest struct {
|
||||||
|
TrackerPatternID gid.GID
|
||||||
|
DisplayName *string
|
||||||
|
MaxAgeSeconds **int
|
||||||
|
Description *string
|
||||||
|
Excluded *bool
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveTrackerPatternToCategoryRequest struct {
|
||||||
|
TrackerPatternID gid.GID
|
||||||
|
TargetCookieCategoryID gid.GID
|
||||||
|
}
|
||||||
|
|
||||||
|
MoveTrackerPatternToCategoryResult struct {
|
||||||
|
TrackerPattern *coredata.TrackerPattern
|
||||||
|
Banner *coredata.CookieBanner
|
||||||
|
}
|
||||||
|
|
||||||
BannerConfig struct {
|
BannerConfig struct {
|
||||||
BannerID gid.GID `json:"banner_id"`
|
BannerID gid.GID `json:"banner_id"`
|
||||||
Version int `json:"version"`
|
Version int `json:"version"`
|
||||||
@@ -2539,3 +2557,276 @@ func (s *Service) reportDetectedTracker(
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) GetTrackerPattern(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
trackerPatternID gid.GID,
|
||||||
|
) (*coredata.TrackerPattern, error) {
|
||||||
|
var pattern coredata.TrackerPattern
|
||||||
|
|
||||||
|
err := s.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
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 fmt.Errorf("cannot load tracker pattern: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pattern, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) UpdateTrackerPattern(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
req UpdateTrackerPatternRequest,
|
||||||
|
) (*coredata.TrackerPattern, error) {
|
||||||
|
var pattern coredata.TrackerPattern
|
||||||
|
|
||||||
|
err := s.pg.WithTx(
|
||||||
|
ctx,
|
||||||
|
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 fmt.Errorf("cannot load tracker 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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 tracker pattern: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !staysExcluded && 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) DeleteTrackerPattern(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
trackerPatternID gid.GID,
|
||||||
|
) error {
|
||||||
|
return s.pg.WithTx(
|
||||||
|
ctx,
|
||||||
|
func(ctx context.Context, tx pg.Tx) error {
|
||||||
|
var pattern coredata.TrackerPattern
|
||||||
|
if err := pattern.LoadByID(ctx, tx, scope, trackerPatternID); err != nil {
|
||||||
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||||
|
return ErrCookiePatternNotFound
|
||||||
|
}
|
||||||
|
return fmt.Errorf("cannot load tracker pattern: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wasExcluded := pattern.Excluded
|
||||||
|
|
||||||
|
if err := pattern.Delete(ctx, tx, scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot delete tracker pattern: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !wasExcluded && 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
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) MoveTrackerPatternToCategory(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
req MoveTrackerPatternToCategoryRequest,
|
||||||
|
) (*MoveTrackerPatternToCategoryResult, error) {
|
||||||
|
var result MoveTrackerPatternToCategoryResult
|
||||||
|
|
||||||
|
err := s.pg.WithTx(
|
||||||
|
ctx,
|
||||||
|
func(ctx context.Context, tx pg.Tx) error {
|
||||||
|
var pattern coredata.TrackerPattern
|
||||||
|
if err := pattern.LoadByID(ctx, tx, scope, req.TrackerPatternID); err != nil {
|
||||||
|
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||||
|
return ErrCookiePatternNotFound
|
||||||
|
}
|
||||||
|
return fmt.Errorf("cannot load tracker 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 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 && pattern.TrackerType == coredata.TrackerTypeCookie {
|
||||||
|
if _, err := s.ensureDraftVersionForBanner(ctx, tx, scope, pattern.CookieBannerID); err != nil {
|
||||||
|
return fmt.Errorf("cannot ensure draft version: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
result.TrackerPattern = &pattern
|
||||||
|
result.Banner = &banner
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) ListUncategorisedTrackerPatterns(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
bannerID gid.GID,
|
||||||
|
cursor *page.Cursor[coredata.CookiePatternOrderField],
|
||||||
|
filter *coredata.CookiePatternFilter,
|
||||||
|
) (coredata.TrackerPatterns, error) {
|
||||||
|
var patterns coredata.TrackerPatterns
|
||||||
|
|
||||||
|
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 tracker patterns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return patterns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CountUncategorisedTrackerPatterns(
|
||||||
|
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.TrackerPatterns
|
||||||
|
var err error
|
||||||
|
|
||||||
|
count, err = patterns.CountUncategorisedByCookieBannerID(ctx, conn, scope, bannerID, filter)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot count uncategorised tracker patterns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Service) CountDetectedTrackersByPatternID(
|
||||||
|
ctx context.Context,
|
||||||
|
scope coredata.Scoper,
|
||||||
|
trackerPatternID gid.GID,
|
||||||
|
) (int, error) {
|
||||||
|
var count int
|
||||||
|
|
||||||
|
err := s.pg.WithConn(
|
||||||
|
ctx,
|
||||||
|
func(ctx context.Context, conn pg.Querier) error {
|
||||||
|
var trackers coredata.DetectedTrackers
|
||||||
|
var err error
|
||||||
|
|
||||||
|
count, err = trackers.CountByTrackerPatternID(ctx, conn, scope, trackerPatternID)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot count detected trackers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import (
|
|||||||
"github.com/jackc/pgx/v5/pgconn"
|
"github.com/jackc/pgx/v5/pgconn"
|
||||||
"go.gearno.de/kit/pg"
|
"go.gearno.de/kit/pg"
|
||||||
"go.probo.inc/probo/pkg/gid"
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
"go.probo.inc/probo/pkg/page"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
@@ -49,6 +50,29 @@ type (
|
|||||||
TrackerPatterns []*TrackerPattern
|
TrackerPatterns []*TrackerPattern
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (tp *TrackerPattern) CursorKey(field CookiePatternOrderField) page.CursorKey {
|
||||||
|
switch field {
|
||||||
|
case CookiePatternOrderFieldCreatedAt:
|
||||||
|
return page.NewCursorKey(tp.ID, tp.CreatedAt)
|
||||||
|
case CookiePatternOrderFieldName:
|
||||||
|
return page.NewCursorKey(tp.ID, tp.DisplayName)
|
||||||
|
case CookiePatternOrderFieldLastMatchedAt:
|
||||||
|
if tp.LastMatchedAt == nil {
|
||||||
|
return page.NewCursorKey(tp.ID, time.Time{})
|
||||||
|
}
|
||||||
|
return page.NewCursorKey(tp.ID, *tp.LastMatchedAt)
|
||||||
|
case CookiePatternOrderFieldUpdatedAt:
|
||||||
|
return page.NewCursorKey(tp.ID, tp.UpdatedAt)
|
||||||
|
case CookiePatternOrderFieldSource:
|
||||||
|
if tp.Source == nil {
|
||||||
|
return page.NewCursorKey(tp.ID, "")
|
||||||
|
}
|
||||||
|
return page.NewCursorKey(tp.ID, string(*tp.Source))
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(fmt.Sprintf("unsupported order by: %s", field))
|
||||||
|
}
|
||||||
|
|
||||||
func (tp *TrackerPattern) AuthorizationAttributes(ctx context.Context, conn pg.Querier) (map[string]string, error) {
|
func (tp *TrackerPattern) AuthorizationAttributes(ctx context.Context, conn pg.Querier) (map[string]string, error) {
|
||||||
q := `SELECT organization_id FROM tracker_patterns WHERE id = $1 LIMIT 1;`
|
q := `SELECT organization_id FROM tracker_patterns WHERE id = $1 LIMIT 1;`
|
||||||
|
|
||||||
@@ -575,3 +599,113 @@ WHERE
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tps *TrackerPatterns) 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,
|
||||||
|
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_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 tracker patterns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
patterns, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[TrackerPattern])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("cannot collect uncategorised tracker patterns: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
*tps = patterns
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tps *TrackerPatterns) CountUncategorisedByCookieBannerID(
|
||||||
|
ctx context.Context,
|
||||||
|
conn pg.Querier,
|
||||||
|
scope Scoper,
|
||||||
|
cookieBannerID gid.GID,
|
||||||
|
filter *CookiePatternFilter,
|
||||||
|
) (int, error) {
|
||||||
|
q := `
|
||||||
|
SELECT
|
||||||
|
COUNT(id)
|
||||||
|
FROM
|
||||||
|
tracker_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
|
||||||
|
}
|
||||||
|
|||||||
@@ -213,6 +213,42 @@ func (r *cookieBannerResolver) UncategorisedPatterns(ctx context.Context, obj *t
|
|||||||
return types.NewCookiePatternConnectionWithFilter(p, r, obj.ID, coredataFilter), nil
|
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 {
|
||||||
|
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.ListUncategorisedTrackerPatterns(ctx, scope, obj.ID, cursor, coredataFilter)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot list uncategorised tracker patterns", log.Error(err))
|
||||||
|
return nil, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
p := page.NewPage(patterns, cursor)
|
||||||
|
|
||||||
|
return types.NewTrackerPatternConnectionWithFilter(p, r, obj.ID, filter), nil
|
||||||
|
}
|
||||||
|
|
||||||
// Permission is the resolver for the permission field.
|
// Permission is the resolver for the permission field.
|
||||||
func (r *cookieBannerResolver) Permission(ctx context.Context, obj *types.CookieBanner, action string) (bool, error) {
|
func (r *cookieBannerResolver) Permission(ctx context.Context, obj *types.CookieBanner, action string) (bool, error) {
|
||||||
return r.Resolver.Permission(ctx, obj, action)
|
return r.Resolver.Permission(ctx, obj, action)
|
||||||
@@ -997,6 +1033,182 @@ func (r *mutationResolver) UpsertCookieBannerTranslation(ctx context.Context, in
|
|||||||
}, nil
|
}, 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 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.TrackerPatternID)
|
||||||
|
|
||||||
|
pattern, err := r.cookieBanner.UpdateTrackerPattern(
|
||||||
|
ctx,
|
||||||
|
scope,
|
||||||
|
cookiebanner.UpdateTrackerPatternRequest{
|
||||||
|
TrackerPatternID: input.TrackerPatternID,
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot update 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.UpdateTrackerPatternPayload{
|
||||||
|
TrackerPattern: types.NewTrackerPatternNode(pattern),
|
||||||
|
CookieBanner: types.NewCookieBanner(banner),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.TrackerPatternID)
|
||||||
|
|
||||||
|
pattern, err := r.cookieBanner.GetTrackerPattern(ctx, scope, input.TrackerPatternID)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, cookiebanner.ErrCookiePatternNotFound) {
|
||||||
|
return nil, gqlutils.NotFound(ctx, err)
|
||||||
|
}
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot get tracker pattern", log.Error(err))
|
||||||
|
return nil, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
bannerID := pattern.CookieBannerID
|
||||||
|
|
||||||
|
if err := r.cookieBanner.DeleteTrackerPattern(ctx, scope, input.TrackerPatternID); err != nil {
|
||||||
|
if errors.Is(err, cookiebanner.ErrCookiePatternNotFound) {
|
||||||
|
return nil, gqlutils.NotFound(ctx, err)
|
||||||
|
}
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot delete tracker 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.DeleteTrackerPatternPayload{
|
||||||
|
DeletedTrackerPatternID: input.TrackerPatternID,
|
||||||
|
CookieBanner: types.NewCookieBanner(banner),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.authorize(ctx, input.TargetCookieCategoryID, probo.ActionCookieCategoryUpdate); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
scope := coredata.NewScopeFromObjectID(input.TrackerPatternID)
|
||||||
|
|
||||||
|
result, err := r.cookieBanner.MoveTrackerPatternToCategory(
|
||||||
|
ctx,
|
||||||
|
scope,
|
||||||
|
cookiebanner.MoveTrackerPatternToCategoryRequest{
|
||||||
|
TrackerPatternID: input.TrackerPatternID,
|
||||||
|
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, "tracker pattern or target category not found")
|
||||||
|
default:
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot move tracker pattern to category", log.Error(err))
|
||||||
|
return nil, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &types.MoveTrackerPatternToCategoryPayload{
|
||||||
|
TrackerPattern: types.NewTrackerPatternNode(result.TrackerPattern),
|
||||||
|
CookieBanner: types.NewCookieBanner(result.Banner),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CookieCategory is the resolver for the cookieCategory field.
|
||||||
|
func (r *trackerPatternResolver) CookieCategory(ctx context.Context, obj *types.TrackerPattern) (*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
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetectedCount is the resolver for the detectedCount field.
|
||||||
|
func (r *trackerPatternResolver) DetectedCount(ctx context.Context, obj *types.TrackerPattern) (int, error) {
|
||||||
|
scope := coredata.NewScopeFromObjectID(obj.ID)
|
||||||
|
|
||||||
|
count, err := r.cookieBanner.CountDetectedTrackersByPatternID(ctx, scope, obj.ID)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot count detected trackers", log.Error(err))
|
||||||
|
return 0, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Permission is the resolver for the permission field.
|
||||||
|
func (r *trackerPatternResolver) Permission(ctx context.Context, obj *types.TrackerPattern, action string) (bool, error) {
|
||||||
|
return r.Resolver.Permission(ctx, obj, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalCount is the resolver for the totalCount field.
|
||||||
|
func (r *trackerPatternConnectionResolver) TotalCount(ctx context.Context, obj *types.TrackerPatternConnection) (int, error) {
|
||||||
|
scope := coredata.NewScopeFromObjectID(obj.ParentID)
|
||||||
|
|
||||||
|
filter := coredata.NewCookiePatternFilter(nil, nil, nil)
|
||||||
|
if obj.Filter != nil {
|
||||||
|
filter = filter.WithQuery(obj.Filter.Query).WithSource(obj.Filter.Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := r.cookieBanner.CountUncategorisedTrackerPatterns(ctx, scope, obj.ParentID, filter)
|
||||||
|
if err != nil {
|
||||||
|
r.logger.ErrorCtx(ctx, "cannot count uncategorised tracker patterns", log.Error(err))
|
||||||
|
return 0, gqlutils.Internal(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
// CookieBanner returns schema.CookieBannerResolver implementation.
|
// CookieBanner returns schema.CookieBannerResolver implementation.
|
||||||
func (r *Resolver) CookieBanner() schema.CookieBannerResolver { return &cookieBannerResolver{r} }
|
func (r *Resolver) CookieBanner() schema.CookieBannerResolver { return &cookieBannerResolver{r} }
|
||||||
|
|
||||||
@@ -1026,6 +1238,14 @@ func (r *Resolver) CookiePatternConnection() schema.CookiePatternConnectionResol
|
|||||||
return &cookiePatternConnectionResolver{r}
|
return &cookiePatternConnectionResolver{r}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TrackerPattern returns schema.TrackerPatternResolver implementation.
|
||||||
|
func (r *Resolver) TrackerPattern() schema.TrackerPatternResolver { return &trackerPatternResolver{r} }
|
||||||
|
|
||||||
|
// TrackerPatternConnection returns schema.TrackerPatternConnectionResolver implementation.
|
||||||
|
func (r *Resolver) TrackerPatternConnection() schema.TrackerPatternConnectionResolver {
|
||||||
|
return &trackerPatternConnectionResolver{r}
|
||||||
|
}
|
||||||
|
|
||||||
type cookieBannerResolver struct{ *Resolver }
|
type cookieBannerResolver struct{ *Resolver }
|
||||||
type cookieBannerConnectionResolver struct{ *Resolver }
|
type cookieBannerConnectionResolver struct{ *Resolver }
|
||||||
type cookieBannerVersionResolver struct{ *Resolver }
|
type cookieBannerVersionResolver struct{ *Resolver }
|
||||||
@@ -1033,3 +1253,5 @@ type cookieCategoryResolver struct{ *Resolver }
|
|||||||
type cookieCategoryConnectionResolver struct{ *Resolver }
|
type cookieCategoryConnectionResolver struct{ *Resolver }
|
||||||
type cookiePatternResolver struct{ *Resolver }
|
type cookiePatternResolver struct{ *Resolver }
|
||||||
type cookiePatternConnectionResolver struct{ *Resolver }
|
type cookiePatternConnectionResolver struct{ *Resolver }
|
||||||
|
type trackerPatternResolver struct{ *Resolver }
|
||||||
|
type trackerPatternConnectionResolver struct{ *Resolver }
|
||||||
|
|||||||
@@ -160,6 +160,15 @@ type CookieBanner implements Node {
|
|||||||
filter: CookiePatternFilter
|
filter: CookiePatternFilter
|
||||||
): CookiePatternConnection @goField(forceResolver: true)
|
): CookiePatternConnection @goField(forceResolver: true)
|
||||||
|
|
||||||
|
uncategorisedTrackerPatterns(
|
||||||
|
first: Int
|
||||||
|
after: CursorKey
|
||||||
|
last: Int
|
||||||
|
before: CursorKey
|
||||||
|
orderBy: TrackerPatternOrder
|
||||||
|
filter: TrackerPatternFilter
|
||||||
|
): TrackerPatternConnection @goField(forceResolver: true)
|
||||||
|
|
||||||
createdAt: Datetime!
|
createdAt: Datetime!
|
||||||
updatedAt: Datetime!
|
updatedAt: Datetime!
|
||||||
|
|
||||||
@@ -285,6 +294,56 @@ type CookiePatternEdge {
|
|||||||
node: CookiePattern!
|
node: CookiePattern!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type TrackerPattern 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!
|
||||||
|
detectedCount: Int! @goField(forceResolver: true)
|
||||||
|
lastMatchedAt: Datetime
|
||||||
|
createdAt: Datetime!
|
||||||
|
updatedAt: Datetime!
|
||||||
|
|
||||||
|
permission(action: String!): Boolean! @goField(forceResolver: true)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrackerPatternConnection
|
||||||
|
@goModel(
|
||||||
|
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrackerPatternConnection"
|
||||||
|
) {
|
||||||
|
totalCount: Int! @goField(forceResolver: true)
|
||||||
|
edges: [TrackerPatternEdge!]!
|
||||||
|
pageInfo: PageInfo!
|
||||||
|
}
|
||||||
|
|
||||||
|
type TrackerPatternEdge {
|
||||||
|
cursor: CursorKey!
|
||||||
|
node: TrackerPattern!
|
||||||
|
}
|
||||||
|
|
||||||
|
input TrackerPatternOrder
|
||||||
|
@goModel(
|
||||||
|
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrackerPatternOrderBy"
|
||||||
|
) {
|
||||||
|
direction: OrderDirection!
|
||||||
|
field: CookiePatternOrderField!
|
||||||
|
}
|
||||||
|
|
||||||
|
input TrackerPatternFilter
|
||||||
|
@goModel(
|
||||||
|
model: "go.probo.inc/probo/pkg/server/api/console/v1/types.TrackerPatternFilter"
|
||||||
|
) {
|
||||||
|
query: String
|
||||||
|
source: CookieSource
|
||||||
|
trackerType: TrackerType
|
||||||
|
}
|
||||||
|
|
||||||
type CookieBannerVersion implements Node {
|
type CookieBannerVersion implements Node {
|
||||||
id: ID!
|
id: ID!
|
||||||
version: Int!
|
version: Int!
|
||||||
@@ -382,6 +441,15 @@ extend type Mutation {
|
|||||||
upsertCookieBannerTranslation(
|
upsertCookieBannerTranslation(
|
||||||
input: UpsertCookieBannerTranslationInput!
|
input: UpsertCookieBannerTranslationInput!
|
||||||
): UpsertCookieBannerTranslationPayload!
|
): UpsertCookieBannerTranslationPayload!
|
||||||
|
updateTrackerPattern(
|
||||||
|
input: UpdateTrackerPatternInput!
|
||||||
|
): UpdateTrackerPatternPayload!
|
||||||
|
deleteTrackerPattern(
|
||||||
|
input: DeleteTrackerPatternInput!
|
||||||
|
): DeleteTrackerPatternPayload!
|
||||||
|
moveTrackerPatternToCategory(
|
||||||
|
input: MoveTrackerPatternToCategoryInput!
|
||||||
|
): MoveTrackerPatternToCategoryPayload!
|
||||||
}
|
}
|
||||||
|
|
||||||
input CreateCookieBannerInput {
|
input CreateCookieBannerInput {
|
||||||
@@ -547,3 +615,35 @@ type UpsertCookieBannerTranslationPayload {
|
|||||||
cookieBannerTranslation: CookieBannerTranslation!
|
cookieBannerTranslation: CookieBannerTranslation!
|
||||||
cookieBanner: CookieBanner!
|
cookieBanner: CookieBanner!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
input UpdateTrackerPatternInput {
|
||||||
|
trackerPatternId: ID!
|
||||||
|
displayName: String
|
||||||
|
maxAgeSeconds: Int @goField(omittable: true)
|
||||||
|
description: String
|
||||||
|
excluded: Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
input DeleteTrackerPatternInput {
|
||||||
|
trackerPatternId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
input MoveTrackerPatternToCategoryInput {
|
||||||
|
trackerPatternId: ID!
|
||||||
|
targetCookieCategoryId: ID!
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateTrackerPatternPayload {
|
||||||
|
trackerPattern: TrackerPattern!
|
||||||
|
cookieBanner: CookieBanner!
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteTrackerPatternPayload {
|
||||||
|
deletedTrackerPatternId: ID!
|
||||||
|
cookieBanner: CookieBanner!
|
||||||
|
}
|
||||||
|
|
||||||
|
type MoveTrackerPatternToCategoryPayload {
|
||||||
|
trackerPattern: TrackerPattern!
|
||||||
|
cookieBanner: CookieBanner!
|
||||||
|
}
|
||||||
|
|||||||
102
pkg/server/api/console/v1/types/tracker_pattern.go
Normal file
102
pkg/server/api/console/v1/types/tracker_pattern.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
// Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||||
|
//
|
||||||
|
// Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
// purpose with or without fee is hereby granted, provided that the above
|
||||||
|
// copyright notice and this permission notice appear in all copies.
|
||||||
|
//
|
||||||
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
// PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go.probo.inc/probo/pkg/coredata"
|
||||||
|
"go.probo.inc/probo/pkg/gid"
|
||||||
|
"go.probo.inc/probo/pkg/page"
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
TrackerPatternOrderBy OrderBy[coredata.CookiePatternOrderField]
|
||||||
|
|
||||||
|
TrackerPatternConnection struct {
|
||||||
|
TotalCount int
|
||||||
|
Edges []*TrackerPatternEdge
|
||||||
|
PageInfo PageInfo
|
||||||
|
|
||||||
|
Resolver any
|
||||||
|
ParentID gid.GID
|
||||||
|
Filter *TrackerPatternFilter
|
||||||
|
}
|
||||||
|
|
||||||
|
TrackerPatternFilter struct {
|
||||||
|
Query *string
|
||||||
|
Source *coredata.CookieSource
|
||||||
|
TrackerType *coredata.TrackerType
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewTrackerPatternConnection(
|
||||||
|
p *page.Page[*coredata.TrackerPattern, coredata.CookiePatternOrderField],
|
||||||
|
parentType any,
|
||||||
|
parentID gid.GID,
|
||||||
|
) *TrackerPatternConnection {
|
||||||
|
edges := make([]*TrackerPatternEdge, len(p.Data))
|
||||||
|
|
||||||
|
for i := range edges {
|
||||||
|
edges[i] = NewTrackerPatternEdge(p.Data[i], p.Cursor.OrderBy.Field)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &TrackerPatternConnection{
|
||||||
|
Edges: edges,
|
||||||
|
PageInfo: *NewPageInfo(p),
|
||||||
|
|
||||||
|
Resolver: parentType,
|
||||||
|
ParentID: parentID,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrackerPatternConnectionWithFilter(
|
||||||
|
p *page.Page[*coredata.TrackerPattern, coredata.CookiePatternOrderField],
|
||||||
|
parentType any,
|
||||||
|
parentID gid.GID,
|
||||||
|
filter *TrackerPatternFilter,
|
||||||
|
) *TrackerPatternConnection {
|
||||||
|
conn := NewTrackerPatternConnection(p, parentType, parentID)
|
||||||
|
conn.Filter = filter
|
||||||
|
return conn
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrackerPatternEdge(tp *coredata.TrackerPattern, orderBy coredata.CookiePatternOrderField) *TrackerPatternEdge {
|
||||||
|
return &TrackerPatternEdge{
|
||||||
|
Cursor: tp.CursorKey(orderBy),
|
||||||
|
Node: NewTrackerPatternNode(tp),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrackerPatternNode(tp *coredata.TrackerPattern) *TrackerPattern {
|
||||||
|
return &TrackerPattern{
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user