Packages
phoenix_kit
1.7.33
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
Current section
Files
lib/modules/posts/schemas/post_view.ex
defmodule PhoenixKit.Modules.Posts.PostView do
@moduledoc """
Schema for post view tracking (analytics).
Tracks when posts are viewed for analytics purposes. Supports session-based
deduplication to avoid counting multiple views from the same visitor.
## Fields
- `post_id` - Reference to the viewed post
- `user_id` - Reference to the viewer (nullable for guests)
- `ip_address` - Hashed IP for privacy
- `user_agent_hash` - Hashed browser fingerprint
- `session_id` - Session identifier for deduplication
- `viewed_at` - Timestamp of view
## Examples
# Logged-in user view
%PostView{
post_id: "018e3c4a-9f6b-7890-abcd-ef1234567890",
user_id: 42,
ip_address: "hashed_ip",
user_agent_hash: "hashed_ua",
session_id: "session_abc123",
viewed_at: ~U[2025-01-01 12:00:00Z]
}
# Guest view
%PostView{
post_id: "018e3c4a-9f6b-7890-abcd-ef1234567890",
user_id: nil,
ip_address: "hashed_ip",
user_agent_hash: "hashed_ua",
session_id: "session_xyz789",
viewed_at: ~U[2025-01-01 13:30:00Z]
}
"""
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, UUIDv7, autogenerate: true}
@type t :: %__MODULE__{
id: UUIDv7.t() | nil,
post_id: UUIDv7.t(),
user_id: integer() | nil,
ip_address: String.t() | nil,
user_agent_hash: String.t() | nil,
session_id: String.t() | nil,
viewed_at: DateTime.t(),
post: PhoenixKit.Modules.Posts.Post.t() | Ecto.Association.NotLoaded.t(),
user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t() | nil,
inserted_at: NaiveDateTime.t() | nil,
updated_at: NaiveDateTime.t() | nil
}
schema "phoenix_kit_post_views" do
field :ip_address, :string
field :user_agent_hash, :string
field :session_id, :string
field :viewed_at, :utc_datetime_usec
belongs_to :post, PhoenixKit.Modules.Posts.Post, type: UUIDv7
belongs_to :user, PhoenixKit.Users.Auth.User, type: :integer
timestamps(type: :naive_datetime)
end
@doc """
Changeset for creating a post view record.
## Required Fields
- `post_id` - Reference to post
- `viewed_at` - Timestamp of view
## Validation Rules
- viewed_at must not be in the future
"""
def changeset(view, attrs) do
view
|> cast(attrs, [:post_id, :user_id, :ip_address, :user_agent_hash, :session_id, :viewed_at])
|> validate_required([:post_id, :viewed_at])
|> validate_viewed_at_not_future()
|> foreign_key_constraint(:post_id)
|> foreign_key_constraint(:user_id)
end
@doc """
Hash an IP address for privacy.
"""
def hash_ip(ip_address) when is_binary(ip_address) do
:crypto.hash(:sha256, ip_address) |> Base.encode16(case: :lower)
end
@doc """
Hash a user agent for privacy.
"""
def hash_user_agent(user_agent) when is_binary(user_agent) do
:crypto.hash(:sha256, user_agent) |> Base.encode16(case: :lower)
end
# Private Functions
defp validate_viewed_at_not_future(changeset) do
viewed_at = get_field(changeset, :viewed_at)
if viewed_at && DateTime.compare(viewed_at, DateTime.utc_now()) == :gt do
add_error(changeset, :viewed_at, "cannot be in the future")
else
changeset
end
end
end