Skip to content

Commit

Permalink
feat: basic admin features and blog migration
Browse files Browse the repository at this point in the history
  • Loading branch information
kainlite committed Mar 18, 2024
1 parent 286d9d7 commit 654a518
Show file tree
Hide file tree
Showing 150 changed files with 13,374 additions and 16 deletions.
1 change: 1 addition & 0 deletions lib/tr/accounts/user.ex
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ defmodule Tr.Accounts.User do
field :accept_emails, :boolean
field :display_name, :string
field :avatar_url, :string
field :admin, :boolean, default: false

timestamps()

Expand Down
34 changes: 34 additions & 0 deletions lib/tr/post.ex
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,24 @@ defmodule Tr.Post do
Repo.preload(comments, :user)
end

@doc """
Returns the list of unapproved comments.
## Examples
iex> get_unapproved_comments()
[%Comment{}, ...]
"""
def get_unapproved_comments do
query =

Check warning on line 100 in lib/tr/post.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr/post.ex#L100

Added line #L100 was not covered by tests
from p in Tr.Post.Comment,
where: not p.approved

comments = Repo.all(query)
Repo.preload(comments, :user)

Check warning on line 105 in lib/tr/post.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr/post.ex#L104-L105

Added lines #L104 - L105 were not covered by tests
end

@doc """
Gets a single comment.
Expand Down Expand Up @@ -173,6 +191,22 @@ defmodule Tr.Post do
Repo.delete(comment)
end

@doc """
Approve a comment.
## Examples
iex> approve_comment(comment)
{:ok, %Comment{}}
iex> approve_comment(comment)
{:error, %Ecto.Changeset{}}
"""
def approve_comment(%Comment{} = comment) do
update_comment(comment, %{approved: true})

Check warning on line 207 in lib/tr/post.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr/post.ex#L207

Added line #L207 was not covered by tests
end

@doc """
Returns an `%Ecto.Changeset{}` for tracking comment changes.
Expand Down
3 changes: 2 additions & 1 deletion lib/tr/post/comment.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ defmodule Tr.Post.Comment do
field :slug, :string
field :body, :string
field :parent_comment_id, :integer, default: nil
field :approved, :boolean, default: false

timestamps()

Expand All @@ -18,7 +19,7 @@ defmodule Tr.Post.Comment do
@doc false
def changeset(comment, attrs) do
comment
|> cast(attrs, [:slug, :body, :user_id, :parent_comment_id])
|> cast(attrs, [:slug, :body, :user_id, :parent_comment_id, :approved])
|> validate_required([:slug, :body, :user_id])
end
end
19 changes: 19 additions & 0 deletions lib/tr_web/admin.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule TrWeb.Hooks.AllowAdmin do
@moduledoc """
Sandbox configuration for integration tests
"""
import Phoenix.LiveView

def on_mount(:default, _params, _session, socket) do

Check warning on line 7 in lib/tr_web/admin.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/admin.ex#L7

Added line #L7 was not covered by tests
{:cont, socket}
end

def on_mount(:require_admin_user, _params, session, socket) do
user = Tr.Accounts.get_user_by_session_token(session["user_token"])

Check warning on line 12 in lib/tr_web/admin.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/admin.ex#L12

Added line #L12 was not covered by tests

case user.admin == true do
true -> {:cont, socket}
_ -> {:halt, redirect(socket, to: "/")}

Check warning on line 16 in lib/tr_web/admin.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/admin.ex#L14-L16

Added lines #L14 - L16 were not covered by tests
end
end
end
10 changes: 10 additions & 0 deletions lib/tr_web/components/layouts/app.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@
Log out
</.link>
</li>
<%= if @current_user.admin do %>
<li>
<.link
href={~p"/admin/dashboard"}
class="text-[1.25rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
>
Dashboard
</.link>
</li>
<% end %>
<% else %>
<li>
<.link
Expand Down
2 changes: 1 addition & 1 deletion lib/tr_web/controllers/page_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,6 @@ defmodule TrWeb.PageController do
alias Tr.Blog

def index(conn, _params) do
render(conn, :home, posts: Blog.all_posts())
render(conn, :home, posts: Blog.recent_posts())
end
end
4 changes: 2 additions & 2 deletions lib/tr_web/controllers/page_html/home.html.heex
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@
<h2>Resources</h2>
<ul>
<li>
<a href="https://techsquad.rocks">Main blog</a>
<a href="https://techsquad.rocks/blog">This blog</a>
</li>
<li>
<a href="https://tr.techsquad.rocks/blog">This blog</a>
<a href="https://legacy.techsquad.rocks">Legacy blog</a>
</li>
<li>
<a href="https://github.com/kainlite/tr">Github repository</a>
Expand Down
96 changes: 96 additions & 0 deletions lib/tr_web/live/dashboard_live.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
defmodule TrWeb.DashboardLive do
@moduledoc """
This module is responsible for managing the admin dashboard
"""

