chore: clear out page/post usage
This commit is contained in:
parent
4c3c5547fe
commit
e0de9ae8d9
33 changed files with 3 additions and 902 deletions
lib
core.ex
mix.exscore
schema.exschema
web
components
controllers
admin_auth.exadmin_session_controller.exglobals.expage_controller.expage_html.ex
page_html
post_controller.expost_html.expost_html
status_controller.exstatus_html.exstatus_html
live
router.expriv/repo/migrations
|
@ -1,4 +1,4 @@
|
|||
defmodule Core do
|
||||
@moduledoc false
|
||||
use Boundary, deps: [Schema], exports: [Author, Posts, Statuses]
|
||||
use Boundary, deps: [Schema], exports: []
|
||||
end
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
defmodule Core.Author do
|
||||
@moduledoc """
|
||||
Properties of the author, Sloane
|
||||
|
||||
Modeled after the [h-card] specification.
|
||||
|
||||
[h-card]: https://microformats.org/wiki/h-card
|
||||
"""
|
||||
use TypedStruct
|
||||
|
||||
@public_properties ~w[name nickname url]a
|
||||
|
||||
typedstruct do
|
||||
field :name, String.t()
|
||||
field :given_name, String.t()
|
||||
field :additional_name, String.t()
|
||||
field :family_name, String.t()
|
||||
field :nickname, String.t()
|
||||
field :email, String.t()
|
||||
field :url, String.t()
|
||||
end
|
||||
|
||||
def sloane do
|
||||
%__MODULE__{
|
||||
name: "Sloane Perrault",
|
||||
given_name: "Sloane",
|
||||
additional_name: "Loretta",
|
||||
family_name: "Perrault",
|
||||
nickname: "sloanely_but_surely",
|
||||
email: "sloane@fastmail.com",
|
||||
url: "https://sloanelybutsurely.com"
|
||||
}
|
||||
end
|
||||
|
||||
def full do
|
||||
sloane()
|
||||
end
|
||||
|
||||
def public do
|
||||
author = full()
|
||||
|
||||
for key <- @public_properties, reduce: %__MODULE__{} do
|
||||
acc -> Map.put(acc, key, Map.get(author, key))
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,37 +0,0 @@
|
|||
defmodule Core.Posts do
|
||||
@moduledoc false
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
alias Core.Repo
|
||||
|
||||
def changeset(%Schema.Post{} = post, attrs) do
|
||||
post
|
||||
|> cast(attrs, [:title, :body])
|
||||
|> validate_required([:body])
|
||||
end
|
||||
|
||||
def create_post(attrs) do
|
||||
%Schema.Post{}
|
||||
|> changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
def update_post(post, attrs) do
|
||||
post
|
||||
|> changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def get_post!(id) do
|
||||
Repo.get!(Schema.Post, id)
|
||||
end
|
||||
|
||||
def list_posts do
|
||||
query =
|
||||
from post in Schema.Post,
|
||||
order_by: [desc: post.inserted_at]
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
end
|
|
@ -1,37 +0,0 @@
|
|||
defmodule Core.Statuses do
|
||||
@moduledoc false
|
||||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
alias Core.Repo
|
||||
|
||||
def changeset(%Schema.Status{} = status, attrs \\ %{}) do
|
||||
status
|
||||
|> cast(attrs, [:body])
|
||||
|> validate_required([:body])
|
||||
end
|
||||
|
||||
def create_status(attrs) do
|
||||
%Schema.Status{}
|
||||
|> changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
def update_status(status, attrs) do
|
||||
status
|
||||
|> changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
def get_status!(id) do
|
||||
Repo.get!(Schema.Status, id)
|
||||
end
|
||||
|
||||
def list_statuses do
|
||||
query =
|
||||
from status in Schema.Status,
|
||||
order_by: [desc: status.inserted_at]
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
defmodule Schema do
|
||||
@moduledoc false
|
||||
use Boundary, deps: [], exports: [Post, Status]
|
||||
use Boundary, deps: [], exports: []
|
||||
end
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
defmodule Schema.Post do
|
||||
@moduledoc false
|
||||
use Ecto.Schema
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
schema "posts" do
|
||||
field :title, :string
|
||||
field :body, :string
|
||||
|
||||
timestamps()
|
||||
end
|
||||
end
|
|
@ -1,11 +0,0 @@
|
|||
defmodule Schema.Status do
|
||||
@moduledoc false
|
||||
use Ecto.Schema
|
||||
|
||||
@primary_key {:id, :binary_id, autogenerate: true}
|
||||
schema "statuses" do
|
||||
field :body
|
||||
|
||||
timestamps()
|
||||
end
|
||||
end
|
|
@ -3,147 +3,4 @@ defmodule Web.CoreComponents do
|
|||
Provides core UI components.
|
||||
"""
|
||||
use Phoenix.Component
|
||||
|
||||
attr :class, :string, default: nil
|
||||
attr :global, :global
|
||||
slot :inner_block
|
||||
|
||||
def title(assigns) do
|
||||
~H"""
|
||||
<h1 class={["font-bold text-xl mb-3", @class]} {@global}>{render_slot(@inner_block)}</h1>
|
||||
"""
|
||||
end
|
||||
|
||||
attr :class, :string, default: nil
|
||||
attr :global, :global
|
||||
slot :inner_block
|
||||
|
||||
def subtitle(assigns) do
|
||||
~H"""
|
||||
<h2 class={["font-bold text-lg mb-2", @class]} {@global}>{render_slot(@inner_block)}</h2>
|
||||
"""
|
||||
end
|
||||
|
||||
attr :class, :string, default: nil
|
||||
|
||||
attr :global, :global,
|
||||
include: ~w[navigate patch href replace method csrf_token download hreflang referrerpolicy rel target type]
|
||||
|
||||
slot :inner_block
|
||||
|
||||
def a(assigns) do
|
||||
~H"""
|
||||
<.link class={["hover:underline", @class]} {@global}>{render_slot(@inner_block)}</.link>
|
||||
"""
|
||||
end
|
||||
|
||||
attr :class, :string, default: nil
|
||||
attr :type, :string, default: "button"
|
||||
attr :global, :global
|
||||
slot :inner_block
|
||||
|
||||
def button(assigns) do
|
||||
~H"""
|
||||
<button type={@type} class={["hover:underline", @class]} {@global}>
|
||||
{render_slot(@inner_block)}
|
||||
</button>
|
||||
"""
|
||||
end
|
||||
|
||||
attr :class, :string, default: nil
|
||||
attr :field, Phoenix.HTML.FormField, required: true
|
||||
attr :type, :string, default: "text"
|
||||
attr :global, :global, include: ~w[required placeholder]
|
||||
|
||||
def input(%{type: "textarea"} = assigns) do
|
||||
~H"""
|
||||
<textarea
|
||||
class={["px-2 py-1 border border-gray-400 rounded", @class]}
|
||||
id={@field.id}
|
||||
name={@field.name}
|
||||
{@global}
|
||||
>{@field.value}</textarea>
|
||||
"""
|
||||
end
|
||||
|
||||
def input(assigns) do
|
||||
~H"""
|
||||
<input
|
||||
class={["px-2 py-1 border border-gray-400 rounded", @class]}
|
||||
type={@type}
|
||||
id={@field.id}
|
||||
name={@field.name}
|
||||
value={@field.value}
|
||||
{@global}
|
||||
/>
|
||||
"""
|
||||
end
|
||||
|
||||
@doc """
|
||||
Renders a [Heroicon](https://heroicons.com).
|
||||
|
||||
Heroicons come in three styles – outline, solid, and mini.
|
||||
By default, the outline style is used, but solid and mini may
|
||||
be applied by using the `-solid` and `-mini` suffix.
|
||||
|
||||
You can customize the size and colors of the icons by setting
|
||||
width, height, and background color classes.
|
||||
|
||||
Icons are extracted from the `deps/heroicons` directory and bundled within
|
||||
your compiled app.css by the plugin in your `assets/tailwind.config.js`.
|
||||
|
||||
## Examples
|
||||
|
||||
<.icon name="hero-x-mark-solid" />
|
||||
<.icon name="hero-arrow-path" class="ml-1 w-3 h-3 animate-spin" />
|
||||
"""
|
||||
attr :name, :string, required: true
|
||||
attr :class, :string, default: nil
|
||||
|
||||
def icon(%{name: "hero-" <> _} = assigns) do
|
||||
~H"""
|
||||
<span class={[@name, @class]} />
|
||||
"""
|
||||
end
|
||||
|
||||
attr :format, :string, required: true
|
||||
attr :value, :any, default: nil
|
||||
attr :formatter, :atom, default: :default
|
||||
attr :timezone, :string, default: "America/New_York"
|
||||
attr :global, :global
|
||||
|
||||
def timex(%{value: nil} = assigns) do
|
||||
~H"""
|
||||
<time datetime="">--</time>
|
||||
"""
|
||||
end
|
||||
|
||||
def timex(%{value: value, timezone: timezone} = assigns) do
|
||||
assigns =
|
||||
assign_new(assigns, :local_value, fn ->
|
||||
case value do
|
||||
%DateTime{} = datetime ->
|
||||
datetime
|
||||
|
||||
%NaiveDateTime{} = naive ->
|
||||
naive
|
||||
|> DateTime.from_naive!("Etc/UTC")
|
||||
|> DateTime.shift_zone!(timezone)
|
||||
end
|
||||
end)
|
||||
|
||||
~H"""
|
||||
<time
|
||||
datetime={Timex.format!(@local_value, "{ISO:Extended}")}
|
||||
title={Timex.format!(@local_value, "{Mshort} {D}, {YYYY}, {h12}:{m} {AM} {Zabbr}")}
|
||||
{@global}
|
||||
>
|
||||
{Timex.format!(@local_value, @format, timex_formatter(@formatter))}
|
||||
</time>
|
||||
"""
|
||||
end
|
||||
|
||||
defp timex_formatter(formatter) do
|
||||
Module.concat(Timex.Format.DateTime.Formatters, :string.titlecase("#{formatter}"))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,10 +10,4 @@ defmodule Web.Layouts do
|
|||
use Web, :html
|
||||
|
||||
embed_templates "layouts/*"
|
||||
|
||||
defp post?("/writing/" <> _), do: true
|
||||
defp post?(_), do: false
|
||||
|
||||
defp status?("/microblog/" <> _), do: true
|
||||
defp status?(_), do: false
|
||||
end
|
||||
|
|
|
@ -1,53 +1 @@
|
|||
<div class="sticky top-0 z-50 flex flex-row bg-white justify-between py-1 md:mb-2 border-b border-gray-200">
|
||||
<div class="flex flex-col md:flex-row">
|
||||
<section class="flex flex-row gap-x-2 md:border-r border-gray-200 px-2">
|
||||
<.a href={~p"/"} class="font-bold">sloanelybutsurely.com</.a>
|
||||
<nav>
|
||||
<ul class="flex flex-row gap-x-2">
|
||||
<li>
|
||||
<.a href={~p"/writing"}>writing</.a>
|
||||
</li>
|
||||
<li>
|
||||
<.a href={~p"/microblog"}>microblog</.a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</section>
|
||||
<section :if={@admin?} class="flex flex-row gap-x-2 px-2">
|
||||
<nav>
|
||||
<ul class="flex flex-row gap-x-2">
|
||||
<li>
|
||||
<.a navigate={~p"/admin"}>admin</.a>
|
||||
</li>
|
||||
<li>
|
||||
<.a href={~p"/admin/statuses/new"}>new status</.a>
|
||||
</li>
|
||||
<li>
|
||||
<.a href={~p"/admin/posts/new"}>new post</.a>
|
||||
</li>
|
||||
<li :if={post?(@current_path)}>
|
||||
<.a href={~p"/admin/posts/#{@post}"}>edit post</.a>
|
||||
</li>
|
||||
<li :if={status?(@current_path)}>
|
||||
<.a href={~p"/admin/statuses/#{@status}"}>edit status</.a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<.a :if={@admin?} class="px-2" href={~p"/admin/session/destroy?return_to=#{@current_path}"}>
|
||||
sign out
|
||||
</.a>
|
||||
<.a
|
||||
:if={!@admin?}
|
||||
class="px-2 text-transparent hover:text-current"
|
||||
href={~p"/sign-in?return_to=#{@current_path}"}
|
||||
>
|
||||
sign in
|
||||
</.a>
|
||||
</div>
|
||||
|
||||
<main class="p-2 max-w-2xl mx-auto">
|
||||
{@inner_content}
|
||||
</main>
|
||||
{@inner_content}
|
||||
|
|
|
@ -10,11 +10,6 @@
|
|||
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
||||
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
||||
</script>
|
||||
<%= if @load_trix? do %>
|
||||
<link rel="stylesheet" type="text/css" href="https://unpkg.com/trix@2.0.8/dist/trix.css" />
|
||||
<script type="text/javascript" src="https://unpkg.com/trix@2.0.8/dist/trix.umd.min.js">
|
||||
</script>
|
||||
<% end %>
|
||||
</head>
|
||||
<body class="bg-white">
|
||||
{@inner_content}
|
||||
|
|
|
@ -1,62 +0,0 @@
|
|||
defmodule Web.AdminAuth do
|
||||
@moduledoc false
|
||||
use Web, :verified_routes
|
||||
|
||||
import Phoenix.Controller
|
||||
import Plug.Conn
|
||||
|
||||
def log_in_admin(conn, params) do
|
||||
conn
|
||||
|> renew_session()
|
||||
|> put_session(:admin?, true)
|
||||
|> redirect(to: params["return_to"] || ~p"/admin")
|
||||
end
|
||||
|
||||
def log_out_admin(conn, params) do
|
||||
if live_socket_id = get_session(conn, :live_socket_id) do
|
||||
Web.Endpoint.broadcast(live_socket_id, "disconnect", %{})
|
||||
end
|
||||
|
||||
conn
|
||||
|> renew_session()
|
||||
|> redirect(to: params["return_to"] || ~p"/")
|
||||
end
|
||||
|
||||
def mount_admin(%Plug.Conn{} = conn, _opts) do
|
||||
assign(conn, :admin?, admin?(conn))
|
||||
end
|
||||
|
||||
def require_admin(%Plug.Conn{assigns: %{admin?: true}} = conn, _opts) do
|
||||
conn
|
||||
end
|
||||
|
||||
def require_admin(conn, _opts) do
|
||||
redirect(conn, to: ~p"/sign-in?return_to=#{conn.request_path}")
|
||||
end
|
||||
|
||||
def correct_password?(password) do
|
||||
password_hash = Application.fetch_env!(:sloanely_but_surely, :password_hash)
|
||||
|
||||
Argon2.verify_pass(password, password_hash)
|
||||
end
|
||||
|
||||
def on_mount(:default, _params, session, socket) do
|
||||
{:cont, Phoenix.Component.assign(socket, :admin?, admin?(session))}
|
||||
end
|
||||
|
||||
## private
|
||||
|
||||
defp renew_session(conn) do
|
||||
delete_csrf_token()
|
||||
|
||||
conn
|
||||
|> configure_session(renew: true)
|
||||
|> clear_session()
|
||||
end
|
||||
|
||||
defp admin?(%Plug.Conn{} = conn) do
|
||||
Plug.Conn.get_session(conn, :admin?, false) == true
|
||||
end
|
||||
|
||||
defp admin?(%{} = session), do: Map.get(session, "admin?", false) == true
|
||||
end
|
|
@ -1,21 +0,0 @@
|
|||
defmodule Web.AdminSessionController do
|
||||
use Web, :controller
|
||||
|
||||
alias Web.AdminAuth
|
||||
|
||||
def create(conn, %{"password" => password} = params) do
|
||||
if AdminAuth.correct_password?(password) do
|
||||
AdminAuth.log_in_admin(conn, params)
|
||||
else
|
||||
redirect(conn, to: ~p"/sign-in")
|
||||
end
|
||||
end
|
||||
|
||||
def create(conn, _params) do
|
||||
redirect(conn, to: ~p"/sign-in")
|
||||
end
|
||||
|
||||
def destroy(conn, params) do
|
||||
AdminAuth.log_out_admin(conn, params)
|
||||
end
|
||||
end
|
|
@ -1,22 +0,0 @@
|
|||
defmodule Web.Globals do
|
||||
@moduledoc false
|
||||
use Web, :live_view
|
||||
|
||||
def assign_globals(%Plug.Conn{} = conn, _opts) do
|
||||
conn
|
||||
|> Plug.Conn.assign(:current_path, conn.request_path)
|
||||
|> Plug.Conn.assign(:load_trix?, false)
|
||||
end
|
||||
|
||||
def on_mount(:default, _params, _session, socket) do
|
||||
socket =
|
||||
socket
|
||||
|> attach_hook(:assign_handle_params_globals, :handle_params, fn _params, uri, socket ->
|
||||
%URI{path: current_path} = URI.parse(uri)
|
||||
{:cont, assign(socket, :current_path, current_path)}
|
||||
end)
|
||||
|> assign(:load_trix?, false)
|
||||
|
||||
{:cont, socket}
|
||||
end
|
||||
end
|
|
@ -1,13 +0,0 @@
|
|||
defmodule Web.PageController do
|
||||
use Web, :controller
|
||||
|
||||
def home(conn, _params) do
|
||||
posts = Enum.take(Core.Posts.list_posts(), 5)
|
||||
statuses = Enum.take(Core.Statuses.list_statuses(), 10)
|
||||
|
||||
conn
|
||||
|> assign(:posts, posts)
|
||||
|> assign(:statuses, statuses)
|
||||
|> render(:home)
|
||||
end
|
||||
end
|
|
@ -1,10 +0,0 @@
|
|||
defmodule Web.PageHTML do
|
||||
@moduledoc """
|
||||
This module contains pages rendered by PageController.
|
||||
|
||||
See the `page_html` directory for all templates available.
|
||||
"""
|
||||
use Web, :html
|
||||
|
||||
embed_templates "page_html/*"
|
||||
end
|
|
@ -1,46 +0,0 @@
|
|||
<div class="flex flex-col gap-y-4">
|
||||
<section>
|
||||
<.title>
|
||||
<.a href={~p"/writing"}>writing</.a>
|
||||
</.title>
|
||||
<ul class="flex flex-col">
|
||||
<li :for={post <- @posts}>
|
||||
<.link href={~p"/writing/#{post}"} class="flex flex-row justify-between group">
|
||||
<span class="group-hover:underline">
|
||||
<%= if post.title do %>
|
||||
{post.title}
|
||||
<% else %>
|
||||
(no title)
|
||||
<% end %>
|
||||
</span>
|
||||
<.timex
|
||||
value={post.inserted_at}
|
||||
format="{YYYY}-{0M}-{0D}"
|
||||
class="text-gray-500 text-nowrap"
|
||||
/>
|
||||
</.link>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<.title>
|
||||
<.a href={~p"/microblog"}>microblog</.a>
|
||||
</.title>
|
||||
<ul class="flex flex-col">
|
||||
<li :for={status <- @statuses}>
|
||||
<.link href={~p"/microblog/#{status}"} class="flex flex-row justify-between group">
|
||||
<span class="group-hover:underline overflow-hidden text-ellipsis text-nowrap">
|
||||
{status.body}
|
||||
</span>
|
||||
<.timex
|
||||
value={status.inserted_at}
|
||||
format="{relative}"
|
||||
formatter={:relative}
|
||||
class="text-gray-500 text-nowrap"
|
||||
/>
|
||||
</.link>
|
||||
</li>
|
||||
</ul>
|
||||
</section>
|
||||
</div>
|
|
@ -1,19 +0,0 @@
|
|||
defmodule Web.PostController do
|
||||
use Web, :controller
|
||||
|
||||
def index(conn, _params) do
|
||||
posts = Core.Posts.list_posts()
|
||||
|
||||
conn
|
||||
|> assign(:posts, posts)
|
||||
|> render(:index)
|
||||
end
|
||||
|
||||
def show(conn, %{"post_id" => post_id}) do
|
||||
post = Core.Posts.get_post!(post_id)
|
||||
|
||||
conn
|
||||
|> assign(:post, post)
|
||||
|> render(:show)
|
||||
end
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
defmodule Web.PostHTML do
|
||||
@moduledoc false
|
||||
use Web, :html
|
||||
|
||||
embed_templates "post_html/*"
|
||||
end
|
|
@ -1,19 +0,0 @@
|
|||
<.title>writing</.title>
|
||||
<ul class="flex flex-col">
|
||||
<li :for={post <- @posts}>
|
||||
<.link href={~p"/writing/#{post}"} class="flex flex-row justify-between group">
|
||||
<span class="group-hover:underline">
|
||||
<%= if post.title do %>
|
||||
{post.title}
|
||||
<% else %>
|
||||
(no title)
|
||||
<% end %>
|
||||
</span>
|
||||
<.timex
|
||||
value={post.inserted_at}
|
||||
format="{YYYY}-{0M}-{0D}"
|
||||
class="text-gray-500 text-nowrap"
|
||||
/>
|
||||
</.link>
|
||||
</li>
|
||||
</ul>
|
|
@ -1,9 +0,0 @@
|
|||
<article>
|
||||
<header class="flex flex-row">
|
||||
<h1 :if={@post.title} class="font-bold text-xl mb-3">{@post.title}</h1>
|
||||
</header>
|
||||
<section class="prose prose-cms max-w-none">{raw(@post.body)}</section>
|
||||
<footer class="mt-5 py-1 border-t border-gray-200 relative">
|
||||
<.link :if={@admin?} class="absolute right-0" href={~p"/admin/posts/#{@post}"}>edit</.link>
|
||||
</footer>
|
||||
</article>
|
|
@ -1,19 +0,0 @@
|
|||
defmodule Web.StatusController do
|
||||
use Web, :controller
|
||||
|
||||
def index(conn, _params) do
|
||||
statuses = Core.Statuses.list_statuses()
|
||||
|
||||
conn
|
||||
|> assign(:statuses, statuses)
|
||||
|> render(:index)
|
||||
end
|
||||
|
||||
def show(conn, %{"status_id" => status_id}) do
|
||||
status = Core.Statuses.get_status!(status_id)
|
||||
|
||||
conn
|
||||
|> assign(:status, status)
|
||||
|> render(:show)
|
||||
end
|
||||
end
|
|
@ -1,6 +0,0 @@
|
|||
defmodule Web.StatusHTML do
|
||||
@moduledoc false
|
||||
use Web, :html
|
||||
|
||||
embed_templates "status_html/*"
|
||||
end
|
|
@ -1,16 +0,0 @@
|
|||
<.title>microblog</.title>
|
||||
<ul class="flex flex-col">
|
||||
<li :for={status <- @statuses}>
|
||||
<.link href={~p"/microblog/#{status}"} class="flex flex-row justify-between group">
|
||||
<span class="group-hover:underline overflow-hidden text-ellipsis text-nowrap">
|
||||
{status.body}
|
||||
</span>
|
||||
<.timex
|
||||
value={status.inserted_at}
|
||||
format="{relative}"
|
||||
formatter={:relative}
|
||||
class="text-gray-500 text-nowrap"
|
||||
/>
|
||||
</.link>
|
||||
</li>
|
||||
</ul>
|
|
@ -1,3 +0,0 @@
|
|||
<article>
|
||||
<p>{@status.body}</p>
|
||||
</article>
|
|
@ -1,16 +0,0 @@
|
|||
defmodule Web.AdminLive do
|
||||
@moduledoc false
|
||||
use Web, :live_view
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<h1>AdminLive</h1>
|
||||
"""
|
||||
end
|
||||
end
|
|
@ -1,39 +0,0 @@
|
|||
defmodule Web.AdminLoginLive do
|
||||
@moduledoc false
|
||||
use Web, :live_view
|
||||
|
||||
@impl true
|
||||
def mount(params, _session, socket) do
|
||||
socket =
|
||||
assign(
|
||||
socket,
|
||||
form: to_form(%{"password" => "", "return_to" => params["return_to"]}),
|
||||
return_to: params["return_to"]
|
||||
)
|
||||
|
||||
{:ok, socket, layout: false}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<main class="flex flex-col w-screen h-screen fixed justify-center items-center">
|
||||
<.form for={@form} action={~p"/admin/session/create"} class="flex flex-col gap-y-2">
|
||||
<.input type="hidden" field={@form[:return_to]} />
|
||||
<.input type="password" placeholder="password" field={@form[:password]} required />
|
||||
<div class="flex flex-col items-end">
|
||||
<button type="submit" class="font-bold hover:underline">sign in</button>
|
||||
<.a href={cancel_href(@return_to)}>
|
||||
cancel
|
||||
</.a>
|
||||
</div>
|
||||
</.form>
|
||||
</main>
|
||||
"""
|
||||
end
|
||||
|
||||
defp cancel_href("/admin"), do: ~p"/"
|
||||
defp cancel_href("/admin/" <> _), do: ~p"/"
|
||||
defp cancel_href(nil), do: ~p"/"
|
||||
defp cancel_href(return_to), do: return_to
|
||||
end
|
|
@ -1,76 +0,0 @@
|
|||
defmodule Web.PostLive do
|
||||
@moduledoc false
|
||||
use Web, :live_view
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
socket = assign(socket, :load_trix?, true)
|
||||
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(_params, _uri, %{assigns: %{live_action: :new}} = socket) do
|
||||
post = %Schema.Post{}
|
||||
changeset = Core.Posts.changeset(post, %{})
|
||||
|
||||
socket = assign(socket, post: post, form: to_form(changeset))
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_params(%{"post_id" => post_id}, _uri, %{assigns: %{live_action: :edit}} = socket) do
|
||||
post = Core.Posts.get_post!(post_id)
|
||||
|
||||
changeset = Core.Posts.changeset(post, %{})
|
||||
|
||||
socket = assign(socket, post: post, form: to_form(changeset))
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("save_post", %{"post" => attrs}, %{assigns: %{live_action: :new}} = socket) do
|
||||
socket =
|
||||
case Core.Posts.create_post(attrs) do
|
||||
{:ok, post} -> push_navigate(socket, to: ~p"/admin/posts/#{post}")
|
||||
{:error, changeset} -> assign(socket, form: to_form(changeset))
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("save_post", %{"post" => attrs}, %{assigns: %{post: post, live_action: :edit}} = socket) do
|
||||
socket =
|
||||
case Core.Posts.update_post(post, attrs) do
|
||||
{:ok, post} ->
|
||||
assign(socket,
|
||||
post: post,
|
||||
form:
|
||||
post
|
||||
|> Core.Posts.changeset(%{})
|
||||
|> to_form()
|
||||
)
|
||||
|
||||
{:error, changeset} ->
|
||||
assign(socket, form: to_form(changeset))
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<.form for={@form} class="flex flex-col gap-y-2" phx-submit="save_post">
|
||||
<.input type="hidden" field={@form[:body]} />
|
||||
<.input class="text-lg" field={@form[:title]} placeholder="Title" />
|
||||
<div id="editor" phx-update="ignore">
|
||||
<trix-editor input={@form[:body].id} class="prose prose-cms max-w-none"></trix-editor>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="self-end">save</button>
|
||||
</.form>
|
||||
"""
|
||||
end
|
||||
end
|
|
@ -1,70 +0,0 @@
|
|||
defmodule Web.StatusLive do
|
||||
@moduledoc false
|
||||
use Web, :live_view
|
||||
|
||||
@impl true
|
||||
def mount(_params, _session, socket) do
|
||||
{:ok, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_params(_params, _uri, %{assigns: %{live_action: :new}} = socket) do
|
||||
status = %Schema.Status{}
|
||||
|
||||
changeset = Core.Statuses.changeset(status, %{})
|
||||
|
||||
socket = assign(socket, status: status, form: to_form(changeset))
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_params(%{"status_id" => status_id}, _uri, %{assigns: %{live_action: :edit}} = socket) do
|
||||
status = Core.Statuses.get_status!(status_id)
|
||||
|
||||
changeset = Core.Statuses.changeset(status, %{})
|
||||
|
||||
socket = assign(socket, status: status, form: to_form(changeset))
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def handle_event("save_status", %{"status" => attrs}, %{assigns: %{live_action: :new}} = socket) do
|
||||
socket =
|
||||
case Core.Statuses.create_status(attrs) do
|
||||
{:ok, status} -> push_navigate(socket, to: ~p"/admin/statuses/#{status}")
|
||||
{:error, changeset} -> assign(socket, form: to_form(changeset))
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
def handle_event("save_status", %{"status" => attrs}, %{assigns: %{status: status, live_action: :edit}} = socket) do
|
||||
socket =
|
||||
case Core.Statuses.update_status(status, attrs) do
|
||||
{:ok, status} ->
|
||||
assign(socket,
|
||||
status: status,
|
||||
form:
|
||||
status
|
||||
|> Core.Statuses.changeset(%{})
|
||||
|> to_form()
|
||||
)
|
||||
|
||||
{:error, changeset} ->
|
||||
assign(socket, form: to_form(changeset))
|
||||
end
|
||||
|
||||
{:noreply, socket}
|
||||
end
|
||||
|
||||
@impl true
|
||||
def render(assigns) do
|
||||
~H"""
|
||||
<.form for={@form} class="flex flex-col gap-y-2" phx-submit="save_status">
|
||||
<.input type="textarea" field={@form[:body]} />
|
||||
<button type="submit" class="self-end">save</button>
|
||||
</.form>
|
||||
"""
|
||||
end
|
||||
end
|
|
@ -1,12 +1,6 @@
|
|||
defmodule Web.Router do
|
||||
use Web, :router
|
||||
|
||||
import Web.AdminAuth
|
||||
import Web.Globals
|
||||
|
||||
alias Web.AdminAuth
|
||||
alias Web.Globals
|
||||
|
||||
pipeline :browser do
|
||||
plug :accepts, ["html"]
|
||||
plug :fetch_session
|
||||
|
@ -14,47 +8,5 @@ defmodule Web.Router do
|
|||
plug :put_root_layout, html: {Web.Layouts, :root}
|
||||
plug :protect_from_forgery
|
||||
plug :put_secure_browser_headers
|
||||
plug :assign_globals
|
||||
end
|
||||
|
||||
pipeline :supports_admin_action do
|
||||
plug :mount_admin
|
||||
end
|
||||
|
||||
pipeline :requires_admin do
|
||||
plug :mount_admin
|
||||
plug :require_admin
|
||||
end
|
||||
|
||||
live_session :default, on_mount: [AdminAuth, Globals] do
|
||||
scope "/", Web do
|
||||
pipe_through :browser
|
||||
pipe_through :supports_admin_action
|
||||
|
||||
get "/", PageController, :home
|
||||
|
||||
get "/writing", PostController, :index
|
||||
get "/writing/:post_id", PostController, :show
|
||||
|
||||
get "/microblog", StatusController, :index
|
||||
get "/microblog/:status_id", StatusController, :show
|
||||
|
||||
live "/sign-in", AdminLoginLive
|
||||
post "/admin/session/create", AdminSessionController, :create
|
||||
get "/admin/session/destroy", AdminSessionController, :destroy
|
||||
end
|
||||
|
||||
scope "/admin", Web do
|
||||
pipe_through :browser
|
||||
pipe_through :requires_admin
|
||||
|
||||
live "/", AdminLive
|
||||
|
||||
live "/posts/new", PostLive, :new
|
||||
live "/posts/:post_id", PostLive, :edit
|
||||
|
||||
live "/statuses/new", StatusLive, :new
|
||||
live "/statuses/:status_id", StatusLive, :edit
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
3
mix.exs
3
mix.exs
|
@ -51,9 +51,6 @@ defmodule SlaonelyButSurely.MixProject do
|
|||
{:bandit, "~> 1.5"},
|
||||
|
||||
# Added dependencies
|
||||
{:argon2_elixir, "~> 4.1"},
|
||||
{:timex, "~> 3.7"},
|
||||
{:typed_struct, "~> 0.3.0"},
|
||||
{:boundary, "~> 0.10.4"},
|
||||
|
||||
# Added dev and/or test dependencies
|
||||
|
|
|
@ -1,13 +0,0 @@
|
|||
defmodule Core.Repo.Migrations.AddPostsTable do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:posts, primary_key: false) do
|
||||
add :id, :uuid, primary_key: true
|
||||
add :title, :text
|
||||
add :body, :text, null: false, default: ""
|
||||
|
||||
timestamps()
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,14 +0,0 @@
|
|||
defmodule Core.Repo.Migrations.AddStatusesTable do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create table(:statuses, primary_key: false) do
|
||||
add :id, :uuid, primary_key: true
|
||||
add :body, :text, null: false
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create index(:statuses, [:inserted_at])
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue