Packages
phoenix_kit
1.7.49
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/publishing/schemas/publishing_post.ex
defmodule PhoenixKit.Modules.Publishing.PublishingPost do
@moduledoc """
Schema for publishing posts within a group.
Each post belongs to a group and has versions with per-language content.
Supports both slug-mode and timestamp-mode URL structures.
## Status Flow
- `draft` - Not visible to public
- `published` - Live and visible
- `archived` - Hidden but preserved
- `scheduled` - Auto-publish at `scheduled_at`
## Data JSONB Keys
- `allow_version_access` - Whether older versions are publicly accessible
- `featured_image` - Featured image reference (file UUID or URL)
- `tags` - List of tag strings
- `seo` - SEO metadata map (og_title, og_description, og_image, etc.)
"""
use Ecto.Schema
import Ecto.Changeset
@primary_key {:uuid, UUIDv7, autogenerate: true}
@foreign_key_type UUIDv7
@type t :: %__MODULE__{
uuid: UUIDv7.t() | nil,
group_uuid: UUIDv7.t(),
slug: String.t(),
status: String.t(),
mode: String.t(),
primary_language: String.t(),
published_at: DateTime.t() | nil,
scheduled_at: DateTime.t() | nil,
post_date: Date.t() | nil,
post_time: Time.t() | nil,
created_by_uuid: UUIDv7.t() | nil,
created_by_id: integer() | nil,
updated_by_uuid: UUIDv7.t() | nil,
updated_by_id: integer() | nil,
data: map(),
inserted_at: DateTime.t() | nil,
updated_at: DateTime.t() | nil
}
schema "phoenix_kit_publishing_posts" do
field :slug, :string
field :status, :string, default: "draft"
field :mode, :string, default: "timestamp"
field :primary_language, :string, default: "en"
field :published_at, :utc_datetime
field :scheduled_at, :utc_datetime
field :post_date, :date
field :post_time, :time
field :data, :map, default: %{}
belongs_to :group, PhoenixKit.Modules.Publishing.PublishingGroup,
foreign_key: :group_uuid,
references: :uuid,
type: UUIDv7
belongs_to :created_by, PhoenixKit.Users.Auth.User,
foreign_key: :created_by_uuid,
references: :uuid,
type: UUIDv7
field :created_by_id, :integer
belongs_to :updated_by, PhoenixKit.Users.Auth.User,
foreign_key: :updated_by_uuid,
references: :uuid,
type: UUIDv7
field :updated_by_id, :integer
has_many :versions, PhoenixKit.Modules.Publishing.PublishingVersion, foreign_key: :post_uuid
timestamps(type: :utc_datetime)
end
@doc """
Changeset for creating or updating a publishing post.
"""
def changeset(post, attrs) do
post
|> cast(attrs, [
:group_uuid,
:slug,
:status,
:mode,
:primary_language,
:published_at,
:scheduled_at,
:post_date,
:post_time,
:created_by_uuid,
:created_by_id,
:updated_by_uuid,
:updated_by_id,
:data
])
|> validate_required([:group_uuid, :slug, :status, :mode, :primary_language])
|> validate_inclusion(:status, ["draft", "published", "archived", "scheduled"])
|> validate_inclusion(:mode, ["timestamp", "slug"])
|> validate_length(:slug, max: 500)
|> validate_length(:primary_language, max: 10)
|> validate_scheduled_at()
|> unique_constraint([:group_uuid, :slug], name: :idx_publishing_posts_group_slug)
|> foreign_key_constraint(:group_uuid, name: :fk_publishing_posts_group)
|> foreign_key_constraint(:created_by_uuid, name: :fk_publishing_posts_created_by)
|> foreign_key_constraint(:updated_by_uuid, name: :fk_publishing_posts_updated_by)
end
@doc "Check if post is published."
def published?(%__MODULE__{status: "published"}), do: true
def published?(_), do: false
@doc "Check if post is scheduled for future publishing."
def scheduled?(%__MODULE__{status: "scheduled"}), do: true
def scheduled?(_), do: false
@doc "Check if post is a draft."
def draft?(%__MODULE__{status: "draft"}), do: true
def draft?(_), do: false
# Data JSONB accessors
@doc "Returns whether older versions are publicly accessible."
def allow_version_access?(%__MODULE__{data: data}),
do: Map.get(data, "allow_version_access", false)
@doc "Returns the featured image reference."
def get_featured_image(%__MODULE__{data: data}), do: Map.get(data, "featured_image")
@doc "Returns the post tags."
def get_tags(%__MODULE__{data: data}), do: Map.get(data, "tags", [])
@doc "Returns SEO metadata."
def get_seo(%__MODULE__{data: data}), do: Map.get(data, "seo", %{})
defp validate_scheduled_at(changeset) do
status = get_field(changeset, :status)
scheduled_at = get_field(changeset, :scheduled_at)
status_changed? = get_change(changeset, :status) != nil
scheduled_at_changed? = get_change(changeset, :scheduled_at) != nil
case {status, scheduled_at} do
{"scheduled", nil} ->
add_error(changeset, :scheduled_at, "must be set when status is scheduled")
{"scheduled", datetime} when not is_nil(datetime) ->
if (scheduled_at_changed? or status_changed?) and
DateTime.compare(datetime, DateTime.utc_now()) == :lt do
add_error(changeset, :scheduled_at, "must be in the future")
else
changeset
end
_ ->
changeset
end
end
end