Knd_Lepe
I’m working on an email API service, Already got the Ash action to send email working in iex.
My send_email action is a custom action that will perfom some non standard CRUD. Got 2 create actions and the calls to api ask for attributes from wrong create action. As for now just trying to get the Ok atom
My resource
defmodule Eppa.Accounts.Servicios do
use Ash.Resource,
otp_app: :eppa,
domain: Eppa.Accounts,
data_layer: AshPostgres.DataLayer,
extensions: [AshJsonApi.Resource]
json_api do
type "servicios"
end
postgres do
table "servicios"
repo Eppa.Repo
references do
reference :user, index?: true, on_delete: :delete
end
end
actions do
defaults [:read, :destroy]
create :create do
primary? true
accept [:tipo, :creditos_disponibles, :vigencia]
change relate_actor(:created_by, allow_nil?: true)
change relate_actor(:updated_by, allow_nil?: true)
end
update :update do
primary? true
accept [:creditos_disponibles, :vigencia]
change relate_actor(:updated_by, allow_nil?: true)
end
create :send_email do
# Definición de argumentos que se esperan recibir vía JSON
argument :user_email, :ci_string, allow_nil?: false
argument :destinatario, :ci_string, allow_nil?: false
argument :cc, {:array, :ci_string}, default: []
argument :asunto, :string, allow_nil?: false
argument :contenido_html, :ci_string, allow_nil?: false
argument :attachments, {:array, :map}, default: []
argument :plantilla_id, :ci_string, allow_nil?: true
# Testing - "
{:ok, "ok"}
end
end
attributes do
uuid_v7_primary_key :id
attribute :tipo, :atom do
constraints one_of: [:correos, :sms]
allow_nil? false
public? true
end
attribute :creditos_disponibles, :integer do
allow_nil? false
default 0
public? true
end
attribute :vigencia, :datetime do
allow_nil? false
public? true
end
attribute :user_id, :uuid do
end
create_timestamp :inserted_at
update_timestamp :updated_at
end
relationships do
belongs_to :created_by, Eppa.Accounts.User
belongs_to :updated_by, Eppa.Accounts.User
belongs_to :user, Eppa.Accounts.User do
allow_nil? false
end
end
end
My domain
defmodule Eppa.Accounts do
use Ash.Domain, otp_app: :eppa, extensions: [AshJsonApi.Domain]
json_api do
routes do
base_route "/servicios", Eppa.Accounts.Servicios do
post :send_email
end
end
end
resources do
resource Eppa.Accounts.Token
resource Eppa.Accounts.User do
define :create_user, action: :register_with_password
define :update_user, action: :update_user
define :get_users, action: :read
define :get_user_by_id, action: :read, get_by: :id
define :get_user_by_email, action: :read, get_by: :email
define :get_users_with_services, action: :read
end
resource Eppa.Accounts.Servicios do
define :get_servicios, action: :read
define :create_servicio, action: :create
define :send_email, action: :send_email
define :get_servicio_by_id, action: :read, get_by: :id
end
resource Eppa.Accounts.Profile do
define :create_perfil, action: :create
define :update_perfil, action: :update
define :get_profiles, action: :read
define :get_perfil_by_id, action: :read, get_by: :id
end
end
authorization do
# disable using the authorize?: false flag when calling actions
authorize :always
end
end
Router
defmodule EppaWeb.Router do
use EppaWeb, :router
use AshAuthentication.Phoenix.Router
import AshAuthentication.Plug.Helpers
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
plug :fetch_live_flash
plug :put_root_layout, html: {EppaWeb.Layouts, :root}
plug :protect_from_forgery
plug :put_secure_browser_headers
plug :load_from_session
end
pipeline :api do
plug :accepts, ["json"]
plug :load_from_bearer
plug :set_actor, :user
end
scope "/api/json" do
pipe_through [:api]
forward "/swaggerui", OpenApiSpex.Plug.SwaggerUI,
path: "/api/json/open_api",
default_model_expand_depth: 4
forward "/", EppaWeb.AshJsonApiRouter
end
scope "/", EppaWeb do
pipe_through :browser
ash_authentication_live_session :authenticated_routes do
live "/dashboard", DashboardLive, :index
live "/users", UserLive.Index, :index
live "/users/new", UserLive.Index, :new
live "/users/:id/edit", UserLive.Index, :edit
live "/users/:id", UserLive.Show, :show
live "/users/:id/show/edit", UserLive.Show, :edit
live "/users/:id/assign_profile", UserLive.Index, :assign_profile
live "/users/:id/servicios", EppaWeb.UserLive.Servicios, :index
live "/profiles", ProfileLive.Index, :index
live "/profiles/new", ProfileLive.Index, :new
live "/profiles/:id/edit", ProfileLive.Index, :edit
live "/profiles/:id", ProfileLive.Show, :show
live "/profiles/:id/show/edit", ProfileLive.Show, :edit
# live "/services", ServiceAssignmentLive.Index, :index
# live "/services/new", ServiceAssignmentLive.Index, :new
live "/services", ServicesLive.Index, :index
live "/services/:id/creditos", ServiceLive.Creditos, :creditos
# in each liveview, add one of the following at the top of the module:
#
# If an authenticated user must be present:
# on_mount {EppaWeb.LiveUserAuth, :live_user_required}
#
# If an authenticated user *may* be present:
# on_mount {EppaWeb.LiveUserAuth, :live_user_optional}
#
# If an authenticated user must *not* be present:
# on_mount {EppaWeb.LiveUserAuth, :live_no_user}
end
end
scope "/", EppaWeb do
pipe_through :browser
# live "/sign-in", SignInLive, :index
get "/", RedirectController, :to_sign_in
# get "/", PageController, :home
auth_routes AuthController, Eppa.Accounts.User, path: "/auth"
sign_out_route AuthController
# Remove these if you'd like to use your own authentication views
sign_in_route register_path: "/register",
reset_path: "/reset",
auth_routes_prefix: "/auth",
on_mount: [{EppaWeb.LiveUserAuth, :live_no_user}],
overrides: [EppaWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default]
# Remove this if you do not want to use the reset password feature
reset_route auth_routes_prefix: "/auth",
overrides: [EppaWeb.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default]
end
# Other scopes may use custom stacks.
# scope "/api", EppaWeb do
# pipe_through :api
# end
# Enable LiveDashboard and Swoosh mailbox preview in development
if Application.compile_env(:eppa, :dev_routes) do
# If you want to use the LiveDashboard in production, you should put
# it behind authentication and allow only admins to access it.
# If your application does not have an admins-only section yet,
# you can use Plug.BasicAuth to set up some basic authentication
# as long as you are also using SSL (which you should anyway).
import Phoenix.LiveDashboard.Router
scope "/dev" do
pipe_through :browser
live_dashboard "/dashboard", metrics: EppaWeb.Telemetry
forward "/mailbox", Plug.Swoosh.MailboxPreview
end
end
end
API CALL
{
"data":{
"type":"servicios",
"attributes": {
"user_email": "knd_rt@hotmail.com",
"destinatario": "blitzlepe@gmail.com" ,
"cc":[
"cande.lepe@agustindeiturbide.com"
],
"asunto":"Prueba desde Insomnia",
"contenido_html":"Su pinche madre, si jalo !!!",
"attachments":[]
}
}
}
Response
{
"errors": [
{
"code": "required",
"id": "c5e78bdc-fc81-421e-a9b9-cf71c85256bb",
"meta": {},
"status": "400",
"title": "Required",
"source": {
"pointer": "/data/attributes/tipo"
},
"detail": "is required"
},
{
"code": "required",
"id": "c6c953b2-0925-4ada-9c8f-fd682c248b99",
"meta": {},
"status": "400",
"title": "Required",
"source": {
"pointer": "/data/attributes/vigencia"
},
"detail": "is required"
}
],
"jsonapi": {
"version": "1.0"
}
}```
Trending in Questions
I having some trouble figuring out if I have set myself too strict of standards for my production server. Currently I can handle 75% of r...
New
Documentation
While reading the Scoped Routes section, I noticed that the documentation currently refers to a problem without explainin...
New
Hello,
I’m trying to build a basic Phoenix web-app, and I’d like to use Tailwind.
However, when I launch mix phx.server, I get an error...
New
Hi everyone,
I am toying with the idea of building a “match maker” for giving personal help to people that wants to start coding.
I sta...
New
I recently noticed that Elixir’s Logger defaults its primary log level to :debug when no :logger, :level application configuration is pre...
New
I’m working on a small exercise involving update_in/3, and I came up with this solution:
data = %{
name: "Periodic Table",
category:...
New
I’ve got trouble wrapping my head around the order in which functions are called in this snippet (from Phoenix’s authentication):
toke...
New
Other Trending Topics
Edit: 2026 May 15 - This post is archived.
Mob is alive!!
Main docs: mob v0.7.11 — Documentation
A bit of explanation for the slightly c...
New
Hey, I’m Jesse and I’m the main contributor behind Dexter, a full-featured, lightning-fast Elixir LSP optimized for large codebases. It s...
New
I am happy to introduce the very α version of the new programming language compiled to BEAM.
Welcome Cure.
It has literally three kille...
New
Hobbes is a low-level distributed database for the Elixir programming language.
Hobbes provides a simple, safe, and scalable storage lay...
New
Hi everyone!
The first release candidate for the Expert language server project is now available!
We’ve published a press release detai...
New
A little off-topic, but I feel like people here have a good head on their shoulders.
I used to be quite good at making software. Was luc...
New
Categories:
Sub Categories:
Forums
Popular Tags
- #ecto
- #liveview
- #troubleshooting
- #learning-elixir
- #library
- #deployment
- #erlang
- #testing
- #genserver
- #mix
- #absinthe
- #remote-other
- #otp
- #plug
- #how-to-question
- #macros
- #postgres
- #elixirconf
- #channels
- #exunit
- #discussion
- #code-sync
- #podcasts
- #javascript
- #onsite
- #dialyzer
- #docker
- #authentication
- #umbrella
- #full-time-contract
- #podcasts-by-brainlid
- #ai
- #ecto-query
- #elixirconf-us
- #blog-post
- #elixir-ls
- #phoenix_html
- #iex
- #graphql
- #genstage
- #websockets
- #supervisor
- #advent-of-code
- #distillery
- #processes
- #elixirconf-eu
- #api
- #forms
- #metaprogramming
- #hex










Showing Posts 1 to 7- Show Best Posts
- Show All (oldest first)
- Show All (newest first)
zachdaniel
Looking at this, I think you need to revisit your action basics before working on
AshJsonApi. Actions don’t have “return values” (except for generic actions, but that is inside therunfunction) i.e that{:ok, "ok"}is not the return of the action (it just does nothing in fact, its evaluated at compile time).The arguments also on their own don’t do anything, you need to do something with them like
change set_attribute(:some_attr, arg(:some_arg))(as an example).The reason you are getting errors about those fields being required is because those attributes are themselves required, and your action is not doing anything to set them:
Knd_Lepe
Thanks, I got api working (it receives json payload and persists it to postgres db, really cool!) Now, I want to execute some logic before_action and after_action, but not sure if my approach is correct, should I move this to a generic action??:
Api call
Response
zachdaniel
Nope, that’s fine to do that way
I would suggest consolidating it to a change module, where you can add both hooks in one place.
Knd_Lepe
Ok, looking for another way to do it, it looks I can´t pipe the “after_action”, if I do the response is:
zachdaniel
The function in after_action takes two arguments,
Ash.Changeset.after_action(changeset, fn changeset, result -> end)Knd_Lepe
Cool, you really helped me, thanks!!
Knd_Lepe
In the end I just used after_action, the only “issue” was to update an attribute that was just created but not persisted to db yet.
This did the magic