use TrWeb, :live_view

@impl true
def mount(_params, _session, socket) do
if connected?(socket) do
Phoenix.PubSub.subscribe(Tr.PubSub, "#admin-dashboard")

Check warning on line 11 in lib/tr_web/live/dashboard_live.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/live/dashboard_live.ex#L9-L11

Added lines #L9 - L11 were not covered by tests
end

{:ok, assign(socket, :comments, Tr.Post.get_unapproved_comments())}
end

@impl true
def render(assigns) do
~H"""

Check warning on line 19 in lib/tr_web/live/dashboard_live.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/live/dashboard_live.ex#L19

Added line #L19 was not covered by tests
<div class="mx-auto">
<h2>Admin Dashboard</h2>
<table>
<tr>
<th>Email</th>
<th>Post</th>
<th>Body</th>
<th>Actions</th>
</tr>
<%= for {record, index} <- Enum.with_index(@comments) do %>

Check warning on line 30 in lib/tr_web/live/dashboard_live.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/live/dashboard_live.ex#L30

Added line #L30 was not covered by tests
<tr>
<td><%= index %></td>
<td><%= record.user.email %></td>
<td><%= record.slug %></td>
<td><%= record.body %></td>

Check warning on line 35 in lib/tr_web/live/dashboard_live.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/live/dashboard_live.ex#L33-L35

Added lines #L33 - L35 were not covered by tests
<td>
<.link

Check warning on line 37 in lib/tr_web/live/dashboard_live.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/live/dashboard_live.ex#L37

Added line #L37 was not covered by tests
class="text-[1.25rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
phx-click="approve_comment"
phx-value-slug={record.slug}
phx-value-comment-id={record.id}

Check warning on line 41 in lib/tr_web/live/dashboard_live.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/live/dashboard_live.ex#L40-L41

Added lines #L40 - L41 were not covered by tests
data-confirm="Are you sure?"
>
✔️
</.link>
<.link

Check warning on line 46 in lib/tr_web/live/dashboard_live.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/live/dashboard_live.ex#L46

Added line #L46 was not covered by tests
class="text-[1.25rem] leading-6 text-zinc-900 font-semibold hover:text-zinc-700"
phx-click="delete_comment"
phx-value-slug={record.slug}
phx-value-comment-id={record.id}

Check warning on line 50 in lib/tr_web/live/dashboard_live.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/live/dashboard_live.ex#L49-L50

Added lines #L49 - L50 were not covered by tests
data-confirm="Are you sure?"
>
</.link>
</td>
</tr>
<% end %>
</table>
</div>
"""
end

@impl true
def handle_event("approve_comment", params, socket) do
comment_id = Map.get(params, "comment-id")
comment = Tr.Post.get_comment(comment_id)

Check warning on line 66 in lib/tr_web/live/dashboard_live.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/live/dashboard_live.ex#L64-L66

Added lines #L64 - L66 were not covered by tests

broadcast(Tr.Post.approve_comment(comment))

Check warning on line 68 in lib/tr_web/live/dashboard_live.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/live/dashboard_live.ex#L68

Added line #L68 was not covered by tests
{:noreply, socket}
end

@impl true
def handle_event("delete_comment", params, socket) do
comment_id = Map.get(params, "comment-id")
comment = Tr.Post.get_comment(comment_id)
broadcast(Tr.Post.delete_comment(comment))

Check warning on line 76 in lib/tr_web/live/dashboard_live.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/live/dashboard_live.ex#L73-L76

Added lines #L73 - L76 were not covered by tests

{:noreply, socket}
end

@impl true
def handle_info(comment, socket) do
comments =
Enum.filter(socket.assigns.comments, fn item ->
item.id != comment.id

Check warning on line 85 in lib/tr_web/live/dashboard_live.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/live/dashboard_live.ex#L82-L85

Added lines #L82 - L85 were not covered by tests
end)

{:noreply, assign(socket, :comments, comments)}
end

defp broadcast({:ok, message}) do
Phoenix.PubSub.broadcast(Tr.PubSub, "#admin-dashboard", message)

