Packages

phoenix_kit

1.7.71
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 posts schemas post_group.ex
Raw

lib/modules/posts/schemas/post_group.ex

defmodule PhoenixKit.Modules.Posts.PostGroup do
@moduledoc """
Schema for user-created post groups (Pinterest-style boards).
Users create groups/collections to organize their posts by theme, project, or topic.
Groups are user-specific - each user has their own set of groups.
## Fields
- `user_uuid` - Owner of the group
- `name` - Group name (e.g., "Travel Photos", "Work Projects")
- `slug` - URL-safe slug (e.g., "travel-photos")
- `description` - Optional group description
- `cover_image_uuid` - Optional cover image (reference to file)
- `post_count` - Denormalized counter (updated via context)
- `is_public` - Whether group is visible to others
- `position` - Manual ordering of user's groups
## Examples
# Public travel group
%PostGroup{
uuid: "018e3c4a-9f6b-7890-abcd-ef1234567890",
user_uuid: "018e3c4a-1234-5678-abcd-ef1234567890",
name: "Travel Adventures",
slug: "travel-adventures",
description: "My favorite travel moments",
cover_image_uuid: "018e3c4a-1234-5678-abcd-ef1234567890",
post_count: 23,
is_public: true,
position: 1
}
# Private work group
%PostGroup{
user_uuid: "018e3c4a-5678-1234-abcd-ef1234567890",
name: "Client Projects",
slug: "client-projects",
description: nil,
cover_image_uuid: nil,
post_count: 0,
is_public: false,
position: 2
}
"""
use Ecto.Schema
import Ecto.Changeset
@primary_key {:uuid, UUIDv7, autogenerate: true}
@foreign_key_type UUIDv7
@type t :: %__MODULE__{
uuid: UUIDv7.t() | nil,
user_uuid: UUIDv7.t() | nil,
name: String.t(),
slug: String.t(),
description: String.t() | nil,
cover_image_uuid: UUIDv7.t() | nil,
post_count: integer(),
is_public: boolean(),
position: integer(),
user: PhoenixKit.Users.Auth.User.t() | Ecto.Association.NotLoaded.t(),
cover_image: PhoenixKit.Modules.Storage.File.t() | Ecto.Association.NotLoaded.t() | nil,
posts: [PhoenixKit.Modules.Posts.Post.t()] | Ecto.Association.NotLoaded.t(),
inserted_at: DateTime.t() | nil,
updated_at: DateTime.t() | nil
}
schema "phoenix_kit_post_groups" do
field :name, :string
field :slug, :string
field :description, :string
field :post_count, :integer, default: 0
field :is_public, :boolean, default: false
field :position, :integer, default: 0
belongs_to :user, PhoenixKit.Users.Auth.User,
foreign_key: :user_uuid,
references: :uuid,
type: UUIDv7
belongs_to :cover_image, PhoenixKit.Modules.Storage.File,
foreign_key: :cover_image_uuid,
references: :uuid,
type: UUIDv7
many_to_many :posts, PhoenixKit.Modules.Posts.Post,
join_through: PhoenixKit.Modules.Posts.PostGroupAssignment,
join_keys: [group_uuid: :uuid, post_uuid: :uuid]
timestamps(type: :utc_datetime)
end
@doc """
Changeset for creating or updating a group.
## Required Fields
- `user_uuid` - Owner of the group
- `name` - Group name
- `slug` - URL-safe slug (auto-generated from name if not provided)
## Validation Rules
- Name must not be empty (max 100 chars)
- Slug must be unique per user
- Slug auto-generated from name if not provided
- Position must be >= 0
- Post count cannot be negative
"""
def changeset(group, attrs) do
group
|> cast(attrs, [
:user_uuid,
:name,
:slug,
:description,
:cover_image_uuid,
:is_public,
:position,
:post_count
])
|> validate_required([:user_uuid, :name])
|> validate_length(:name, min: 1, max: 100)
|> validate_length(:description, max: 1000)
|> maybe_generate_slug()
|> validate_required([:slug])
|> validate_format(:slug, ~r/^[a-z0-9-]+$/,
message: "must be lowercase letters, numbers, and hyphens only"
)
|> validate_number(:position, greater_than_or_equal_to: 0)
|> validate_number(:post_count, greater_than_or_equal_to: 0)
|> foreign_key_constraint(:user_uuid)
|> foreign_key_constraint(:cover_image_uuid)
|> unique_constraint([:user_uuid, :slug],
name: :phoenix_kit_post_groups_user_uuid_slug_index,
message: "you already have a group with this slug"
)
end
@doc """
Check if group is public.
"""
def public?(%__MODULE__{is_public: true}), do: true
def public?(_), do: false
@doc """
Check if user owns the group.
"""
def user_owns?(%__MODULE__{user_uuid: user_uuid}, user_uuid) when not is_nil(user_uuid),
do: true
def user_owns?(_, _), do: false
# Private Functions
defp maybe_generate_slug(changeset) do
case get_change(changeset, :slug) do
nil ->
name = get_field(changeset, :name)
if name do
slug = slugify(name)
put_change(changeset, :slug, slug)
else
changeset
end
_slug ->
changeset
end
end
defp slugify(name) do
name
|> String.downcase()
|> String.replace(~r/[^\w\s-]/, "")
|> String.replace(~r/\s+/, "-")
|> String.trim("-")
end
end