Add users
This commit is contained in:
parent
956f2625fd
commit
162aa095d3
15 changed files with 489 additions and 99 deletions
106
lib/nulla/users.ex
Normal file
106
lib/nulla/users.ex
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
defmodule Nulla.Users do
|
||||||
|
@moduledoc """
|
||||||
|
The Users context.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import Ecto.Query, warn: false
|
||||||
|
alias Nulla.Repo
|
||||||
|
|
||||||
|
alias Nulla.Users.User
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the list of users.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> list_users()
|
||||||
|
[%User{}, ...]
|
||||||
|
|
||||||
|
"""
|
||||||
|
def list_users do
|
||||||
|
Repo.all(User)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single user.
|
||||||
|
|
||||||
|
Raises `Ecto.NoResultsError` if the User does not exist.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> get_user!(123)
|
||||||
|
%User{}
|
||||||
|
|
||||||
|
iex> get_user!(456)
|
||||||
|
** (Ecto.NoResultsError)
|
||||||
|
|
||||||
|
"""
|
||||||
|
def get_user!(id), do: Repo.get!(User, id)
|
||||||
|
|
||||||
|
def get_user_by_username!(username), do: Repo.get_by!(User, username: username)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Creates a user.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> create_user(%{field: value})
|
||||||
|
{:ok, %User{}}
|
||||||
|
|
||||||
|
iex> create_user(%{field: bad_value})
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def create_user(attrs \\ %{}) do
|
||||||
|
%User{}
|
||||||
|
|> User.changeset(attrs)
|
||||||
|
|> Repo.insert()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Updates a user.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> update_user(user, %{field: new_value})
|
||||||
|
{:ok, %User{}}
|
||||||
|
|
||||||
|
iex> update_user(user, %{field: bad_value})
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def update_user(%User{} = user, attrs) do
|
||||||
|
user
|
||||||
|
|> User.changeset(attrs)
|
||||||
|
|> Repo.update()
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Deletes a user.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> delete_user(user)
|
||||||
|
{:ok, %User{}}
|
||||||
|
|
||||||
|
iex> delete_user(user)
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def delete_user(%User{} = user) do
|
||||||
|
Repo.delete(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns an `%Ecto.Changeset{}` for tracking user changes.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> change_user(user)
|
||||||
|
%Ecto.Changeset{data: %User{}}
|
||||||
|
|
||||||
|
"""
|
||||||
|
def change_user(%User{} = user, attrs \\ %{}) do
|
||||||
|
User.changeset(user, attrs)
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,4 +1,4 @@
|
||||||
defmodule Nulla.User do
|
defmodule Nulla.Users.User do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ defmodule Nulla.User do
|
||||||
field :location, :string
|
field :location, :string
|
||||||
field :birthday, :date
|
field :birthday, :date
|
||||||
field :fields, :map
|
field :fields, :map
|
||||||
|
field :tags, {:array, :string}
|
||||||
field :follow_approval, :boolean, default: false
|
field :follow_approval, :boolean, default: false
|
||||||
field :is_bot, :boolean, default: false
|
field :is_bot, :boolean, default: false
|
||||||
field :is_discoverable, :boolean, default: true
|
field :is_discoverable, :boolean, default: true
|
||||||
|
@ -20,7 +21,7 @@ defmodule Nulla.User do
|
||||||
field :is_memorial, :boolean, default: false
|
field :is_memorial, :boolean, default: false
|
||||||
field :private_key, :string
|
field :private_key, :string
|
||||||
field :public_key, :string
|
field :public_key, :string
|
||||||
field :avater, :string
|
field :avatar, :string
|
||||||
field :banner, :string
|
field :banner, :string
|
||||||
|
|
||||||
timestamps(type: :utc_datetime)
|
timestamps(type: :utc_datetime)
|
||||||
|
@ -29,7 +30,7 @@ defmodule Nulla.User do
|
||||||
@doc false
|
@doc false
|
||||||
def changeset(user, attrs) do
|
def changeset(user, attrs) do
|
||||||
user
|
user
|
||||||
|> cast(attrs, [:username, :email, :password, :is_moderator, :realname, :bio, :location, :birthday, :fields, :follow_approval, :is_bot, :is_discoverable, :is_indexable, :is_memorial, :private_key, :public_key, :avater, :banner])
|
|> cast(attrs, [:username, :email, :password, :is_moderator, :realname, :bio, :location, :birthday, :fields, :follow_approval, :is_bot, :is_discoverable, :is_indexable, :is_memorial, :private_key, :public_key, :avatar, :banner])
|
||||||
|> validate_required([:username, :email, :password, :is_moderator, :realname, :bio, :location, :birthday, :fields, :follow_approval, :is_bot, :is_discoverable, :is_indexable, :is_memorial, :private_key, :public_key, :avater, :banner])
|
|> validate_required([:username, :email, :password, :is_moderator, :realname, :bio, :location, :birthday, :fields, :follow_approval, :is_bot, :is_discoverable, :is_indexable, :is_memorial, :private_key, :public_key, :avatar, :banner])
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -1,64 +1,21 @@
|
||||||
defmodule NullaWeb.UserController do
|
defmodule NullaWeb.UserController do
|
||||||
use NullaWeb, :controller
|
use NullaWeb, :controller
|
||||||
|
alias Nulla.Users
|
||||||
|
alias Nulla.InstanceSettings
|
||||||
|
alias Nulla.ActivityPub
|
||||||
|
|
||||||
def show(conn, %{"username" => username}) do
|
def show(conn, %{"username" => username}) do
|
||||||
accept = get_req_header(conn, "accept") |> List.first() || ""
|
accept = List.first(get_req_header(conn, "accept"))
|
||||||
|
instance_settings = InstanceSettings.get_instance_settings!()
|
||||||
|
domain = instance_settings.domain
|
||||||
|
user = Users.get_user_by_username!(username)
|
||||||
|
|
||||||
if String.contains?(accept, "application/activity+json") or String.contains?(accept, "application/ld+json") do
|
if accept in ["application/activity+json", "application/ld+json"] do
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/activity+json")
|
|> put_resp_content_type("application/activity+json")
|
||||||
|> json(%{
|
|> json(ActivityPub.ap_user(domain, user))
|
||||||
id: "https://localhost/@#{username}",
|
|
||||||
type: "Person",
|
|
||||||
following: "https://localhost/@#{username}/following",
|
|
||||||
followers: "https://localhost/@#{username}/followers",
|
|
||||||
inbox: "https://localhost/@#{username}/inbox",
|
|
||||||
outbox: "https://localhost/@#{username}/outbox",
|
|
||||||
featured: "https://localhost/@#{username}/collections/featured",
|
|
||||||
preferredUsername: "miraikumiko",
|
|
||||||
name: "Mirai Kumiko",
|
|
||||||
summary: "Lol Kek Cheburek",
|
|
||||||
url: "https://localhost/@#{username}",
|
|
||||||
manuallyApprovesFollowers: false,
|
|
||||||
discoverable: true,
|
|
||||||
indexable: true,
|
|
||||||
published: "2025-05-05T00:00:00Z",
|
|
||||||
memorial: false,
|
|
||||||
publicKey: %{
|
|
||||||
id: "https://localhost/@#{username}#main-key",
|
|
||||||
owner: "https://localhost/@#{username}",
|
|
||||||
publicKeyPem: "public key"
|
|
||||||
},
|
|
||||||
tag: [
|
|
||||||
%{
|
|
||||||
type: "Hashtag",
|
|
||||||
href: "https://localhost/tags/linux",
|
|
||||||
name: "#linux"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
attachment: [
|
|
||||||
%{
|
|
||||||
type: "PropertyValue",
|
|
||||||
name: "Website",
|
|
||||||
value: "<a href=\"https://miraikumiko.com/\" rel=\"me nofollow noopener\" target=\"_blank\">https://miraikumiko.com</a>"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
endpoints: %{
|
|
||||||
sharedInbox: "https://localhost/inbox"
|
|
||||||
},
|
|
||||||
icon: %{
|
|
||||||
type: "Image",
|
|
||||||
mediaType: "image/jpeg",
|
|
||||||
url: "url"
|
|
||||||
},
|
|
||||||
image: %{
|
|
||||||
type: "Image",
|
|
||||||
mediaType: "image/jpeg",
|
|
||||||
url: "url"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
else
|
else
|
||||||
render(conn, :show, username: username, layout: false)
|
render(conn, :show, user: user, domain: domain, layout: false)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
62
lib/nulla_web/controllers/user_controller.ex.bak
Normal file
62
lib/nulla_web/controllers/user_controller.ex.bak
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
defmodule NullaWeb.UserController do
|
||||||
|
use NullaWeb, :controller
|
||||||
|
|
||||||
|
alias Nulla.Users
|
||||||
|
alias Nulla.Users.User
|
||||||
|
|
||||||
|
def index(conn, _params) do
|
||||||
|
users = Users.list_users()
|
||||||
|
render(conn, :index, users: users)
|
||||||
|
end
|
||||||
|
|
||||||
|
def new(conn, _params) do
|
||||||
|
changeset = Users.change_user(%User{})
|
||||||
|
render(conn, :new, changeset: changeset)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create(conn, %{"user" => user_params}) do
|
||||||
|
case Users.create_user(user_params) do
|
||||||
|
{:ok, user} ->
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "User created successfully.")
|
||||||
|
|> redirect(to: ~p"/users/#{user}")
|
||||||
|
|
||||||
|
{:error, %Ecto.Changeset{} = changeset} ->
|
||||||
|
render(conn, :new, changeset: changeset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def show(conn, %{"id" => id}) do
|
||||||
|
user = Users.get_user!(id)
|
||||||
|
render(conn, :show, user: user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit(conn, %{"id" => id}) do
|
||||||
|
user = Users.get_user!(id)
|
||||||
|
changeset = Users.change_user(user)
|
||||||
|
render(conn, :edit, user: user, changeset: changeset)
|
||||||
|
end
|
||||||
|
|
||||||
|
def update(conn, %{"id" => id, "user" => user_params}) do
|
||||||
|
user = Users.get_user!(id)
|
||||||
|
|
||||||
|
case Users.update_user(user, user_params) do
|
||||||
|
{:ok, user} ->
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "User updated successfully.")
|
||||||
|
|> redirect(to: ~p"/users/#{user}")
|
||||||
|
|
||||||
|
{:error, %Ecto.Changeset{} = changeset} ->
|
||||||
|
render(conn, :edit, user: user, changeset: changeset)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def delete(conn, %{"id" => id}) do
|
||||||
|
user = Users.get_user!(id)
|
||||||
|
{:ok, _user} = Users.delete_user(user)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_flash(:info, "User deleted successfully.")
|
||||||
|
|> redirect(to: ~p"/users")
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,10 +1,41 @@
|
||||||
defmodule NullaWeb.UserHTML do
|
defmodule NullaWeb.UserHTML do
|
||||||
@moduledoc """
|
|
||||||
This module contains pages rendered by UserController.
|
|
||||||
|
|
||||||
See the `user_html` directory for all templates available.
|
|
||||||
"""
|
|
||||||
use NullaWeb, :html
|
use NullaWeb, :html
|
||||||
|
|
||||||
embed_templates "user_html/*"
|
embed_templates "user_html/*"
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Renders a user form.
|
||||||
|
"""
|
||||||
|
attr :changeset, Ecto.Changeset, required: true
|
||||||
|
attr :action, :string, required: true
|
||||||
|
|
||||||
|
def user_form(assigns)
|
||||||
|
|
||||||
|
def format_birthdate(date) do
|
||||||
|
formatted = Date.to_string(date) |> String.replace("-", "/")
|
||||||
|
age = Timex.diff(Timex.today(), date, :years)
|
||||||
|
"#{formatted} (#{age} years old)"
|
||||||
|
end
|
||||||
|
|
||||||
|
def format_registration_date(date) do
|
||||||
|
now = Timex.now()
|
||||||
|
formatted = Date.to_string(date) |> String.replace("-", "/")
|
||||||
|
|
||||||
|
diff = Timex.diff(now, date, :days)
|
||||||
|
|
||||||
|
relative =
|
||||||
|
cond do
|
||||||
|
diff == 0 -> "today"
|
||||||
|
diff == 1 -> "1 day ago"
|
||||||
|
diff < 30 -> "#{diff} days ago"
|
||||||
|
diff < 365 ->
|
||||||
|
months = Timex.diff(now, date, :months)
|
||||||
|
if months == 1, do: "1 month ago", else: "#{months} months ago"
|
||||||
|
true ->
|
||||||
|
years = Timex.diff(now, date, :years)
|
||||||
|
if years == 1, do: "1 year ago", else: "#{years} years ago"
|
||||||
|
end
|
||||||
|
|
||||||
|
"#{formatted} (#{relative})"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
8
lib/nulla_web/controllers/user_html/edit.html.heex
Normal file
8
lib/nulla_web/controllers/user_html/edit.html.heex
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<.header>
|
||||||
|
Edit User {@user.id}
|
||||||
|
<:subtitle>Use this form to manage user records in your database.</:subtitle>
|
||||||
|
</.header>
|
||||||
|
|
||||||
|
<.user_form changeset={@changeset} action={~p"/users/#{@user}"} />
|
||||||
|
|
||||||
|
<.back navigate={~p"/users"}>Back to users</.back>
|
23
lib/nulla_web/controllers/user_html/index.html.heex
Normal file
23
lib/nulla_web/controllers/user_html/index.html.heex
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
<.header>
|
||||||
|
Listing Users
|
||||||
|
<:actions>
|
||||||
|
<.link href={~p"/users/new"}>
|
||||||
|
<.button>New User</.button>
|
||||||
|
</.link>
|
||||||
|
</:actions>
|
||||||
|
</.header>
|
||||||
|
|
||||||
|
<.table id="users" rows={@users} row_click={&JS.navigate(~p"/users/#{&1}")}>
|
||||||
|
<:col :let={user} label="Username">{user.username}</:col>
|
||||||
|
<:action :let={user}>
|
||||||
|
<div class="sr-only">
|
||||||
|
<.link navigate={~p"/users/#{user}"}>Show</.link>
|
||||||
|
</div>
|
||||||
|
<.link navigate={~p"/users/#{user}/edit"}>Edit</.link>
|
||||||
|
</:action>
|
||||||
|
<:action :let={user}>
|
||||||
|
<.link href={~p"/users/#{user}"} method="delete" data-confirm="Are you sure?">
|
||||||
|
Delete
|
||||||
|
</.link>
|
||||||
|
</:action>
|
||||||
|
</.table>
|
8
lib/nulla_web/controllers/user_html/new.html.heex
Normal file
8
lib/nulla_web/controllers/user_html/new.html.heex
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<.header>
|
||||||
|
New User
|
||||||
|
<:subtitle>Use this form to manage user records in your database.</:subtitle>
|
||||||
|
</.header>
|
||||||
|
|
||||||
|
<.user_form changeset={@changeset} action={~p"/users"} />
|
||||||
|
|
||||||
|
<.back navigate={~p"/users"}>Back to users</.back>
|
|
@ -14,50 +14,56 @@
|
||||||
<img src={~p"/images/avatar.jpg"} class="absolute left-4 bottom-0 translate-y-1/2 rounded-full border-4 border-white w-[8.33vw] h-[8.33vw] min-w-[80px] min-h-[80px] max-w-[160px] max-h-[160px]"/>
|
<img src={~p"/images/avatar.jpg"} class="absolute left-4 bottom-0 translate-y-1/2 rounded-full border-4 border-white w-[8.33vw] h-[8.33vw] min-w-[80px] min-h-[80px] max-w-[160px] max-h-[160px]"/>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-[4.5vw] px-4 flex flex-col">
|
<div class="mt-[4.5vw] px-4 flex flex-col">
|
||||||
<span class="text-xl font-bold">Mirai Kumiko</span>
|
<span class="text-xl font-bold">{@user.realname}</span>
|
||||||
<span class="text-gray-500">@miraikumiko@nulla.social</span>
|
<span class="text-gray-500">@{@user.username}@{@domain}</span>
|
||||||
<div class="text-sm pt-2">
|
<div class="text-sm pt-2">
|
||||||
<p>Cryptopunk in the past.</p>
|
<p>{@user.bio}</p>
|
||||||
<p>Silent girl now and admin of this instance.</p>
|
|
||||||
<br>
|
|
||||||
<p>Grew up on hacker culture, philosophy, good old movies and anime. That's why I love cyberpunk — modern philosophy and technolization in one bottle. I also use Linux on a first-name basis and can program.</p>
|
|
||||||
<br>
|
|
||||||
<p>Can play shooters, chess and other games where strategy and psychological analysis of opponents are important.</p>
|
|
||||||
<br>
|
|
||||||
<p>Bunnies and rabbits are superior!</p>
|
|
||||||
</div>
|
</div>
|
||||||
<dl class="mt-2 space-y-1 text-sm text-gray-700">
|
<dl class="mt-2 text-sm text-gray-700 grid grid-cols-[auto,1fr] gap-x-2 gap-y-1 items-center">
|
||||||
<div class="flex items-center gap-2">
|
<%= if @user.location do %>
|
||||||
<dt><.icon name="hero-map-pin" class="mt-0.5 h-5 w-5 flex-none" /></dt>
|
<dt class="flex items-center gap-2">
|
||||||
<dd>Catalonia, Spain</dd>
|
<.icon name="hero-map-pin" class="mt-0.5 h-5 w-5 flex-none" />
|
||||||
</div>
|
</dt>
|
||||||
<div class="flex items-center gap-2">
|
<dd><%= @user.location %></dd>
|
||||||
<dt><.icon name="hero-cake" class="mt-0.5 h-5 w-5 flex-none" /></dt>
|
<% end %>
|
||||||
<dd>2005/02/25 (20 years old)</dd>
|
|
||||||
</div>
|
<%= if @user.birthday do %>
|
||||||
<div class="flex items-center gap-2">
|
<dt class="flex items-center gap-2">
|
||||||
<dt><.icon name="hero-calendar" class="mt-0.5 h-5 w-5 flex-none" /></dt>
|
<.icon name="hero-cake" class="mt-0.5 h-5 w-5 flex-none" />
|
||||||
<dd>03/20/2025 (2mo ago)</dd>
|
</dt>
|
||||||
</div>
|
<dd><%= format_birthdate(@user.birthday) %></dd>
|
||||||
</dl>
|
<% end %>
|
||||||
<dl class="pt-5">
|
|
||||||
<div class="flex items-center gap-5">
|
<dt class="flex items-center gap-2">
|
||||||
<dt>Website</dt>
|
<.icon name="hero-calendar" class="mt-0.5 h-5 w-5 flex-none" />
|
||||||
<dd>
|
</dt>
|
||||||
<a href="https://miraikumiko.com" class="text-[#1D9BF0]">miraikumiko.com</a>
|
<dd><%= format_registration_date(@user.inserted_at) %></dd>
|
||||||
</dd>
|
|
||||||
</div>
|
|
||||||
</dl>
|
</dl>
|
||||||
|
<%= if @user.fields do %>
|
||||||
|
<dl class="mt-5 grid grid-cols-[max-content,1fr] gap-x-5 gap-y-2 items-center">
|
||||||
|
<%= for {key, value} <- @user.fields do %>
|
||||||
|
<dt><%= key %></dt>
|
||||||
|
<dd>
|
||||||
|
<%= if Regex.match?(~r{://}, value) do %>
|
||||||
|
<a href={value} class="text-[#1D9BF0]"><%= Regex.replace(~r{^\w+://}, value, "") %></a>
|
||||||
|
<% else %>
|
||||||
|
<%= value %>
|
||||||
|
<% end %>
|
||||||
|
</dd>
|
||||||
|
<% end %>
|
||||||
|
</dl>
|
||||||
|
<% end %>
|
||||||
<div class="flex mt-5 gap-3">
|
<div class="flex mt-5 gap-3">
|
||||||
<a href="/@miraikumiko">1.7K <span class="text-gray-700">Posts</span></a>
|
<a href={~p"/@#{@user.username}"}>1.7K Posts</a>
|
||||||
<a href="/@miraikumiko/following">33 <span class="text-gray-700">Following</span></a>
|
<a href={~p"/@#{@user.username}/following"}>33 Following</a>
|
||||||
<a href="/@miraikumiko/followers">31 <span class="text-gray-700">Followers</span></a>
|
<a href={~p"/@#{@user.username}/followers"}>31 Followers</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between px-20 py-2 mt-5 border border-gray-300">
|
<div class="flex justify-between px-20 py-2 mt-5 border border-gray-300">
|
||||||
<div>Posts</div>
|
<a href={~p"/@#{@user.username}/featured"}>Featured</a>
|
||||||
<div>Posts and replies</div>
|
<a href={~p"/@#{@user.username}"}>Posts</a>
|
||||||
<div>Media</div>
|
<a href={~p"/@#{@user.username}/with_replies"}>Posts and replies</a>
|
||||||
|
<a href={~p"/@#{@user.username}/media"}>Media</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col items-center mt-5 gap-5">
|
<div class="flex flex-col items-center mt-5 gap-5">
|
||||||
|
|
15
lib/nulla_web/controllers/user_html/show.html.heex.bak
Normal file
15
lib/nulla_web/controllers/user_html/show.html.heex.bak
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<.header>
|
||||||
|
User {@user.id}
|
||||||
|
<:subtitle>This is a user record from your database.</:subtitle>
|
||||||
|
<:actions>
|
||||||
|
<.link href={~p"/users/#{@user}/edit"}>
|
||||||
|
<.button>Edit user</.button>
|
||||||
|
</.link>
|
||||||
|
</:actions>
|
||||||
|
</.header>
|
||||||
|
|
||||||
|
<.list>
|
||||||
|
<:item title="Username">{@user.username}</:item>
|
||||||
|
</.list>
|
||||||
|
|
||||||
|
<.back navigate={~p"/users"}>Back to users</.back>
|
9
lib/nulla_web/controllers/user_html/user_form.html.heex
Normal file
9
lib/nulla_web/controllers/user_html/user_form.html.heex
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
<.simple_form :let={f} for={@changeset} action={@action}>
|
||||||
|
<.error :if={@changeset.action}>
|
||||||
|
Oops, something went wrong! Please check the errors below.
|
||||||
|
</.error>
|
||||||
|
<.input field={f[:username]} type="text" label="Username" />
|
||||||
|
<:actions>
|
||||||
|
<.button>Save User</.button>
|
||||||
|
</:actions>
|
||||||
|
</.simple_form>
|
|
@ -19,6 +19,7 @@ defmodule NullaWeb.Router do
|
||||||
|
|
||||||
get "/", PageController, :home
|
get "/", PageController, :home
|
||||||
get "/@:username", UserController, :show
|
get "/@:username", UserController, :show
|
||||||
|
resources "/users", UserController
|
||||||
end
|
end
|
||||||
|
|
||||||
# Other scopes may use custom stacks.
|
# Other scopes may use custom stacks.
|
||||||
|
|
59
test/nulla/users_test.exs
Normal file
59
test/nulla/users_test.exs
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
defmodule Nulla.UsersTest do
|
||||||
|
use Nulla.DataCase
|
||||||
|
|
||||||
|
alias Nulla.Users
|
||||||
|
|
||||||
|
describe "users" do
|
||||||
|
alias Nulla.Users.User
|
||||||
|
|
||||||
|
import Nulla.UsersFixtures
|
||||||
|
|
||||||
|
@invalid_attrs %{username: nil}
|
||||||
|
|
||||||
|
test "list_users/0 returns all users" do
|
||||||
|
user = user_fixture()
|
||||||
|
assert Users.list_users() == [user]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "get_user!/1 returns the user with given id" do
|
||||||
|
user = user_fixture()
|
||||||
|
assert Users.get_user!(user.id) == user
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_user/1 with valid data creates a user" do
|
||||||
|
valid_attrs = %{username: "some username"}
|
||||||
|
|
||||||
|
assert {:ok, %User{} = user} = Users.create_user(valid_attrs)
|
||||||
|
assert user.username == "some username"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "create_user/1 with invalid data returns error changeset" do
|
||||||
|
assert {:error, %Ecto.Changeset{}} = Users.create_user(@invalid_attrs)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_user/2 with valid data updates the user" do
|
||||||
|
user = user_fixture()
|
||||||
|
update_attrs = %{username: "some updated username"}
|
||||||
|
|
||||||
|
assert {:ok, %User{} = user} = Users.update_user(user, update_attrs)
|
||||||
|
assert user.username == "some updated username"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "update_user/2 with invalid data returns error changeset" do
|
||||||
|
user = user_fixture()
|
||||||
|
assert {:error, %Ecto.Changeset{}} = Users.update_user(user, @invalid_attrs)
|
||||||
|
assert user == Users.get_user!(user.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delete_user/1 deletes the user" do
|
||||||
|
user = user_fixture()
|
||||||
|
assert {:ok, %User{}} = Users.delete_user(user)
|
||||||
|
assert_raise Ecto.NoResultsError, fn -> Users.get_user!(user.id) end
|
||||||
|
end
|
||||||
|
|
||||||
|
test "change_user/1 returns a user changeset" do
|
||||||
|
user = user_fixture()
|
||||||
|
assert %Ecto.Changeset{} = Users.change_user(user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
84
test/nulla_web/controllers/user_controller_test.exs
Normal file
84
test/nulla_web/controllers/user_controller_test.exs
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
defmodule NullaWeb.UserControllerTest do
|
||||||
|
use NullaWeb.ConnCase
|
||||||
|
|
||||||
|
import Nulla.UsersFixtures
|
||||||
|
|
||||||
|
@create_attrs %{username: "some username"}
|
||||||
|
@update_attrs %{username: "some updated username"}
|
||||||
|
@invalid_attrs %{username: nil}
|
||||||
|
|
||||||
|
describe "index" do
|
||||||
|
test "lists all users", %{conn: conn} do
|
||||||
|
conn = get(conn, ~p"/users")
|
||||||
|
assert html_response(conn, 200) =~ "Listing Users"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "new user" do
|
||||||
|
test "renders form", %{conn: conn} do
|
||||||
|
conn = get(conn, ~p"/users/new")
|
||||||
|
assert html_response(conn, 200) =~ "New User"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "create user" do
|
||||||
|
test "redirects to show when data is valid", %{conn: conn} do
|
||||||
|
conn = post(conn, ~p"/users", user: @create_attrs)
|
||||||
|
|
||||||
|
assert %{id: id} = redirected_params(conn)
|
||||||
|
assert redirected_to(conn) == ~p"/users/#{id}"
|
||||||
|
|
||||||
|
conn = get(conn, ~p"/users/#{id}")
|
||||||
|
assert html_response(conn, 200) =~ "User #{id}"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders errors when data is invalid", %{conn: conn} do
|
||||||
|
conn = post(conn, ~p"/users", user: @invalid_attrs)
|
||||||
|
assert html_response(conn, 200) =~ "New User"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "edit user" do
|
||||||
|
setup [:create_user]
|
||||||
|
|
||||||
|
test "renders form for editing chosen user", %{conn: conn, user: user} do
|
||||||
|
conn = get(conn, ~p"/users/#{user}/edit")
|
||||||
|
assert html_response(conn, 200) =~ "Edit User"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "update user" do
|
||||||
|
setup [:create_user]
|
||||||
|
|
||||||
|
test "redirects when data is valid", %{conn: conn, user: user} do
|
||||||
|
conn = put(conn, ~p"/users/#{user}", user: @update_attrs)
|
||||||
|
assert redirected_to(conn) == ~p"/users/#{user}"
|
||||||
|
|
||||||
|
conn = get(conn, ~p"/users/#{user}")
|
||||||
|
assert html_response(conn, 200) =~ "some updated username"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "renders errors when data is invalid", %{conn: conn, user: user} do
|
||||||
|
conn = put(conn, ~p"/users/#{user}", user: @invalid_attrs)
|
||||||
|
assert html_response(conn, 200) =~ "Edit User"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "delete user" do
|
||||||
|
setup [:create_user]
|
||||||
|
|
||||||
|
test "deletes chosen user", %{conn: conn, user: user} do
|
||||||
|
conn = delete(conn, ~p"/users/#{user}")
|
||||||
|
assert redirected_to(conn) == ~p"/users"
|
||||||
|
|
||||||
|
assert_error_sent 404, fn ->
|
||||||
|
get(conn, ~p"/users/#{user}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp create_user(_) do
|
||||||
|
user = user_fixture()
|
||||||
|
%{user: user}
|
||||||
|
end
|
||||||
|
end
|
20
test/support/fixtures/users_fixtures.ex
Normal file
20
test/support/fixtures/users_fixtures.ex
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
defmodule Nulla.UsersFixtures do
|
||||||
|
@moduledoc """
|
||||||
|
This module defines test helpers for creating
|
||||||
|
entities via the `Nulla.Users` context.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Generate a user.
|
||||||
|
"""
|
||||||
|
def user_fixture(attrs \\ %{}) do
|
||||||
|
{:ok, user} =
|
||||||
|
attrs
|
||||||
|
|> Enum.into(%{
|
||||||
|
username: "some username"
|
||||||
|
})
|
||||||
|
|> Nulla.Users.create_user()
|
||||||
|
|
||||||
|
user
|
||||||
|
end
|
||||||
|
end
|
Loading…
Add table
Add a link
Reference in a new issue