Packages

phoenix_kit

1.7.21
1.7.216 1.7.215 1.7.214 1.7.213 1.7.212 1.7.211 1.7.210 1.7.209 1.7.208 1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules legal schemas consent_log.ex
Raw

lib/modules/legal/schemas/consent_log.ex

defmodule PhoenixKit.Modules.Legal.ConsentLog do
@moduledoc """
Schema for consent log entries.
Tracks user consent for cookies and data processing, supporting GDPR, CCPA,
and other privacy regulations.
## Fields
* `user_id` - The ID of the logged-in user (nil for anonymous)
* `session_id` - Session identifier for anonymous tracking
* `consent_type` - Type of consent (necessary, analytics, marketing, preferences)
* `consent_given` - Whether consent was given
* `consent_version` - Version of privacy/cookie policy
* `ip_address` - IP address when consent was recorded
* `user_agent_hash` - SHA256 hash of user agent for fingerprinting
* `metadata` - Additional metadata about the consent
## Consent Types
* `necessary` - Essential cookies (always required)
* `analytics` - Performance and analytics cookies
* `marketing` - Advertising and targeting cookies
* `preferences` - Functionality and preference cookies
## Usage
# Log consent for anonymous user
ConsentLog.create(%{
session_id: "abc123",
consent_type: "analytics",
consent_given: true,
consent_version: "1.0",
ip_address: "192.168.1.1"
})
# Log consent for logged-in user
ConsentLog.create(%{
user_id: 123,
consent_type: "marketing",
consent_given: false
})
# Get current consent status
ConsentLog.get_consent_status(user_id: 123)
ConsentLog.get_consent_status(session_id: "abc123")
"""
use Ecto.Schema
import Ecto.Changeset
import Ecto.Query, warn: false
@type t :: %__MODULE__{
id: integer() | nil,
uuid: Ecto.UUID.t() | nil,
user_id: integer() | nil,
session_id: String.t() | nil,
consent_type: String.t(),
consent_given: boolean(),
consent_version: String.t() | nil,
ip_address: String.t() | nil,
user_agent_hash: String.t() | nil,
metadata: map() | nil,
inserted_at: DateTime.t() | nil,
updated_at: DateTime.t() | nil
}
@consent_types ["necessary", "analytics", "marketing", "preferences"]
schema "phoenix_kit_consent_logs" do
field :uuid, Ecto.UUID
field :user_id, :integer
field :session_id, :string
field :consent_type, :string
field :consent_given, :boolean, default: false
field :consent_version, :string
field :ip_address, :string
field :user_agent_hash, :string
field :metadata, :map, default: %{}
timestamps(type: :utc_datetime_usec)
end
@doc """
Returns valid consent types.
"""
@spec consent_types() :: list(String.t())
def consent_types, do: @consent_types
@doc """
Creates a changeset for consent log entry.
## Required Fields
* `:consent_type` - Type of consent
## Optional Fields
* `:user_id` - User ID (for logged-in users)
* `:session_id` - Session ID (for anonymous users)
* `:consent_given` - Whether consent was given (default: false)
* `:consent_version` - Version of policy
* `:ip_address` - IP address
* `:user_agent_hash` - Hashed user agent
* `:metadata` - Additional metadata
"""
@spec changeset(t(), map()) :: Ecto.Changeset.t()
def changeset(consent_log, attrs) do
consent_log
|> cast(attrs, [
:user_id,
:session_id,
:consent_type,
:consent_given,
:consent_version,
:ip_address,
:user_agent_hash,
:metadata
])
|> validate_required([:consent_type])
|> validate_inclusion(:consent_type, @consent_types)
|> validate_user_or_session()
|> maybe_generate_uuid()
end
# Validate that either user_id or session_id is present
defp validate_user_or_session(changeset) do
user_id = get_field(changeset, :user_id)
session_id = get_field(changeset, :session_id)
if is_nil(user_id) and is_nil(session_id) do
add_error(changeset, :base, "Either user_id or session_id must be present")
else
changeset
end
end
# Generate UUIDv7 if not present
defp maybe_generate_uuid(changeset) do
if get_field(changeset, :uuid) do
changeset
else
put_change(changeset, :uuid, PhoenixKit.UUID.generate())
end
end
# ===================================
# CRUD OPERATIONS
# ===================================
@doc """
Create a new consent log entry.
"""
@spec create(map()) :: {:ok, t()} | {:error, Ecto.Changeset.t()}
def create(attrs) do
%__MODULE__{}
|> changeset(attrs)
|> repo().insert()
end
@doc """
Get consent status for a user or session.
Returns a map of consent_type => consent_given for the most recent entries.
## Options
* `:user_id` - Get consent for logged-in user
* `:session_id` - Get consent for anonymous session
"""
@spec get_consent_status(keyword()) :: map()
def get_consent_status(opts) do
user_id = Keyword.get(opts, :user_id)
session_id = Keyword.get(opts, :session_id)
query =
from(c in __MODULE__,
select: %{consent_type: c.consent_type, consent_given: c.consent_given},
order_by: [desc: c.inserted_at]
)
query =
cond do
user_id -> where(query, [c], c.user_id == ^user_id)
session_id -> where(query, [c], c.session_id == ^session_id)
true -> query
end
query
|> repo().all()
|> Enum.uniq_by(& &1.consent_type)
|> Map.new(fn %{consent_type: type, consent_given: given} -> {type, given} end)
rescue
_ -> %{}
end
@doc """
Log consent for multiple types at once.
## Parameters
* `consents` - Map of consent_type => consent_given
* `opts` - Options including :user_id, :session_id, :ip_address, etc.
## Example
ConsentLog.log_consents(
%{"analytics" => true, "marketing" => false},
user_id: 123,
consent_version: "1.0"
)
"""
@spec log_consents(map(), keyword()) :: {:ok, list(t())} | {:error, term()}
def log_consents(consents, opts) when is_map(consents) do
base_attrs = %{
user_id: Keyword.get(opts, :user_id),
session_id: Keyword.get(opts, :session_id),
consent_version: Keyword.get(opts, :consent_version),
ip_address: Keyword.get(opts, :ip_address),
user_agent_hash: Keyword.get(opts, :user_agent_hash),
metadata: Keyword.get(opts, :metadata, %{})
}
results =
Enum.map(consents, fn {consent_type, consent_given} ->
attrs =
Map.merge(base_attrs, %{
consent_type: consent_type,
consent_given: consent_given
})
create(attrs)
end)
errors = Enum.filter(results, &match?({:error, _}, &1))
if Enum.empty?(errors) do
{:ok, Enum.map(results, fn {:ok, log} -> log end)}
else
{:error, errors}
end
end
@doc """
Hash user agent string for privacy-focused storage.
"""
@spec hash_user_agent(String.t()) :: String.t()
def hash_user_agent(user_agent) when is_binary(user_agent) do
:crypto.hash(:sha256, user_agent)
|> Base.encode16(case: :lower)
end
def hash_user_agent(_), do: nil
# ===================================
# PRIVATE HELPERS
# ===================================
defp repo do
PhoenixKit.RepoHelper.repo()
end
end