After 'mix phx.gen.live Items Item items name:string description:string info:string amount:integer factor:float type:string'

This commit is contained in:
2026-04-21 13:29:13 +02:00
parent e930c742b5
commit 851665ef60
10 changed files with 698 additions and 0 deletions

View File

@ -0,0 +1,147 @@
defmodule GenericRestServer.Items do
@moduledoc """
The Items context.
"""
import Ecto.Query, warn: false
alias GenericRestServer.Repo
alias GenericRestServer.Items.Item
alias GenericRestServer.Accounts.Scope
@doc """
Subscribes to scoped notifications about any item changes.
The broadcasted messages match the pattern:
* {:created, %Item{}}
* {:updated, %Item{}}
* {:deleted, %Item{}}
"""
def subscribe_items(%Scope{} = scope) do
key = scope.user.id
Phoenix.PubSub.subscribe(GenericRestServer.PubSub, "user:#{key}:items")
end
defp broadcast_item(%Scope{} = scope, message) do
key = scope.user.id
Phoenix.PubSub.broadcast(GenericRestServer.PubSub, "user:#{key}:items", message)
end
@doc """
Returns the list of items.
## Examples
iex> list_items(scope)
[%Item{}, ...]
"""
def list_items(%Scope{} = scope) do
Repo.all_by(Item, user_id: scope.user.id)
end
@doc """
Gets a single item.
Raises `Ecto.NoResultsError` if the Item does not exist.
## Examples
iex> get_item!(scope, 123)
%Item{}
iex> get_item!(scope, 456)
** (Ecto.NoResultsError)
"""
def get_item!(%Scope{} = scope, id) do
Repo.get_by!(Item, id: id, user_id: scope.user.id)
end
@doc """
Creates a item.
## Examples
iex> create_item(scope, %{field: value})
{:ok, %Item{}}
iex> create_item(scope, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def create_item(%Scope{} = scope, attrs) do
with {:ok, item = %Item{}} <-
%Item{}
|> Item.changeset(attrs, scope)
|> Repo.insert() do
broadcast_item(scope, {:created, item})
{:ok, item}
end
end
@doc """
Updates a item.
## Examples
iex> update_item(scope, item, %{field: new_value})
{:ok, %Item{}}
iex> update_item(scope, item, %{field: bad_value})
{:error, %Ecto.Changeset{}}
"""
def update_item(%Scope{} = scope, %Item{} = item, attrs) do
true = item.user_id == scope.user.id
with {:ok, item = %Item{}} <-
item
|> Item.changeset(attrs, scope)
|> Repo.update() do
broadcast_item(scope, {:updated, item})
{:ok, item}
end
end
@doc """
Deletes a item.
## Examples
iex> delete_item(scope, item)
{:ok, %Item{}}
iex> delete_item(scope, item)
{:error, %Ecto.Changeset{}}
"""
def delete_item(%Scope{} = scope, %Item{} = item) do
true = item.user_id == scope.user.id
with {:ok, item = %Item{}} <-
Repo.delete(item) do
broadcast_item(scope, {:deleted, item})
{:ok, item}
end
end
@doc """
Returns an `%Ecto.Changeset{}` for tracking item changes.
## Examples
iex> change_item(scope, item)
%Ecto.Changeset{data: %Item{}}
"""
def change_item(%Scope{} = scope, %Item{} = item, attrs \\ %{}) do
true = item.user_id == scope.user.id
Item.changeset(item, attrs, scope)
end
end

View File

@ -0,0 +1,26 @@
defmodule GenericRestServer.Items.Item do
use Ecto.Schema
import Ecto.Changeset
@primary_key {:id, :binary_id, autogenerate: true}
@foreign_key_type :binary_id
schema "items" do
field :name, :string
field :description, :string
field :info, :string
field :amount, :integer
field :factor, :float
field :type, :string
field :user_id, :binary_id
timestamps(type: :utc_datetime)
end
@doc false
def changeset(item, attrs, user_scope) do
item
|> cast(attrs, [:name, :description, :info, :amount, :factor, :type])
|> validate_required([:name, :description, :info, :amount, :factor, :type])
|> put_change(:user_id, user_scope.user.id)
end
end

View File

@ -0,0 +1,103 @@
defmodule GenericRestServerWeb.ItemLive.Form do
use GenericRestServerWeb, :live_view
alias GenericRestServer.Items
alias GenericRestServer.Items.Item
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_scope={@current_scope}>
<.header>
{@page_title}
<:subtitle>Use this form to manage item records in your database.</:subtitle>
</.header>
<.form for={@form} id="item-form" phx-change="validate" phx-submit="save">
<.input field={@form[:name]} type="text" label="Name" />
<.input field={@form[:description]} type="text" label="Description" />
<.input field={@form[:info]} type="text" label="Info" />
<.input field={@form[:amount]} type="number" label="Amount" />
<.input field={@form[:factor]} type="number" label="Factor" step="any" />
<.input field={@form[:type]} type="text" label="Type" />
<footer>
<.button phx-disable-with="Saving..." variant="primary">Save Item</.button>
<.button navigate={return_path(@current_scope, @return_to, @item)}>Cancel</.button>
</footer>
</.form>
</Layouts.app>
"""
end
@impl true
def mount(params, _session, socket) do
{:ok,
socket
|> assign(:return_to, return_to(params["return_to"]))
|> apply_action(socket.assigns.live_action, params)}
end
defp return_to("show"), do: "show"
defp return_to(_), do: "index"
defp apply_action(socket, :edit, %{"id" => id}) do
item = Items.get_item!(socket.assigns.current_scope, id)
socket
|> assign(:page_title, "Edit Item")
|> assign(:item, item)
|> assign(:form, to_form(Items.change_item(socket.assigns.current_scope, item)))
end
defp apply_action(socket, :new, _params) do
item = %Item{user_id: socket.assigns.current_scope.user.id}
socket
|> assign(:page_title, "New Item")
|> assign(:item, item)
|> assign(:form, to_form(Items.change_item(socket.assigns.current_scope, item)))
end
@impl true
def handle_event("validate", %{"item" => item_params}, socket) do
changeset = Items.change_item(socket.assigns.current_scope, socket.assigns.item, item_params)
{:noreply, assign(socket, form: to_form(changeset, action: :validate))}
end
def handle_event("save", %{"item" => item_params}, socket) do
save_item(socket, socket.assigns.live_action, item_params)
end
defp save_item(socket, :edit, item_params) do
case Items.update_item(socket.assigns.current_scope, socket.assigns.item, item_params) do
{:ok, item} ->
{:noreply,
socket
|> put_flash(:info, "Item updated successfully")
|> push_navigate(
to: return_path(socket.assigns.current_scope, socket.assigns.return_to, item)
)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
defp save_item(socket, :new, item_params) do
case Items.create_item(socket.assigns.current_scope, item_params) do
{:ok, item} ->
{:noreply,
socket
|> put_flash(:info, "Item created successfully")
|> push_navigate(
to: return_path(socket.assigns.current_scope, socket.assigns.return_to, item)
)}
{:error, %Ecto.Changeset{} = changeset} ->
{:noreply, assign(socket, form: to_form(changeset))}
end
end
defp return_path(_scope, "index", _item), do: ~p"/items"
defp return_path(_scope, "show", item), do: ~p"/items/#{item}"
end

View File

@ -0,0 +1,78 @@
defmodule GenericRestServerWeb.ItemLive.Index do
use GenericRestServerWeb, :live_view
alias GenericRestServer.Items
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_scope={@current_scope}>
<.header>
Listing Items
<:actions>
<.button variant="primary" navigate={~p"/items/new"}>
<.icon name="hero-plus" /> New Item
</.button>
</:actions>
</.header>
<.table
id="items"
rows={@streams.items}
row_click={fn {_id, item} -> JS.navigate(~p"/items/#{item}") end}
>
<:col :let={{_id, item}} label="Name">{item.name}</:col>
<:col :let={{_id, item}} label="Description">{item.description}</:col>
<:col :let={{_id, item}} label="Info">{item.info}</:col>
<:col :let={{_id, item}} label="Amount">{item.amount}</:col>
<:col :let={{_id, item}} label="Factor">{item.factor}</:col>
<:col :let={{_id, item}} label="Type">{item.type}</:col>
<:action :let={{_id, item}}>
<div class="sr-only">
<.link navigate={~p"/items/#{item}"}>Show</.link>
</div>
<.link navigate={~p"/items/#{item}/edit"}>Edit</.link>
</:action>
<:action :let={{id, item}}>
<.link
phx-click={JS.push("delete", value: %{id: item.id}) |> hide("##{id}")}
data-confirm="Are you sure?"
>
Delete
</.link>
</:action>
</.table>
</Layouts.app>
"""
end
@impl true
def mount(_params, _session, socket) do
if connected?(socket) do
Items.subscribe_items(socket.assigns.current_scope)
end
{:ok,
socket
|> assign(:page_title, "Listing Items")
|> stream(:items, list_items(socket.assigns.current_scope))}
end
@impl true
def handle_event("delete", %{"id" => id}, socket) do
item = Items.get_item!(socket.assigns.current_scope, id)
{:ok, _} = Items.delete_item(socket.assigns.current_scope, item)
{:noreply, stream_delete(socket, :items, item)}
end
@impl true
def handle_info({type, %GenericRestServer.Items.Item{}}, socket)
when type in [:created, :updated, :deleted] do
{:noreply, stream(socket, :items, list_items(socket.assigns.current_scope), reset: true)}
end
defp list_items(current_scope) do
Items.list_items(current_scope)
end
end

View File

@ -0,0 +1,69 @@
defmodule GenericRestServerWeb.ItemLive.Show do
use GenericRestServerWeb, :live_view
alias GenericRestServer.Items
@impl true
def render(assigns) do
~H"""
<Layouts.app flash={@flash} current_scope={@current_scope}>
<.header>
Item {@item.id}
<:subtitle>This is a item record from your database.</:subtitle>
<:actions>
<.button navigate={~p"/items"}>
<.icon name="hero-arrow-left" />
</.button>
<.button variant="primary" navigate={~p"/items/#{@item}/edit?return_to=show"}>
<.icon name="hero-pencil-square" /> Edit item
</.button>
</:actions>
</.header>
<.list>
<:item title="Name">{@item.name}</:item>
<:item title="Description">{@item.description}</:item>
<:item title="Info">{@item.info}</:item>
<:item title="Amount">{@item.amount}</:item>
<:item title="Factor">{@item.factor}</:item>
<:item title="Type">{@item.type}</:item>
</.list>
</Layouts.app>
"""
end
@impl true
def mount(%{"id" => id}, _session, socket) do
if connected?(socket) do
Items.subscribe_items(socket.assigns.current_scope)
end
{:ok,
socket
|> assign(:page_title, "Show Item")
|> assign(:item, Items.get_item!(socket.assigns.current_scope, id))}
end
@impl true
def handle_info(
{:updated, %GenericRestServer.Items.Item{id: id} = item},
%{assigns: %{item: %{id: id}}} = socket
) do
{:noreply, assign(socket, :item, item)}
end
def handle_info(
{:deleted, %GenericRestServer.Items.Item{id: id}},
%{assigns: %{item: %{id: id}}} = socket
) do
{:noreply,
socket
|> put_flash(:error, "The current item was deleted.")
|> push_navigate(to: ~p"/items")}
end
def handle_info({type, %GenericRestServer.Items.Item{}}, socket)
when type in [:created, :updated, :deleted] do
{:noreply, socket}
end
end

View File

@ -54,6 +54,11 @@ defmodule GenericRestServerWeb.Router do
on_mount: [{GenericRestServerWeb.UserAuth, :require_authenticated}] do
live "/users/settings", UserLive.Settings, :edit
live "/users/settings/confirm-email/:token", UserLive.Settings, :confirm_email
live "/items", ItemLive.Index, :index
live "/items/new", ItemLive.Form, :new
live "/items/:id", ItemLive.Show, :show
live "/items/:id/edit", ItemLive.Form, :edit
end
post "/users/update-password", UserSessionController, :update_password