Packages
phoenix_kit
1.7.77
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/customer_service/ticket_attachment.ex
defmodule PhoenixKit.Modules.CustomerService.TicketAttachment do
@moduledoc """
Junction schema for ticket and comment attachments.
Links tickets or comments to uploaded files (images, documents, etc.)
with ordering and optional captions. An attachment belongs to either
a ticket directly OR a comment, but not both.
## Fields
- `ticket_uuid` - Reference to ticket (if attached to ticket directly)
- `comment_uuid` - Reference to comment (if attached to comment)
- `file_uuid` - Reference to the uploaded file (PhoenixKit.Storage.File)
- `position` - Display order (1, 2, 3, etc.)
- `caption` - Optional caption/alt text
Note: Either `ticket_uuid` OR `comment_uuid` must be set, but not both.
## Examples
# Attachment on ticket itself
%TicketAttachment{
ticket_uuid: "018e3c4a-9f6b-7890-abcd-ef1234567890",
comment_uuid: nil,
file_uuid: "018e3c4a-1234-5678-abcd-ef1234567890",
position: 1,
caption: "Screenshot of the error"
}
# Attachment on a comment
%TicketAttachment{
ticket_uuid: nil,
comment_uuid: "018e3c4a-5678-1234-abcd-ef1234567890",
file_uuid: "018e3c4a-abcd-efgh-ijkl-mnopqrstuvwx",
position: 1,
caption: nil
}
"""
use Ecto.Schema
import Ecto.Changeset
@primary_key {:uuid, UUIDv7, autogenerate: true}
@foreign_key_type UUIDv7
@type t :: %__MODULE__{
uuid: UUIDv7.t() | nil,
ticket_uuid: UUIDv7.t() | nil,
comment_uuid: UUIDv7.t() | nil,
file_uuid: UUIDv7.t(),
position: integer(),
caption: String.t() | nil,
ticket:
PhoenixKit.Modules.CustomerService.Ticket.t() | Ecto.Association.NotLoaded.t() | nil,
comment:
PhoenixKit.Modules.CustomerService.TicketComment.t()
| Ecto.Association.NotLoaded.t()
| nil,
file: PhoenixKit.Modules.Storage.File.t() | Ecto.Association.NotLoaded.t(),
inserted_at: DateTime.t() | nil,
updated_at: DateTime.t() | nil
}
schema "phoenix_kit_ticket_attachments" do
field :position, :integer
field :caption, :string
belongs_to :ticket, PhoenixKit.Modules.CustomerService.Ticket,
foreign_key: :ticket_uuid,
references: :uuid
belongs_to :comment, PhoenixKit.Modules.CustomerService.TicketComment,
foreign_key: :comment_uuid,
references: :uuid
belongs_to :file, PhoenixKit.Modules.Storage.File, foreign_key: :file_uuid, references: :uuid
timestamps(type: :utc_datetime)
end
@doc """
Changeset for creating or updating an attachment.
## Required Fields
- `file_uuid` - Reference to file
- `position` - Display order (must be positive)
- Either `ticket_uuid` OR `comment_uuid` (but not both)
## Validation Rules
- Position must be greater than 0
- Must have exactly one of ticket_uuid or comment_uuid
"""
def changeset(attachment, attrs) do
attachment
|> cast(attrs, [:ticket_uuid, :comment_uuid, :file_uuid, :position, :caption])
|> validate_required([:file_uuid, :position])
|> validate_number(:position, greater_than: 0)
|> validate_parent_reference()
|> foreign_key_constraint(:ticket_uuid)
|> foreign_key_constraint(:comment_uuid)
|> foreign_key_constraint(:file_uuid)
end
@doc """
Check if attachment is attached to a ticket directly.
"""
def ticket_attachment?(%__MODULE__{ticket_uuid: ticket_uuid}) when not is_nil(ticket_uuid),
do: true
def ticket_attachment?(_), do: false
@doc """
Check if attachment is attached to a comment.
"""
def comment_attachment?(%__MODULE__{comment_uuid: comment_uuid}) when not is_nil(comment_uuid),
do: true
def comment_attachment?(_), do: false
# Private Functions
defp validate_parent_reference(changeset) do
ticket_uuid = get_field(changeset, :ticket_uuid)
comment_uuid = get_field(changeset, :comment_uuid)
case {ticket_uuid, comment_uuid} do
{nil, nil} ->
add_error(changeset, :ticket_uuid, "either ticket_uuid or comment_uuid must be set")
{id, cid} when not is_nil(id) and not is_nil(cid) ->
add_error(changeset, :comment_uuid, "cannot set both ticket_uuid and comment_uuid")
_ ->
changeset
end
end
end