366
pkg/coredata/cookie_banner.go
Normal file
366
pkg/coredata/cookie_banner.go
Normal file
@@ -0,0 +1,366 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
CookieBanner struct {
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
Name string `db:"name"`
|
||||
Origin string `db:"origin"`
|
||||
State CookieBannerState `db:"state"`
|
||||
PrivacyPolicyURL string `db:"privacy_policy_url"`
|
||||
ConsentExpiryDays int `db:"consent_expiry_days"`
|
||||
ConsentMode CookieConsentMode `db:"consent_mode"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
CookieBanners []*CookieBanner
|
||||
)
|
||||
|
||||
func (b *CookieBanner) CursorKey(field CookieBannerOrderField) page.CursorKey {
|
||||
switch field {
|
||||
case CookieBannerOrderFieldCreatedAt:
|
||||
return page.NewCursorKey(b.ID, b.CreatedAt)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unsupported order by: %s", field))
|
||||
}
|
||||
|
||||
func (b *CookieBanner) AuthorizationAttributes(ctx context.Context, conn pg.Querier) (map[string]string, error) {
|
||||
q := `SELECT organization_id FROM cookie_banners WHERE id = $1 LIMIT 1;`
|
||||
|
||||
var organizationID gid.GID
|
||||
if err := conn.QueryRow(ctx, q, b.ID).Scan(&organizationID); err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, ErrResourceNotFound
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("cannot query cookie banner authorization attributes: %w", err)
|
||||
}
|
||||
|
||||
return map[string]string{"organization_id": organizationID.String()}, nil
|
||||
}
|
||||
|
||||
func (b *CookieBanner) LoadByID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
bannerID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
name,
|
||||
origin,
|
||||
state,
|
||||
privacy_policy_url,
|
||||
consent_expiry_days,
|
||||
consent_mode,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
cookie_banners
|
||||
WHERE
|
||||
%s
|
||||
AND id = @banner_id
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"banner_id": bannerID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query cookie banners: %w", err)
|
||||
}
|
||||
|
||||
banner, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CookieBanner])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return ErrResourceNotFound
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot collect cookie banner: %w", err)
|
||||
}
|
||||
|
||||
*b = banner
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *CookieBanner) LoadPublishedByID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
bannerID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
name,
|
||||
origin,
|
||||
state,
|
||||
privacy_policy_url,
|
||||
consent_expiry_days,
|
||||
consent_mode,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
cookie_banners
|
||||
WHERE
|
||||
id = @banner_id
|
||||
AND state = 'PUBLISHED'
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{"banner_id": bannerID}
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query cookie banners: %w", err)
|
||||
}
|
||||
|
||||
banner, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CookieBanner])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return ErrResourceNotFound
|
||||
}
|
||||
|
||||
return fmt.Errorf("cannot collect cookie banner: %w", err)
|
||||
}
|
||||
|
||||
*b = banner
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *CookieBanners) LoadByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
cursor *page.Cursor[CookieBannerOrderField],
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
organization_id,
|
||||
name,
|
||||
origin,
|
||||
state,
|
||||
privacy_policy_url,
|
||||
consent_expiry_days,
|
||||
consent_mode,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
cookie_banners
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
AND %s
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
||||
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 banners: %w", err)
|
||||
}
|
||||
|
||||
banners, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[CookieBanner])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect cookie banners: %w", err)
|
||||
}
|
||||
|
||||
*b = banners
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *CookieBanners) CountByOrganizationID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
organizationID gid.GID,
|
||||
) (int, error) {
|
||||
q := `
|
||||
SELECT
|
||||
COUNT(id)
|
||||
FROM
|
||||
cookie_banners
|
||||
WHERE
|
||||
%s
|
||||
AND organization_id = @organization_id
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"organization_id": organizationID}
|
||||
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 (b *CookieBanner) Insert(
|
||||
ctx context.Context,
|
||||
tx pg.Tx,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
INSERT INTO cookie_banners (
|
||||
id,
|
||||
tenant_id,
|
||||
organization_id,
|
||||
name,
|
||||
origin,
|
||||
state,
|
||||
privacy_policy_url,
|
||||
consent_expiry_days,
|
||||
consent_mode,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
@id,
|
||||
@tenant_id,
|
||||
@organization_id,
|
||||
@name,
|
||||
@origin,
|
||||
@state,
|
||||
@privacy_policy_url,
|
||||
@consent_expiry_days,
|
||||
@consent_mode,
|
||||
@created_at,
|
||||
@updated_at
|
||||
)
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": b.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"organization_id": b.OrganizationID,
|
||||
"name": b.Name,
|
||||
"origin": b.Origin,
|
||||
"state": b.State,
|
||||
"privacy_policy_url": b.PrivacyPolicyURL,
|
||||
"consent_expiry_days": b.ConsentExpiryDays,
|
||||
"consent_mode": b.ConsentMode,
|
||||
"created_at": b.CreatedAt,
|
||||
"updated_at": b.UpdatedAt,
|
||||
}
|
||||
|
||||
_, err := tx.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert cookie banner: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *CookieBanner) Update(
|
||||
ctx context.Context,
|
||||
tx pg.Tx,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE cookie_banners
|
||||
SET
|
||||
name = @name,
|
||||
origin = @origin,
|
||||
state = @state,
|
||||
privacy_policy_url = @privacy_policy_url,
|
||||
consent_expiry_days = @consent_expiry_days,
|
||||
consent_mode = @consent_mode,
|
||||
updated_at = @updated_at
|
||||
WHERE
|
||||
%s
|
||||
AND id = @id
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": b.ID,
|
||||
"name": b.Name,
|
||||
"origin": b.Origin,
|
||||
"state": b.State,
|
||||
"privacy_policy_url": b.PrivacyPolicyURL,
|
||||
"consent_expiry_days": b.ConsentExpiryDays,
|
||||
"consent_mode": b.ConsentMode,
|
||||
"updated_at": b.UpdatedAt,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := tx.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update cookie banner: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *CookieBanner) Delete(
|
||||
ctx context.Context,
|
||||
tx pg.Tx,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
DELETE FROM cookie_banners
|
||||
WHERE
|
||||
%s
|
||||
AND id = @id
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"id": b.ID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := tx.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete cookie banner: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
55
pkg/coredata/cookie_banner_order_field.go
Normal file
55
pkg/coredata/cookie_banner_order_field.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import "fmt"
|
||||
|
||||
type CookieBannerOrderField string
|
||||
|
||||
const (
|
||||
CookieBannerOrderFieldCreatedAt CookieBannerOrderField = "CREATED_AT"
|
||||
)
|
||||
|
||||
func (p CookieBannerOrderField) Column() string {
|
||||
switch p {
|
||||
case CookieBannerOrderFieldCreatedAt:
|
||||
return "created_at"
|
||||
}
|
||||
panic(fmt.Sprintf("unsupported order by: %s", p))
|
||||
}
|
||||
|
||||
func (p CookieBannerOrderField) IsValid() bool {
|
||||
switch p {
|
||||
case CookieBannerOrderFieldCreatedAt:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p CookieBannerOrderField) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
func (p *CookieBannerOrderField) UnmarshalText(text []byte) error {
|
||||
*p = CookieBannerOrderField(text)
|
||||
if !p.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid CookieBannerOrderField", string(text))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p CookieBannerOrderField) MarshalText() ([]byte, error) {
|
||||
return []byte(p.String()), nil
|
||||
}
|
||||
75
pkg/coredata/cookie_banner_state.go
Normal file
75
pkg/coredata/cookie_banner_state.go
Normal file
@@ -0,0 +1,75 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type CookieBannerState string
|
||||
|
||||
const (
|
||||
CookieBannerStateDraft CookieBannerState = "DRAFT"
|
||||
CookieBannerStatePublished CookieBannerState = "PUBLISHED"
|
||||
CookieBannerStateDisabled CookieBannerState = "DISABLED"
|
||||
)
|
||||
|
||||
func CookieBannerStates() []CookieBannerState {
|
||||
return []CookieBannerState{
|
||||
CookieBannerStateDraft,
|
||||
CookieBannerStatePublished,
|
||||
CookieBannerStateDisabled,
|
||||
}
|
||||
}
|
||||
|
||||
func (s CookieBannerState) String() string {
|
||||
return string(s)
|
||||
}
|
||||
|
||||
func (s *CookieBannerState) Scan(value any) error {
|
||||
var v string
|
||||
switch val := value.(type) {
|
||||
case string:
|
||||
v = val
|
||||
case []byte:
|
||||
v = string(val)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type for CookieBannerState: %T", value)
|
||||
}
|
||||
|
||||
switch CookieBannerState(v) {
|
||||
case CookieBannerStateDraft:
|
||||
*s = CookieBannerStateDraft
|
||||
case CookieBannerStatePublished:
|
||||
*s = CookieBannerStatePublished
|
||||
case CookieBannerStateDisabled:
|
||||
*s = CookieBannerStateDisabled
|
||||
default:
|
||||
return fmt.Errorf("invalid CookieBannerState value: %q", v)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s CookieBannerState) Value() (driver.Value, error) {
|
||||
switch s {
|
||||
case CookieBannerStateDraft,
|
||||
CookieBannerStatePublished,
|
||||
CookieBannerStateDisabled:
|
||||
return string(s), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid CookieBannerState: %s", s)
|
||||
}
|
||||
}
|
||||
396
pkg/coredata/cookie_category.go
Normal file
396
pkg/coredata/cookie_category.go
Normal file
@@ -0,0 +1,396 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
CookieItem struct {
|
||||
Name string `json:"name"`
|
||||
Duration string `json:"duration"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
CookieItems []CookieItem
|
||||
|
||||
CookieCategory struct {
|
||||
ID gid.GID `db:"id"`
|
||||
CookieBannerID gid.GID `db:"cookie_banner_id"`
|
||||
Name string `db:"name"`
|
||||
Description string `db:"description"`
|
||||
Required bool `db:"required"`
|
||||
Rank int `db:"rank"`
|
||||
Cookies CookieItems `db:"cookies"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at"`
|
||||
}
|
||||
|
||||
CookieCategories []*CookieCategory
|
||||
)
|
||||
|
||||
func (c CookieItems) MarshalJSON() ([]byte, error) {
|
||||
if c == nil {
|
||||
return []byte("[]"), nil
|
||||
}
|
||||
return json.Marshal([]CookieItem(c))
|
||||
}
|
||||
|
||||
func (c *CookieItems) UnmarshalJSON(data []byte) error {
|
||||
if len(data) == 0 || string(data) == "null" {
|
||||
*c = CookieItems{}
|
||||
return nil
|
||||
}
|
||||
return json.Unmarshal(data, (*[]CookieItem)(c))
|
||||
}
|
||||
|
||||
func (c *CookieCategory) CursorKey(field CookieCategoryOrderField) page.CursorKey {
|
||||
switch field {
|
||||
case CookieCategoryOrderFieldRank:
|
||||
return page.NewCursorKey(c.ID, c.Rank)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unsupported order by: %s", field))
|
||||
}
|
||||
|
||||
func (c *CookieCategory) AuthorizationAttributes(ctx context.Context, conn pg.Querier) (map[string]string, error) {
|
||||
q := `
|
||||
SELECT cb.organization_id
|
||||
FROM cookie_categories cc
|
||||
JOIN cookie_banners cb ON cc.cookie_banner_id = cb.id
|
||||
WHERE cc.id = $1
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
var organizationID gid.GID
|
||||
if err := conn.QueryRow(ctx, q, c.ID).Scan(&organizationID); err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, ErrResourceNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("cannot query cookie category authorization attributes: %w", err)
|
||||
}
|
||||
|
||||
return map[string]string{"organization_id": organizationID.String()}, nil
|
||||
}
|
||||
|
||||
func (c *CookieCategory) LoadByID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
categoryID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
cookie_banner_id,
|
||||
name,
|
||||
description,
|
||||
required,
|
||||
rank,
|
||||
cookies,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
cookie_categories
|
||||
WHERE
|
||||
%s
|
||||
AND id = @category_id
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"category_id": categoryID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query cookie categories: %w", err)
|
||||
}
|
||||
|
||||
category, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CookieCategory])
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return ErrResourceNotFound
|
||||
}
|
||||
return fmt.Errorf("cannot collect cookie category: %w", err)
|
||||
}
|
||||
|
||||
*c = category
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CookieCategories) LoadByCookieBannerID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
cookieBannerID gid.GID,
|
||||
cursor *page.Cursor[CookieCategoryOrderField],
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
cookie_banner_id,
|
||||
name,
|
||||
description,
|
||||
required,
|
||||
rank,
|
||||
cookies,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
cookie_categories
|
||||
WHERE
|
||||
%s
|
||||
AND cookie_banner_id = @cookie_banner_id
|
||||
AND %s
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"cookie_banner_id": cookieBannerID}
|
||||
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 categories: %w", err)
|
||||
}
|
||||
|
||||
categories, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[CookieCategory])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect cookie categories: %w", err)
|
||||
}
|
||||
|
||||
*c = categories
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CookieCategories) CountByCookieBannerID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
cookieBannerID gid.GID,
|
||||
) (int, error) {
|
||||
q := `
|
||||
SELECT
|
||||
COUNT(id)
|
||||
FROM
|
||||
cookie_categories
|
||||
WHERE
|
||||
%s
|
||||
AND cookie_banner_id = @cookie_banner_id
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"cookie_banner_id": cookieBannerID}
|
||||
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 (c *CookieCategories) LoadAllPublicByCookieBannerID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
cookieBannerID gid.GID,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
cookie_banner_id,
|
||||
name,
|
||||
description,
|
||||
required,
|
||||
rank,
|
||||
cookies,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM
|
||||
cookie_categories
|
||||
WHERE
|
||||
cookie_banner_id = @cookie_banner_id
|
||||
ORDER BY
|
||||
rank ASC, id ASC;
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{"cookie_banner_id": cookieBannerID}
|
||||
|
||||
rows, err := conn.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot query cookie categories: %w", err)
|
||||
}
|
||||
|
||||
categories, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[CookieCategory])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect cookie categories: %w", err)
|
||||
}
|
||||
|
||||
*c = categories
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CookieCategory) Insert(
|
||||
ctx context.Context,
|
||||
tx pg.Tx,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
INSERT INTO cookie_categories (
|
||||
id,
|
||||
tenant_id,
|
||||
cookie_banner_id,
|
||||
name,
|
||||
description,
|
||||
required,
|
||||
rank,
|
||||
cookies,
|
||||
created_at,
|
||||
updated_at
|
||||
) VALUES (
|
||||
@id,
|
||||
@tenant_id,
|
||||
@cookie_banner_id,
|
||||
@name,
|
||||
@description,
|
||||
@required,
|
||||
@rank,
|
||||
@cookies,
|
||||
@created_at,
|
||||
@updated_at
|
||||
)
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": c.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"cookie_banner_id": c.CookieBannerID,
|
||||
"name": c.Name,
|
||||
"description": c.Description,
|
||||
"required": c.Required,
|
||||
"rank": c.Rank,
|
||||
"cookies": c.Cookies,
|
||||
"created_at": c.CreatedAt,
|
||||
"updated_at": c.UpdatedAt,
|
||||
}
|
||||
|
||||
_, err := tx.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert cookie category: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CookieCategory) Update(
|
||||
ctx context.Context,
|
||||
tx pg.Tx,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
UPDATE cookie_categories
|
||||
SET
|
||||
name = @name,
|
||||
description = @description,
|
||||
rank = @rank,
|
||||
cookies = @cookies,
|
||||
updated_at = @updated_at
|
||||
WHERE
|
||||
%s
|
||||
AND id = @id
|
||||
RETURNING
|
||||
id,
|
||||
cookie_banner_id,
|
||||
name,
|
||||
description,
|
||||
required,
|
||||
rank,
|
||||
cookies,
|
||||
created_at,
|
||||
updated_at
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": c.ID,
|
||||
"name": c.Name,
|
||||
"description": c.Description,
|
||||
"rank": c.Rank,
|
||||
"cookies": c.Cookies,
|
||||
"updated_at": c.UpdatedAt,
|
||||
}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
rows, err := tx.Query(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot update cookie category: %w", err)
|
||||
}
|
||||
|
||||
category, err := pgx.CollectExactlyOneRow(rows, pgx.RowToStructByName[CookieCategory])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect updated cookie category: %w", err)
|
||||
}
|
||||
|
||||
*c = category
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CookieCategory) Delete(
|
||||
ctx context.Context,
|
||||
tx pg.Tx,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
DELETE FROM cookie_categories
|
||||
WHERE
|
||||
%s
|
||||
AND id = @id
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"id": c.ID}
|
||||
maps.Copy(args, scope.SQLArguments())
|
||||
|
||||
_, err := tx.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot delete cookie category: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
55
pkg/coredata/cookie_category_order_field.go
Normal file
55
pkg/coredata/cookie_category_order_field.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import "fmt"
|
||||
|
||||
type CookieCategoryOrderField string
|
||||
|
||||
const (
|
||||
CookieCategoryOrderFieldRank CookieCategoryOrderField = "RANK"
|
||||
)
|
||||
|
||||
func (p CookieCategoryOrderField) Column() string {
|
||||
switch p {
|
||||
case CookieCategoryOrderFieldRank:
|
||||
return "rank"
|
||||
}
|
||||
panic(fmt.Sprintf("unsupported order by: %s", p))
|
||||
}
|
||||
|
||||
func (p CookieCategoryOrderField) IsValid() bool {
|
||||
switch p {
|
||||
case CookieCategoryOrderFieldRank:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p CookieCategoryOrderField) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
func (p *CookieCategoryOrderField) UnmarshalText(text []byte) error {
|
||||
*p = CookieCategoryOrderField(text)
|
||||
if !p.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid CookieCategoryOrderField", string(text))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p CookieCategoryOrderField) MarshalText() ([]byte, error) {
|
||||
return []byte(p.String()), nil
|
||||
}
|
||||
82
pkg/coredata/cookie_consent_action.go
Normal file
82
pkg/coredata/cookie_consent_action.go
Normal file
@@ -0,0 +1,82 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type CookieConsentAction string
|
||||
|
||||
const (
|
||||
CookieConsentActionAcceptAll CookieConsentAction = "ACCEPT_ALL"
|
||||
CookieConsentActionRejectAll CookieConsentAction = "REJECT_ALL"
|
||||
CookieConsentActionCustomize CookieConsentAction = "CUSTOMIZE"
|
||||
// Global Privacy Control
|
||||
CookieConsentActionGPC CookieConsentAction = "GPC"
|
||||
)
|
||||
|
||||
func CookieConsentActions() []CookieConsentAction {
|
||||
return []CookieConsentAction{
|
||||
CookieConsentActionAcceptAll,
|
||||
CookieConsentActionRejectAll,
|
||||
CookieConsentActionCustomize,
|
||||
CookieConsentActionGPC,
|
||||
}
|
||||
}
|
||||
|
||||
func (a CookieConsentAction) String() string {
|
||||
return string(a)
|
||||
}
|
||||
|
||||
func (a *CookieConsentAction) Scan(value any) error {
|
||||
var v string
|
||||
switch val := value.(type) {
|
||||
case string:
|
||||
v = val
|
||||
case []byte:
|
||||
v = string(val)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type for CookieConsentAction: %T", value)
|
||||
}
|
||||
|
||||
switch CookieConsentAction(v) {
|
||||
case CookieConsentActionAcceptAll:
|
||||
*a = CookieConsentActionAcceptAll
|
||||
case CookieConsentActionRejectAll:
|
||||
*a = CookieConsentActionRejectAll
|
||||
case CookieConsentActionCustomize:
|
||||
*a = CookieConsentActionCustomize
|
||||
case CookieConsentActionGPC:
|
||||
*a = CookieConsentActionGPC
|
||||
default:
|
||||
return fmt.Errorf("invalid CookieConsentAction value: %q", v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a CookieConsentAction) Value() (driver.Value, error) {
|
||||
switch a {
|
||||
case CookieConsentActionAcceptAll,
|
||||
CookieConsentActionRejectAll,
|
||||
CookieConsentActionCustomize,
|
||||
CookieConsentActionGPC:
|
||||
return string(a), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid CookieConsentAction: %s", a)
|
||||
}
|
||||
}
|
||||
71
pkg/coredata/cookie_consent_mode.go
Normal file
71
pkg/coredata/cookie_consent_mode.go
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"database/sql/driver"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type CookieConsentMode string
|
||||
|
||||
const (
|
||||
CookieConsentModeOptIn CookieConsentMode = "OPT_IN"
|
||||
CookieConsentModeOptOut CookieConsentMode = "OPT_OUT"
|
||||
)
|
||||
|
||||
func CookieConsentModes() []CookieConsentMode {
|
||||
return []CookieConsentMode{
|
||||
CookieConsentModeOptIn,
|
||||
CookieConsentModeOptOut,
|
||||
}
|
||||
}
|
||||
|
||||
func (m CookieConsentMode) String() string {
|
||||
return string(m)
|
||||
}
|
||||
|
||||
func (m *CookieConsentMode) Scan(value any) error {
|
||||
var v string
|
||||
switch val := value.(type) {
|
||||
case string:
|
||||
v = val
|
||||
case []byte:
|
||||
v = string(val)
|
||||
default:
|
||||
return fmt.Errorf("unsupported type for CookieConsentMode: %T", value)
|
||||
}
|
||||
|
||||
switch CookieConsentMode(v) {
|
||||
case CookieConsentModeOptIn:
|
||||
*m = CookieConsentModeOptIn
|
||||
case CookieConsentModeOptOut:
|
||||
*m = CookieConsentModeOptOut
|
||||
default:
|
||||
return fmt.Errorf("invalid CookieConsentMode value: %q", v)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m CookieConsentMode) Value() (driver.Value, error) {
|
||||
switch m {
|
||||
case CookieConsentModeOptIn,
|
||||
CookieConsentModeOptOut:
|
||||
return string(m), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("invalid CookieConsentMode: %s", m)
|
||||
}
|
||||
}
|
||||
205
pkg/coredata/cookie_consent_record.go
Normal file
205
pkg/coredata/cookie_consent_record.go
Normal file
@@ -0,0 +1,205 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"go.gearno.de/kit/pg"
|
||||
"go.probo.inc/probo/pkg/gid"
|
||||
"go.probo.inc/probo/pkg/page"
|
||||
)
|
||||
|
||||
type (
|
||||
ConsentRecord struct {
|
||||
ID gid.GID `db:"id"`
|
||||
CookieBannerID gid.GID `db:"cookie_banner_id"`
|
||||
VisitorID string `db:"visitor_id"`
|
||||
IPAddress *string `db:"ip_address"`
|
||||
UserAgent *string `db:"user_agent"`
|
||||
ConsentData json.RawMessage `db:"consent_data"`
|
||||
Action CookieConsentAction `db:"action"`
|
||||
CreatedAt time.Time `db:"created_at"`
|
||||
}
|
||||
|
||||
ConsentRecords []*ConsentRecord
|
||||
)
|
||||
|
||||
func (r *ConsentRecord) CursorKey(field CookieConsentRecordOrderField) page.CursorKey {
|
||||
switch field {
|
||||
case CookieConsentRecordOrderFieldCreatedAt:
|
||||
return page.NewCursorKey(r.ID, r.CreatedAt)
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf("unsupported order by: %s", field))
|
||||
}
|
||||
|
||||
func (r *ConsentRecord) AuthorizationAttributes(ctx context.Context, conn pg.Querier) (map[string]string, error) {
|
||||
q := `
|
||||
SELECT cb.organization_id
|
||||
FROM cookie_consent_records cr
|
||||
JOIN cookie_banners cb ON cr.cookie_banner_id = cb.id
|
||||
WHERE cr.id = $1
|
||||
LIMIT 1;
|
||||
`
|
||||
|
||||
var organizationID gid.GID
|
||||
if err := conn.QueryRow(ctx, q, r.ID).Scan(&organizationID); err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, ErrResourceNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("cannot query consent record authorization attributes: %w", err)
|
||||
}
|
||||
|
||||
return map[string]string{"organization_id": organizationID.String()}, nil
|
||||
}
|
||||
|
||||
func (r *ConsentRecords) LoadByCookieBannerID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
cookieBannerID gid.GID,
|
||||
cursor *page.Cursor[CookieConsentRecordOrderField],
|
||||
filter *CookieConsentRecordFilter,
|
||||
) error {
|
||||
q := `
|
||||
SELECT
|
||||
id,
|
||||
cookie_banner_id,
|
||||
visitor_id,
|
||||
ip_address,
|
||||
user_agent,
|
||||
consent_data,
|
||||
action,
|
||||
created_at
|
||||
FROM
|
||||
cookie_consent_records
|
||||
WHERE
|
||||
%s
|
||||
AND cookie_banner_id = @cookie_banner_id
|
||||
AND %s
|
||||
AND %s
|
||||
`
|
||||
|
||||
q = fmt.Sprintf(q, scope.SQLFragment(), filter.SQLFragment(), cursor.SQLFragment())
|
||||
|
||||
args := pgx.StrictNamedArgs{"cookie_banner_id": cookieBannerID}
|
||||
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 consent records: %w", err)
|
||||
}
|
||||
|
||||
records, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[ConsentRecord])
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot collect consent records: %w", err)
|
||||
}
|
||||
|
||||
*r = records
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *ConsentRecords) CountByCookieBannerID(
|
||||
ctx context.Context,
|
||||
conn pg.Querier,
|
||||
scope Scoper,
|
||||
cookieBannerID gid.GID,
|
||||
filter *CookieConsentRecordFilter,
|
||||
) (int, error) {
|
||||
q := `
|
||||
SELECT
|
||||
COUNT(id)
|
||||
FROM
|
||||
cookie_consent_records
|
||||
WHERE
|
||||
%s
|
||||
AND cookie_banner_id = @cookie_banner_id
|
||||
AND %s
|
||||
`
|
||||
|
||||
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())
|
||||
|
||||
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 (r *ConsentRecord) Insert(
|
||||
ctx context.Context,
|
||||
tx pg.Tx,
|
||||
scope Scoper,
|
||||
) error {
|
||||
q := `
|
||||
INSERT INTO cookie_consent_records (
|
||||
id,
|
||||
tenant_id,
|
||||
cookie_banner_id,
|
||||
visitor_id,
|
||||
ip_address,
|
||||
user_agent,
|
||||
consent_data,
|
||||
action,
|
||||
created_at
|
||||
) VALUES (
|
||||
@id,
|
||||
@tenant_id,
|
||||
@cookie_banner_id,
|
||||
@visitor_id,
|
||||
@ip_address,
|
||||
@user_agent,
|
||||
@consent_data,
|
||||
@action,
|
||||
@created_at
|
||||
)
|
||||
`
|
||||
|
||||
args := pgx.StrictNamedArgs{
|
||||
"id": r.ID,
|
||||
"tenant_id": scope.GetTenantID(),
|
||||
"cookie_banner_id": r.CookieBannerID,
|
||||
"visitor_id": r.VisitorID,
|
||||
"ip_address": r.IPAddress,
|
||||
"user_agent": r.UserAgent,
|
||||
"consent_data": r.ConsentData,
|
||||
"action": r.Action,
|
||||
"created_at": r.CreatedAt,
|
||||
}
|
||||
|
||||
_, err := tx.Exec(ctx, q, args)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot insert consent record: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
50
pkg/coredata/cookie_consent_record_filter.go
Normal file
50
pkg/coredata/cookie_consent_record_filter.go
Normal file
@@ -0,0 +1,50 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import "github.com/jackc/pgx/v5"
|
||||
|
||||
type CookieConsentRecordFilter struct {
|
||||
action *CookieConsentAction
|
||||
}
|
||||
|
||||
func NewCookieConsentRecordFilter(action *CookieConsentAction) *CookieConsentRecordFilter {
|
||||
return &CookieConsentRecordFilter{
|
||||
action: action,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *CookieConsentRecordFilter) SQLFragment() string {
|
||||
return `
|
||||
(
|
||||
CASE
|
||||
WHEN @filter_action::text IS NOT NULL THEN
|
||||
action = @filter_action::consent_action
|
||||
ELSE TRUE
|
||||
END
|
||||
)`
|
||||
}
|
||||
|
||||
func (f *CookieConsentRecordFilter) SQLArguments() pgx.StrictNamedArgs {
|
||||
args := pgx.StrictNamedArgs{
|
||||
"filter_action": nil,
|
||||
}
|
||||
|
||||
if f.action != nil {
|
||||
args["filter_action"] = string(*f.action)
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
55
pkg/coredata/cookie_consent_record_order_field.go
Normal file
55
pkg/coredata/cookie_consent_record_order_field.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2025-2026 Probo Inc <hello@getprobo.com>.
|
||||
//
|
||||
// Permission to use, copy, modify, and/or distribute this software for any
|
||||
// purpose with or without fee is hereby granted, provided that the above
|
||||
// copyright notice and this permission notice appear in all copies.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||
// PERFORMANCE OF THIS SOFTWARE.
|
||||
|
||||
package coredata
|
||||
|
||||
import "fmt"
|
||||
|
||||
type CookieConsentRecordOrderField string
|
||||
|
||||
const (
|
||||
CookieConsentRecordOrderFieldCreatedAt CookieConsentRecordOrderField = "CREATED_AT"
|
||||
)
|
||||
|
||||
func (p CookieConsentRecordOrderField) Column() string {
|
||||
switch p {
|
||||
case CookieConsentRecordOrderFieldCreatedAt:
|
||||
return "created_at"
|
||||
}
|
||||
panic(fmt.Sprintf("unsupported order by: %s", p))
|
||||
}
|
||||
|
||||
func (p CookieConsentRecordOrderField) IsValid() bool {
|
||||
switch p {
|
||||
case CookieConsentRecordOrderFieldCreatedAt:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p CookieConsentRecordOrderField) String() string {
|
||||
return string(p)
|
||||
}
|
||||
|
||||
func (p *CookieConsentRecordOrderField) UnmarshalText(text []byte) error {
|
||||
*p = CookieConsentRecordOrderField(text)
|
||||
if !p.IsValid() {
|
||||
return fmt.Errorf("%s is not a valid CookieConsentRecordOrderField", string(text))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p CookieConsentRecordOrderField) MarshalText() ([]byte, error) {
|
||||
return []byte(p.String()), nil
|
||||
}
|
||||
@@ -98,6 +98,9 @@ const (
|
||||
AccessReviewCampaignEntityType uint16 = 72
|
||||
AccessEntryEntityType uint16 = 73
|
||||
AccessEntryDecisionHistoryEntityType uint16 = 74
|
||||
CookieBannerEntityType uint16 = 75
|
||||
CookieCategoryEntityType uint16 = 76
|
||||
ConsentRecordEntityType uint16 = 77
|
||||
)
|
||||
|
||||
func NewEntityFromID(id gid.GID) (any, bool) {
|
||||
@@ -244,6 +247,12 @@ func NewEntityFromID(id gid.GID) (any, bool) {
|
||||
return &AccessEntry{ID: id}, true
|
||||
case AccessEntryDecisionHistoryEntityType:
|
||||
return &AccessEntryDecisionHistory{ID: id}, true
|
||||
case CookieBannerEntityType:
|
||||
return &CookieBanner{ID: id}, true
|
||||
case CookieCategoryEntityType:
|
||||
return &CookieCategory{ID: id}, true
|
||||
case ConsentRecordEntityType:
|
||||
return &ConsentRecord{ID: id}, true
|
||||
default:
|
||||
return nil, false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user