Check warning on line 92 in lib/tr_web/live/dashboard_live.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/live/dashboard_live.ex#L91-L92

Added lines #L91 - L92 were not covered by tests

{:ok, message}
end
end
2 changes: 0 additions & 2 deletions lib/tr_web/live/post_live.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ defmodule TrWeb.PostLive do

@topic "users:connected"

on_mount {TrWeb.UserAuth, :mount_current_user}

@impl true
def mount(params, _session, socket) do
changeset = Tr.Post.change_comment(%Tr.Post.Comment{})
Expand Down
28 changes: 24 additions & 4 deletions lib/tr_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ defmodule TrWeb.Router do
end
end

live_session :default, on_mount: TrWeb.Hooks.AllowEctoSandbox do
live_session :default,
on_mount: [{TrWeb.Hooks.AllowEctoSandbox, :default}, {TrWeb.UserAuth, :mount_current_user}] do
scope "/blog", TrWeb do
pipe_through :browser

Expand All @@ -54,14 +55,28 @@ defmodule TrWeb.Router do
end
end

scope "/admin", TrWeb do
pipe_through [:browser, :require_admin_user]

live_session :require_admin_user,
on_mount: [
{TrWeb.Hooks.AllowAdmin, :require_admin_user},
{TrWeb.Hooks.AllowEctoSandbox, :default},
{TrWeb.UserAuth, :mount_current_user}
] do
live "/dashboard", DashboardLive, :index
end
end

## Authentication routes
scope "/", TrWeb do
pipe_through [:browser, :redirect_if_user_is_authenticated]

live_session :redirect_if_user_is_authenticated,
on_mount: [
{TrWeb.UserAuth, :redirect_if_user_is_authenticated},
{TrWeb.Hooks.AllowEctoSandbox, :default}
{TrWeb.Hooks.AllowEctoSandbox, :default},
{TrWeb.UserAuth, :mount_current_user}
] do
live "/users/register", UserRegistrationLive, :new
live "/users/log_in", UserLoginLive, :new
Expand All @@ -78,7 +93,8 @@ defmodule TrWeb.Router do
live_session :require_authenticated_user,
on_mount: [
{TrWeb.UserAuth, :ensure_authenticated},
{TrWeb.Hooks.AllowEctoSandbox, :default}
{TrWeb.Hooks.AllowEctoSandbox, :default},
{TrWeb.UserAuth, :mount_current_user}
] do
live "/users/settings", UserSettingsLive, :edit
live "/users/settings/confirm_email/:token", UserSettingsLive, :confirm_email
Expand All @@ -91,7 +107,11 @@ defmodule TrWeb.Router do
delete "/users/log_out", UserSessionController, :delete

live_session :current_user,
on_mount: [{TrWeb.UserAuth, :mount_current_user}, {TrWeb.Hooks.AllowEctoSandbox, :default}] do
on_mount: [
{TrWeb.UserAuth, :mount_current_user},
{TrWeb.Hooks.AllowEctoSandbox, :default},
{TrWeb.UserAuth, :mount_current_user}
] do
live "/users/confirm/:token", UserConfirmationLive, :edit
live "/users/confirm", UserConfirmationInstructionsLive, :new
end
Expand Down
11 changes: 11 additions & 0 deletions lib/tr_web/user_auth.ex
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,17 @@ defmodule TrWeb.UserAuth do
end
end

def require_admin_user(conn, _opts) do
if conn.assigns[:current_user] && conn.assigns[:current_user].admin do
conn

Check warning on line 220 in lib/tr_web/user_auth.ex

View check run for this annotation

Codecov / codecov/patch

lib/tr_web/user_auth.ex#L220

Added line #L220 was not covered by tests
else
conn
|> put_flash(:error, "You must be an admin to access this page.")
|> redirect(to: ~p"/")
|> halt()
end
end

defp put_token_in_session(conn, token) do
conn
|> put_session(:user_token, token)
Expand Down
11 changes: 11 additions & 0 deletions manifests/app/04-ingress.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ metadata:
spec:
tls:
- hosts:
- techsquad.rocks
- tr.techsquad.rocks
- blog.techsquad.rocks
secretName: tr-prod-tls
Expand All @@ -39,3 +40,13 @@ spec:
name: tr
port:
number: 4000
- host: techsquad.rocks
http:
paths:
- pathType: Prefix
path: "/"
backend:
service:
name: tr
port:
number: 4000
Loading

0 comments on commit 654a518

Please sign in to comment.