diff --git a/lib/cms/posts.ex b/lib/cms/posts.ex new file mode 100644 index 0000000..8bcf248 --- /dev/null +++ b/lib/cms/posts.ex @@ -0,0 +1,25 @@ +defmodule CMS.Posts do + @moduledoc false + alias CMS.Posts.Post + alias CMS.Repo + + def create_post(attrs) do + %Post{} + |> Post.changeset(attrs) + |> Repo.insert() + end + + def update_post(post, attrs) do + post + |> Post.changeset(attrs) + |> Repo.update() + end + + def get_post!(id) do + Repo.get!(Post, id) + end + + def list_posts do + Repo.all(Post) + end +end diff --git a/lib/cms/posts/post.ex b/lib/cms/posts/post.ex new file mode 100644 index 0000000..3918e97 --- /dev/null +++ b/lib/cms/posts/post.ex @@ -0,0 +1,20 @@ +defmodule CMS.Posts.Post do + @moduledoc false + use Ecto.Schema + + import Ecto.Changeset, warn: false + + @primary_key {:id, :binary_id, autogenerate: true} + schema "posts" do + field :title, :string + field :contents, :string + + timestamps() + end + + def changeset(%__MODULE__{} = post, attrs \\ %{}) do + post + |> cast(attrs, [:title, :contents]) + |> validate_required([:contents]) + end +end diff --git a/lib/cms_web/components/layouts/app.html.heex b/lib/cms_web/components/layouts/app.html.heex index fbe0799..223cdd9 100644 --- a/lib/cms_web/components/layouts/app.html.heex +++ b/lib/cms_web/components/layouts/app.html.heex @@ -1,11 +1,14 @@ -<div :if={@admin?} class="flex flex-row justify-between py-1 px-3 mb-2 border-b border-slate-100"> +<div + :if={@admin?} + class="flex flex-row justify-between py-1 px-3 md:mb-2 border-b border-slate-100" +> <section class="flex flex-row gap-x-2"> <div class="pr-2 border-r border-slate-100"> <.link navigate={~p"/admin"} class="font-bold">admin mode</.link> </div> <nav> <ul class="flex flex-row"> - <.link href="#" class="hover:underline">new post</.link> + <.link href={~p"/admin/posts/new"} class="hover:underline">new post</.link> </ul> </nav> </section> @@ -21,12 +24,11 @@ <.link href={~p"/"} class="font-bold hover:underline">sloanelybutsurely.com</.link> <nav> <ul> - <li><.link href={~p"/writing"} class="hover:underline">writing</.link></li> - <li><.link href={~p"/microblog"} class="hover:underline">microblog</.link></li> + <li><.link href={~p"/posts"} class="hover:underline">writing</.link></li> </ul> </nav> </section> - <main class="p-2"> + <main class="p-2 w-full"> {@inner_content} </main> </div> diff --git a/lib/cms_web/controllers/post_controller.ex b/lib/cms_web/controllers/post_controller.ex new file mode 100644 index 0000000..22b3b62 --- /dev/null +++ b/lib/cms_web/controllers/post_controller.ex @@ -0,0 +1,21 @@ +defmodule CMSWeb.PostController do + use CMSWeb, :controller + + alias CMS.Posts + + def index(conn, _params) do + posts = Posts.list_posts() + + conn + |> assign(:posts, posts) + |> render(:index) + end + + def show(conn, %{"post_id" => post_id}) do + post = Posts.get_post!(post_id) + + conn + |> assign(:post, post) + |> render(:show) + end +end diff --git a/lib/cms_web/controllers/post_html.ex b/lib/cms_web/controllers/post_html.ex new file mode 100644 index 0000000..f3f2082 --- /dev/null +++ b/lib/cms_web/controllers/post_html.ex @@ -0,0 +1,6 @@ +defmodule CMSWeb.PostHTML do + @moduledoc false + use CMSWeb, :html + + embed_templates "post_html/*" +end diff --git a/lib/cms_web/controllers/post_html/index.html.heex b/lib/cms_web/controllers/post_html/index.html.heex new file mode 100644 index 0000000..30c87c7 --- /dev/null +++ b/lib/cms_web/controllers/post_html/index.html.heex @@ -0,0 +1,6 @@ +<%= for post <- @posts do %> + <article id={"post-#{post.id}"}> + <h2><.link href={~p"/posts/#{post}"}>{post.title}</.link></h2> + <p>{post.contents}</p> + </article> +<% end %> diff --git a/lib/cms_web/controllers/post_html/show.html.heex b/lib/cms_web/controllers/post_html/show.html.heex new file mode 100644 index 0000000..d037a53 --- /dev/null +++ b/lib/cms_web/controllers/post_html/show.html.heex @@ -0,0 +1,5 @@ +<header class="flex flex-row justify-between"> + <h1>{@post.title}</h1> + <.link :if={@admin?} href={~p"/admin/posts/#{@post}"}>edit</.link> +</header> +<p>{@post.contents}</p> diff --git a/lib/cms_web/live/post_live.ex b/lib/cms_web/live/post_live.ex new file mode 100644 index 0000000..10a5514 --- /dev/null +++ b/lib/cms_web/live/post_live.ex @@ -0,0 +1,62 @@ +defmodule CMSWeb.PostLive do + @moduledoc false + use CMSWeb, :live_view + + alias CMS.Posts + alias CMS.Posts.Post + + @impl true + def handle_params(_params, _uri, %{assigns: %{live_action: :new}} = socket) do + post = %Post{} + changeset = Post.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 = Posts.get_post!(post_id) + + changeset = Post.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 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 Posts.update_post(post, attrs) do + {:ok, post} -> + assign(socket, post: post, form: post |> Post.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" phx-submit="save_post"> + <input type="text" id={@form[:title].id} name={@form[:title].name} value={@form[:title].value} /> + <textarea id={@form[:contents].id} name={@form[:contents].name}>{@form[:contents].value} </textarea> + <button type="submit" class="self-end">save</button> + </.form> + """ + end +end diff --git a/lib/cms_web/router.ex b/lib/cms_web/router.ex index 6a5885b..b394f2d 100644 --- a/lib/cms_web/router.ex +++ b/lib/cms_web/router.ex @@ -32,8 +32,9 @@ defmodule CMSWeb.Router do pipe_through :supports_admin_action get "/", PageController, :home - get "/writing", PageController, :writing - get "/microblog", PageController, :microblog + + get "/posts", PostController, :index + get "/posts/:post_id", PostController, :show live "/sign-in", AdminLoginLive post "/admin/session/create", AdminSessionController, :create @@ -45,6 +46,9 @@ defmodule CMSWeb.Router do pipe_through :requires_admin live "/", AdminLive + + live "/posts/new", PostLive, :new + live "/posts/:post_id", PostLive, :edit end end diff --git a/priv/repo/migrations/20250222164951_add_posts_table.exs b/priv/repo/migrations/20250222164951_add_posts_table.exs new file mode 100644 index 0000000..065b85c --- /dev/null +++ b/priv/repo/migrations/20250222164951_add_posts_table.exs @@ -0,0 +1,13 @@ +defmodule CMS.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 :contents, :text, null: false, default: "" + + timestamps() + end + end +end