Generate tracker policy document from banner snapshot
Add a markdown "Cookie and Tracking Technologies Policy" template in the policy-writer tone that covers cookies and other tracking technologies, with GDPR, UK GDPR, FADP, and CCPA/CPRA privacy-rights framing. Convert it to ProseMirror JSON and add PublishTrackerPolicy, which builds the document from a banner's latest published version snapshot and its tracker third parties. The document is GENERATED and private in the trust center, linked to the banner via policy_document_id. Add CookieItem.HumanizedDuration so server-rendered lifetimes match the banner's humanizeDuration helper, and reword the publish trigger and backfill comment to reflect the broader tracker scope. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -20,6 +20,7 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
@@ -39,6 +40,12 @@ type (
|
||||
|
||||
CookieItems []CookieItem
|
||||
|
||||
cookieDurationUnit struct {
|
||||
secs int
|
||||
name string
|
||||
snap int
|
||||
}
|
||||
|
||||
CookieCategory struct {
|
||||
ID gid.GID `db:"id"`
|
||||
OrganizationID gid.GID `db:"organization_id"`
|
||||
@@ -57,6 +64,67 @@ type (
|
||||
CookieCategories []*CookieCategory
|
||||
)
|
||||
|
||||
// cookieDurationUnits mirrors DURATION_UNITS in
|
||||
// packages/cookie-banner/src/cookie-utils.ts (and the snap table used by the
|
||||
// pattern-analysis worker) so durations rendered server-side match exactly what
|
||||
// visitors see in the consent banner. snap is the per-unit buffer: when the
|
||||
// remainder is within snap seconds of the next whole unit, round up instead of
|
||||
// carrying into smaller units.
|
||||
var cookieDurationUnits = [...]cookieDurationUnit{
|
||||
{365 * 24 * 3600, "year", 21 * 24 * 3600},
|
||||
{30 * 24 * 3600, "month", 2 * 24 * 3600},
|
||||
{7 * 24 * 3600, "week", 12 * 3600},
|
||||
{24 * 3600, "day", 2 * 3600},
|
||||
{3600, "hour", 5 * 60},
|
||||
{60, "minute", 5},
|
||||
{1, "second", 0},
|
||||
}
|
||||
|
||||
// HumanizedDuration renders the cookie's max-age into a human-readable lifetime
|
||||
// using the same snapping and composition rules as the banner's
|
||||
// humanizeDuration helper. A nil or non-positive max-age denotes a session
|
||||
// cookie that is cleared when the browser closes.
|
||||
func (c CookieItem) HumanizedDuration() string {
|
||||
if c.MaxAgeSeconds == nil || *c.MaxAgeSeconds <= 0 {
|
||||
return "Session"
|
||||
}
|
||||
|
||||
remaining := *c.MaxAgeSeconds
|
||||
|
||||
var parts []string
|
||||
|
||||
for _, u := range cookieDurationUnits {
|
||||
if remaining < u.secs-u.snap {
|
||||
continue
|
||||
}
|
||||
|
||||
count := remaining / u.secs
|
||||
|
||||
leftover := remaining - count*u.secs
|
||||
switch {
|
||||
case leftover >= u.secs-u.snap:
|
||||
count++
|
||||
remaining = 0
|
||||
case leftover <= u.snap:
|
||||
remaining = 0
|
||||
default:
|
||||
remaining = leftover
|
||||
}
|
||||
|
||||
if count == 1 {
|
||||
parts = append(parts, fmt.Sprintf("1 %s", u.name))
|
||||
} else {
|
||||
parts = append(parts, fmt.Sprintf("%d %ss", count, u.name))
|
||||
}
|
||||
}
|
||||
|
||||
if len(parts) == 0 {
|
||||
return "Session"
|
||||
}
|
||||
|
||||
return strings.Join(parts, ", ")
|
||||
}
|
||||
|
||||
func (c CookieItems) MarshalJSON() ([]byte, error) {
|
||||
if c == nil {
|
||||
return []byte("[]"), nil
|
||||
|
||||
@@ -16,7 +16,7 @@ ALTER TABLE cookie_banners
|
||||
ADD COLUMN policy_document_id TEXT,
|
||||
ADD COLUMN policy_generation_requested_at TIMESTAMP WITH TIME ZONE;
|
||||
|
||||
-- Backfill: any banner that already has a published version gets its cookie
|
||||
-- Backfill: any banner that already has a published version gets its tracker
|
||||
-- policy generated on the worker's first pass.
|
||||
UPDATE cookie_banners
|
||||
SET policy_generation_requested_at = NOW()
|
||||
|
||||
Reference in New Issue
Block a user