mirror of
https://codeberg.org/JasterV/spazio-solazzo.git
synced 2026-04-26 18:20:03 +00:00
refactor ash validations to be reusable
This commit is contained in:
parent
c01bd8e733
commit
9dd9feac27
9 changed files with 121 additions and 83 deletions
|
|
@ -116,7 +116,7 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
end
|
||||
|
||||
# Apply shared admin filters preparation
|
||||
prepare SpazioSolazzo.BookingSystem.Preparations.ApplyAdminFilters
|
||||
prepare SpazioSolazzo.BookingSystem.Booking.Preparations.ApplyAdminFilters
|
||||
|
||||
prepare fn query, _ctx ->
|
||||
Ash.Query.sort(query, inserted_at: :desc)
|
||||
|
|
@ -142,7 +142,7 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
end
|
||||
|
||||
# Apply shared admin filters preparation
|
||||
prepare SpazioSolazzo.BookingSystem.Preparations.ApplyAdminFilters
|
||||
prepare SpazioSolazzo.BookingSystem.Booking.Preparations.ApplyAdminFilters
|
||||
|
||||
prepare fn query, _ctx ->
|
||||
Ash.Query.sort(query, start_datetime: :desc)
|
||||
|
|
@ -161,40 +161,11 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
argument :customer_comment, :string, allow_nil?: true
|
||||
|
||||
change manage_relationship(:space_id, :space, type: :append_and_remove)
|
||||
|
||||
change manage_relationship(:user_id, :user, type: :append_and_remove, authorize?: false)
|
||||
|
||||
validate fn changeset, _ctx ->
|
||||
date = Ash.Changeset.get_argument(changeset, :date)
|
||||
today = Date.utc_today()
|
||||
|
||||
if date && Date.compare(date, today) == :lt do
|
||||
{:error, field: :date, message: "cannot be in the past"}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
validate fn changeset, _ctx ->
|
||||
start_time = Ash.Changeset.get_argument(changeset, :start_time)
|
||||
end_time = Ash.Changeset.get_argument(changeset, :end_time)
|
||||
|
||||
if start_time && end_time && Time.compare(end_time, start_time) != :gt do
|
||||
{:error, field: :end_time, message: "must be after start time"}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
validate fn changeset, _ctx ->
|
||||
email = Ash.Changeset.get_argument(changeset, :customer_email)
|
||||
|
||||
if email && !String.match?(email, ~r/^[^\s@]+@[^\s@]+\.[^\s@]+$/) do
|
||||
{:error, field: :customer_email, message: "must be a valid email"}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
validate {SpazioSolazzo.BookingSystem.Validations.FutureDate, field: :date}
|
||||
validate {SpazioSolazzo.BookingSystem.Validations.ChronologicalOrder, start: :start_time, end: :end_time}
|
||||
validate {SpazioSolazzo.BookingSystem.Validations.Email, field: :customer_email}
|
||||
|
||||
change fn changeset, _ctx ->
|
||||
date = Ash.Changeset.get_argument(changeset, :date)
|
||||
|
|
@ -258,38 +229,9 @@ defmodule SpazioSolazzo.BookingSystem.Booking do
|
|||
|
||||
change manage_relationship(:space_id, :space, type: :append_and_remove)
|
||||
|
||||
validate fn changeset, _ctx ->
|
||||
end_datetime = Ash.Changeset.get_argument(changeset, :end_datetime)
|
||||
now = DateTime.utc_now()
|
||||
|
||||
if end_datetime && DateTime.compare(end_datetime, now) == :lt do
|
||||
{:error, field: :end_datetime, message: "cannot be in the past"}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
validate fn changeset, _ctx ->
|
||||
start_datetime = Ash.Changeset.get_argument(changeset, :start_datetime)
|
||||
end_datetime = Ash.Changeset.get_argument(changeset, :end_datetime)
|
||||
|
||||
if start_datetime && end_datetime &&
|
||||
DateTime.compare(end_datetime, start_datetime) != :gt do
|
||||
{:error, field: :end_datetime, message: "must be after start datetime"}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
validate fn changeset, _ctx ->
|
||||
email = Ash.Changeset.get_argument(changeset, :customer_email)
|
||||
|
||||
if email && !String.match?(email, ~r/^[^\s@]+@[^\s@]+\.[^\s@]+$/) do
|
||||
{:error, field: :customer_email, message: "must be a valid email"}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
validate {SpazioSolazzo.BookingSystem.Validations.FutureDate, field: :end_datetime}
|
||||
validate {SpazioSolazzo.BookingSystem.Validations.ChronologicalOrder, start: :start_datetime, end: :end_datetime}
|
||||
validate {SpazioSolazzo.BookingSystem.Validations.Email, field: :customer_email}
|
||||
|
||||
change fn changeset, _ctx ->
|
||||
start_datetime = Ash.Changeset.get_argument(changeset, :start_datetime)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
defmodule SpazioSolazzo.BookingSystem.Preparations.ApplyAdminFilters do
|
||||
defmodule SpazioSolazzo.BookingSystem.Booking.Preparations.ApplyAdminFilters do
|
||||
@moduledoc """
|
||||
Ash Preparation that applies common admin filters (space_id, email, date) to booking queries.
|
||||
"""
|
||||
|
|
@ -21,16 +21,8 @@ defmodule SpazioSolazzo.BookingSystem.TimeSlotTemplate do
|
|||
create :create do
|
||||
accept [:start_time, :end_time, :space_id, :day_of_week]
|
||||
|
||||
validate fn changeset, _ctx ->
|
||||
start_time = Ash.Changeset.get_attribute(changeset, :start_time)
|
||||
end_time = Ash.Changeset.get_attribute(changeset, :end_time)
|
||||
|
||||
if start_time && end_time && Time.compare(end_time, start_time) != :gt do
|
||||
{:error, field: :end_time, message: "must be after start time"}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
validate {SpazioSolazzo.BookingSystem.Validations.ChronologicalOrder,
|
||||
start: :start_time, end: :end_time}
|
||||
|
||||
change {Changes.PreventCreationOverlap, []}
|
||||
end
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
defmodule SpazioSolazzo.BookingSystem.Validations.ChronologicalOrder do
|
||||
@moduledoc """
|
||||
Validates that an end time/datetime occurs after a start time/datetime.
|
||||
"""
|
||||
use Ash.Resource.Validation
|
||||
|
||||
@impl true
|
||||
def init(opts) do
|
||||
if Keyword.has_key?(opts, :start) && Keyword.has_key?(opts, :end) do
|
||||
{:ok, opts}
|
||||
else
|
||||
{:error, "Both `start` and `end` options are required."}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def validate(changeset, opts, _context) do
|
||||
start_field = opts[:start]
|
||||
end_field = opts[:end]
|
||||
|
||||
start_val = get_value(changeset, start_field)
|
||||
end_val = get_value(changeset, end_field)
|
||||
|
||||
if start_val && end_val && !after?(end_val, start_val) do
|
||||
{:error, field: end_field, message: "must be after #{start_field}"}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp after?(%Time{} = a, %Time{} = b), do: Time.compare(a, b) == :gt
|
||||
defp after?(%DateTime{} = a, %DateTime{} = b), do: DateTime.compare(a, b) == :gt
|
||||
defp after?(_, _), do: true
|
||||
|
||||
defp get_value(changeset, field) do
|
||||
Ash.Changeset.get_argument(changeset, field) || Ash.Changeset.get_attribute(changeset, field)
|
||||
end
|
||||
end
|
||||
31
lib/spazio_solazzo/booking_system/validations/email.ex
Normal file
31
lib/spazio_solazzo/booking_system/validations/email.ex
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
defmodule SpazioSolazzo.BookingSystem.Validations.Email do
|
||||
@moduledoc """
|
||||
Validates that a field contains a valid email address.
|
||||
"""
|
||||
use Ash.Resource.Validation
|
||||
|
||||
@impl true
|
||||
def init(opts) do
|
||||
if Keyword.has_key?(opts, :field) do
|
||||
{:ok, opts}
|
||||
else
|
||||
{:error, "The `field` option is required."}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def validate(changeset, opts, _context) do
|
||||
field = opts[:field]
|
||||
value = get_value(changeset, field)
|
||||
|
||||
if value && !String.match?(value, ~r/^[^\s@]+@[^\s@]+\.[^\s@]+$/) do
|
||||
{:error, field: field, message: "must be a valid email"}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp get_value(changeset, field) do
|
||||
Ash.Changeset.get_argument(changeset, field) || Ash.Changeset.get_attribute(changeset, field)
|
||||
end
|
||||
end
|
||||
35
lib/spazio_solazzo/booking_system/validations/future_date.ex
Normal file
35
lib/spazio_solazzo/booking_system/validations/future_date.ex
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
defmodule SpazioSolazzo.BookingSystem.Validations.FutureDate do
|
||||
@moduledoc """
|
||||
Validates that a date or datetime is in the future relative to UTC now/today.
|
||||
"""
|
||||
use Ash.Resource.Validation
|
||||
|
||||
@impl true
|
||||
def init(opts) do
|
||||
if Keyword.has_key?(opts, :field) do
|
||||
{:ok, opts}
|
||||
else
|
||||
{:error, "The `field` option is required."}
|
||||
end
|
||||
end
|
||||
|
||||
@impl true
|
||||
def validate(changeset, opts, _context) do
|
||||
field = opts[:field]
|
||||
value = get_value(changeset, field)
|
||||
|
||||
if value && in_past?(value) do
|
||||
{:error, field: field, message: "cannot be in the past"}
|
||||
else
|
||||
:ok
|
||||
end
|
||||
end
|
||||
|
||||
defp in_past?(%Date{} = date), do: Date.compare(date, Date.utc_today()) == :lt
|
||||
defp in_past?(%DateTime{} = dt), do: DateTime.compare(dt, DateTime.utc_now()) == :lt
|
||||
defp in_past?(_), do: false
|
||||
|
||||
defp get_value(changeset, field) do
|
||||
Ash.Changeset.get_argument(changeset, field) || Ash.Changeset.get_attribute(changeset, field)
|
||||
end
|
||||
end
|
||||
|
|
@ -3,7 +3,7 @@ defmodule SpazioSolazzoWeb.BookingCalendarLiveComponent do
|
|||
The calendar displayed in the space booking view.
|
||||
It allows users to select a date in a beautifully-styled calendar grid.
|
||||
"""
|
||||
|
||||
|
||||
use SpazioSolazzoWeb, :live_component
|
||||
alias SpazioSolazzo.CalendarExt
|
||||
|
||||
|
|
@ -54,7 +54,7 @@ defmodule SpazioSolazzoWeb.BookingCalendarLiveComponent do
|
|||
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<div class="calendar-container">
|
||||
<div id={@id} class="calendar-container">
|
||||
<%!-- Header --%>
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<button
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ defmodule SpazioSolazzo.BookingSystem.BookingTest do
|
|||
)
|
||||
|
||||
error_messages = Ash.Error.error_descriptions(error)
|
||||
assert String.contains?(error_messages, "must be after start time")
|
||||
assert String.contains?(error_messages, "must be after start_time")
|
||||
end
|
||||
|
||||
test "rejects booking in the past", %{space: space} do
|
||||
|
|
@ -682,7 +682,7 @@ defmodule SpazioSolazzo.BookingSystem.BookingTest do
|
|||
)
|
||||
|
||||
error_messages = Ash.Error.error_descriptions(error)
|
||||
assert String.contains?(error_messages, "must be after start datetime")
|
||||
assert String.contains?(error_messages, "must be after start_datetime")
|
||||
end
|
||||
|
||||
test "rejects walk-in with end time in the past", %{space: space} do
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ defmodule SpazioSolazzo.BookingSystem.TimeSlotTemplateTest do
|
|||
)
|
||||
|
||||
error_messages = Ash.Error.error_descriptions(error)
|
||||
assert String.contains?(error_messages, "must be after start time")
|
||||
assert String.contains?(error_messages, "must be after start_time")
|
||||
end
|
||||
|
||||
test "rejects equal start and end times", %{space: space} do
|
||||
|
|
@ -149,7 +149,7 @@ defmodule SpazioSolazzo.BookingSystem.TimeSlotTemplateTest do
|
|||
)
|
||||
|
||||
error_messages = Ash.Error.error_descriptions(error)
|
||||
assert String.contains?(error_messages, "must be after start time")
|
||||
assert String.contains?(error_messages, "must be after start_time")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue