badges and awards

This commit is contained in:
byte[] 2019-12-15 15:02:13 -05:00
parent 2a6b596765
commit e37ab2849e
21 changed files with 361 additions and 17 deletions

View file

@ -4,9 +4,11 @@ defmodule Philomena.Badges do
""" """
import Ecto.Query, warn: false import Ecto.Query, warn: false
alias Ecto.Multi
alias Philomena.Repo alias Philomena.Repo
alias Philomena.Badges.Badge alias Philomena.Badges.Badge
alias Philomena.Badges.Uploader
@doc """ @doc """
Returns the list of badges. Returns the list of badges.
@ -50,9 +52,20 @@ defmodule Philomena.Badges do
""" """
def create_badge(attrs \\ %{}) do def create_badge(attrs \\ %{}) do
%Badge{} badge =
|> Badge.changeset(attrs) %Badge{}
|> Repo.insert() |> Badge.changeset(attrs)
|> Uploader.analyze_upload(attrs)
Multi.new()
|> Multi.insert(:badge, badge)
|> Multi.run(:after, fn _repo, %{badge: badge} ->
Uploader.persist_upload(badge)
Uploader.unpersist_old_upload(badge)
{:ok, nil}
end)
|> Repo.isolated_transaction(:serializable)
end end
@doc """ @doc """
@ -68,9 +81,20 @@ defmodule Philomena.Badges do
""" """
def update_badge(%Badge{} = badge, attrs) do def update_badge(%Badge{} = badge, attrs) do
badge badge =
|> Badge.changeset(attrs) badge
|> Repo.update() |> Badge.changeset(attrs)
|> Uploader.analyze_upload(attrs)
Multi.new()
|> Multi.update(:badge, badge)
|> Multi.run(:after, fn _repo, %{badge: badge} ->
Uploader.persist_upload(badge)
Uploader.unpersist_old_upload(badge)
{:ok, nil}
end)
|> Repo.isolated_transaction(:serializable)
end end
@doc """ @doc """
@ -145,8 +169,8 @@ defmodule Philomena.Badges do
{:error, %Ecto.Changeset{}} {:error, %Ecto.Changeset{}}
""" """
def create_badge_award(attrs \\ %{}) do def create_badge_award(creator, user, attrs \\ %{}) do
%Award{} %Award{awarded_by_id: creator.id, user_id: user.id}
|> Award.changeset(attrs) |> Award.changeset(attrs)
|> Repo.insert() |> Repo.insert()
end end

View file

@ -21,7 +21,14 @@ defmodule Philomena.Badges.Award do
@doc false @doc false
def changeset(badge_award, attrs) do def changeset(badge_award, attrs) do
badge_award badge_award
|> cast(attrs, []) |> cast(attrs, [:badge_id, :label, :reason, :badge_name])
|> validate_required([]) |> put_awarded_on()
end end
defp put_awarded_on(%{data: %{awarded_on: nil}} = changeset) do
now = DateTime.utc_now() |> DateTime.truncate(:second)
put_change(changeset, :awarded_on, now)
end
defp put_awarded_on(changeset), do: changeset
end end

View file

@ -4,18 +4,29 @@ defmodule Philomena.Badges.Badge do
schema "badges" do schema "badges" do
field :title, :string field :title, :string
field :description, :string field :description, :string, default: ""
field :image, :string field :image, :string
field :disable_award, :boolean, default: false field :disable_award, :boolean, default: false
field :priority, :boolean, default: false field :priority, :boolean, default: false
field :uploaded_image, :string, virtual: true
field :removed_image, :string, virtual: true
field :image_mime_type, :string, virtual: true
timestamps(inserted_at: :created_at) timestamps(inserted_at: :created_at)
end end
@doc false @doc false
def changeset(badge, attrs) do def changeset(badge, attrs) do
badge badge
|> cast(attrs, []) |> cast(attrs, [:title, :description, :disable_award, :priority])
|> validate_required([]) |> validate_required([:title])
end
def image_changeset(badge, attrs) do
badge
|> cast(attrs, [:image, :image_mime_type, :uploaded_image, :removed_image])
|> validate_required([:image, :image_mime_type])
|> validate_inclusion(:image_mime_type, ["image/svg+xml"])
end end
end end

View file

@ -0,0 +1,24 @@
defmodule Philomena.Badges.Uploader do
@moduledoc """
Upload and processing callback logic for Badge images.
"""
alias Philomena.Badges.Badge
alias Philomena.Uploader
def analyze_upload(badge, params) do
Uploader.analyze_upload(badge, "image", params["image"], &Badge.image_changeset/2)
end
def persist_upload(badge) do
Uploader.persist_upload(badge, badge_file_root(), "image")
end
def unpersist_old_upload(badge) do
Uploader.unpersist_old_upload(badge, badge_file_root(), "image")
end
defp badge_file_root do
Application.get_env(:philomena, :badge_file_root)
end
end

View file

@ -1,5 +1,6 @@
defimpl Canada.Can, for: [Atom, Philomena.Users.User] do defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
alias Philomena.Users.User alias Philomena.Users.User
alias Philomena.Badges.Award
alias Philomena.Comments.Comment alias Philomena.Comments.Comment
alias Philomena.Commissions.Commission alias Philomena.Commissions.Commission
alias Philomena.Conversations.Conversation alias Philomena.Conversations.Conversation
@ -93,6 +94,9 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
def can?(%User{role: "moderator"}, :edit, %Tag{}), do: true def can?(%User{role: "moderator"}, :edit, %Tag{}), do: true
def can?(%User{role: "moderator"}, :alias, %Tag{}), do: true def can?(%User{role: "moderator"}, :alias, %Tag{}), do: true
# Award badges
def can?(%User{role: "moderator"}, :create, %Award{}), do: true
# #
# Assistants can... # Assistants can...
# #

View file

@ -0,0 +1,61 @@
defmodule PhilomenaWeb.Admin.BadgeController do
use PhilomenaWeb, :controller
alias Philomena.Badges.Badge
alias Philomena.Badges
alias Philomena.Repo
import Ecto.Query
plug :verify_authorized
plug :load_resource, model: Badge, only: [:edit, :update]
def index(conn, _params) do
badges =
Badge
|> order_by(asc: :title)
|> Repo.paginate(conn.assigns.scrivener)
render(conn, "index.html", badges: badges)
end
def new(conn, _params) do
changeset = Badges.change_badge(%Badge{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"badge" => badge_params}) do
case Badges.create_badge(badge_params) do
{:ok, %{badge: _badge}} ->
conn
|> put_flash(:info, "Badge created successfully.")
|> redirect(to: Routes.admin_badge_path(conn, :index))
{:error, :badge, changeset, _changes} ->
render(conn, "new.html", changeset: changeset)
end
end
def edit(conn, _params) do
changeset = Badges.change_badge(conn.assigns.badge)
render(conn, "edit.html", changeset: changeset)
end
def update(conn, %{"badge" => badge_params}) do
case Badges.update_badge(conn.assigns.badge, badge_params) do
{:ok, %{badge: _badge}} ->
conn
|> put_flash(:info, "Badge updated successfully.")
|> redirect(to: Routes.admin_badge_path(conn, :index))
{:error, :badge, changeset, _changes} ->
render(conn, "edit.html", changeset: changeset)
end
end
defp verify_authorized(conn, _opts) do
case Canada.Can.can?(conn.assigns.current_user, :index, Badge) do
true -> conn
_false -> PhilomenaWeb.NotAuthorizedPlug.call(conn)
end
end
end

View file

@ -18,7 +18,7 @@ defmodule PhilomenaWeb.Admin.ForumController do
def create(conn, %{"forum" => forum_params}) do def create(conn, %{"forum" => forum_params}) do
case Forums.create_forum(forum_params) do case Forums.create_forum(forum_params) do
{:ok, forum} -> {:ok, _forum} ->
conn conn
|> put_flash(:info, "Forum created successfully.") |> put_flash(:info, "Forum created successfully.")
|> redirect(to: Routes.admin_forum_path(conn, :index)) |> redirect(to: Routes.admin_forum_path(conn, :index))
@ -35,7 +35,7 @@ defmodule PhilomenaWeb.Admin.ForumController do
def update(conn, %{"forum" => forum_params}) do def update(conn, %{"forum" => forum_params}) do
case Forums.update_forum(conn.assigns.forum, forum_params) do case Forums.update_forum(conn.assigns.forum, forum_params) do
{:ok, forum} -> {:ok, _forum} ->
conn conn
|> put_flash(:info, "Forum updated successfully.") |> put_flash(:info, "Forum updated successfully.")
|> redirect(to: Routes.admin_forum_path(conn, :index)) |> redirect(to: Routes.admin_forum_path(conn, :index))

View file

@ -0,0 +1,74 @@
defmodule PhilomenaWeb.Profile.AwardController do
use PhilomenaWeb, :controller
alias Philomena.Badges.Award
alias Philomena.Badges.Badge
alias Philomena.Users.User
alias Philomena.Badges
alias Philomena.Repo
import Ecto.Query
plug :verify_authorized
plug :load_resource, model: User, id_name: "profile_id", id_field: "slug", persisted: true
plug :load_resource, model: Award, only: [:edit, :update, :delete]
plug :load_badges when action in [:new, :create, :edit, :update]
def new(conn, _params) do
changeset = Badges.change_badge_award(%Award{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"award" => award_params}) do
case Badges.create_badge_award(conn.assigns.current_user, conn.assigns.user, award_params) do
{:ok, _award} ->
conn
|> put_flash(:info, "Award successfully created.")
|> redirect(to: Routes.profile_path(conn, :show, conn.assigns.user))
{:error, changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def edit(conn, _params) do
changeset = Badges.change_badge_award(conn.assigns.award)
render(conn, "edit.html", changeset: changeset)
end
def update(conn, %{"award" => award_params}) do
case Badges.update_badge_award(conn.assigns.award, award_params) do
{:ok, _award} ->
conn
|> put_flash(:info, "Award successfully updated.")
|> redirect(to: Routes.profile_path(conn, :show, conn.assigns.user))
{:error, changeset} ->
render(conn, "edit.html", changeset: changeset)
end
end
def delete(conn, _params) do
{:ok, _award} = Badges.delete_badge_award(conn.assigns.award)
conn
|> put_flash(:info, "Award successfully destroyed. By cruel and unusual means.")
|> redirect(to: Routes.profile_path(conn, :show, conn.assigns.user))
end
defp verify_authorized(conn, _opts) do
case Canada.Can.can?(conn.assigns.current_user, :create, Award) do
true -> conn
_false -> PhilomenaWeb.NotAuthorizedPlug.call(conn)
end
end
defp load_badges(conn, _opts) do
badges =
Badge
|> where(disable_award: false)
|> order_by(asc: :title)
|> Repo.all()
assign(conn, :badges, badges)
end
end

View file

@ -147,6 +147,7 @@ defmodule PhilomenaWeb.Router do
end end
resources "/description", Profile.DescriptionController, only: [:edit, :update], singleton: true resources "/description", Profile.DescriptionController, only: [:edit, :update], singleton: true
resources "/user_links", Profile.UserLinkController resources "/user_links", Profile.UserLinkController
resources "/awards", Profile.AwardController, except: [:index, :show]
end end
scope "/filters", Filter, as: :filter do scope "/filters", Filter, as: :filter do
@ -203,6 +204,7 @@ defmodule PhilomenaWeb.Router do
resources "/adverts", AdvertController, except: [:show] resources "/adverts", AdvertController, except: [:show]
resources "/forums", ForumController, except: [:show, :delete] resources "/forums", ForumController, except: [:show, :delete]
resources "/badges", BadgeController, except: [:show, :delete]
end end
resources "/duplicate_reports", DuplicateReportController, only: [] do resources "/duplicate_reports", DuplicateReportController, only: [] do

View file

@ -0,0 +1,31 @@
= form_for @changeset, @action, [multipart: true], fn f ->
= if @changeset.action do
.alert.alert-danger
p Oops, something went wrong! Please check the errors below.
.field
=> label f, :title, "Badge name:"
= text_input f, :title, class: "input input--wide", placeholder: "Name", required: true
= error_tag f, :title
.field
=> label f, :description, "An optional short description:"
= text_input f, :description, class: "input input--wide", placeholder: "Description"
= error_tag f, :description
.field
=> checkbox f, :disable_award, class: "checkbox"
= label f, :disable_award, "Prevent image from appearing in \"Badge to award\" list"
.field
=> checkbox f, :priority, class: "checkbox"
= label f, :priority, "Displays before badges that don't have this checkbox checked"
h4 Image
.field
=> label f, :image, "Upload SVG image:"
= file_input f, :image, class: "input input--wide"
= error_tag f, :image
= error_tag f, :image_mime_type
= submit "Save Badge", class: "button", data: [disable_with: raw("Saving…")]

View file

@ -0,0 +1,5 @@
h2 Edit Badge
= render "_form.html", Map.put(assigns, :action, Routes.admin_badge_path(@conn, :update, @badge))
= link "Back", to: Routes.admin_badge_path(@conn, :index)

View file

@ -0,0 +1,34 @@
h2 Badges
- route = fn p -> Routes.admin_badge_path(@conn, :index, p) end
- pagination = render PhilomenaWeb.PaginationView, "_pagination.html", page: @badges, route: route, conn: @conn
.block
.block__header
a href=Routes.admin_badge_path(@conn, :new)
i.fa.fa-plus>
' New Badge
= pagination
.block__content
table.table
thead
tr
th Badge
th Image
th Options
tbody
= for badge <- @badges do
tr
td
= badge.title
td
= badge_image(badge, width: 32, height: 32)
td
= link "Edit", to: Routes.admin_badge_path(@conn, :edit, badge)
.block__header.block__header--light
= pagination

View file

@ -0,0 +1,5 @@
h2 New Badge
= render "_form.html", Map.put(assigns, :action, Routes.admin_badge_path(@conn, :create))
= link "Back", to: Routes.admin_badge_path(@conn, :index)

View file

@ -17,7 +17,7 @@
' Users ' Users
= if manages_forums?(@conn) do = if manages_forums?(@conn) do
= link to: "#", class: "header__link" do = link to: Routes.admin_forum_path(@conn, :index), class: "header__link" do
i.fa.fa-fw.fa-paragraph> i.fa.fa-fw.fa-paragraph>
' Forums ' Forums
@ -27,7 +27,7 @@
' Adverts ' Adverts
= if manages_badges?(@conn) do = if manages_badges?(@conn) do
= link to: "#", class: "header__link" do = link to: Routes.admin_badge_path(@conn, :index), class: "header__link" do
i.fa.fa-fw.fa-trophy> i.fa.fa-fw.fa-trophy>
' Badges ' Badges

View file

@ -0,0 +1,27 @@
= form_for @changeset, @action, fn f ->
= if @changeset.action do
.alert.alert-danger
p Oops, something went wrong! Please check the errors below.
.field
=> label f, :badge_id, "Badge type"
= select f, :badge_id, badge_options(@badges), class: "input", data: [change_selectvalue: "#award_label"]
= error_tag f, :badge_id
.field
=> label f, :label, "Label (Public)"
= text_input f, :label, class: "input input--wide"
= error_tag f, :label
.field
=> label f, :reason, "Reason (Private)"
= text_input f, :reason, class: "input input--wide"
= error_tag f, :reason
.field
=> label f, :badge_name, "Badge name (overrides default name if set, leave blank for default badge name)"
= text_input f, :badge_name, class: "input input--wide"
= error_tag f, :badge_name
.field
= submit "Save", class: "button", data: [disable_with: raw("Saving&hellip;")]

View file

@ -0,0 +1,3 @@
h1 Editing award
= render PhilomenaWeb.Profile.AwardView, "_form.html", changeset: @changeset, badges: @badges, action: Routes.profile_award_path(@conn, :update, @user, @award), conn: @conn

View file

@ -0,0 +1,3 @@
h1 New award
= render PhilomenaWeb.Profile.AwardView, "_form.html", changeset: @changeset, badges: @badges, action: Routes.profile_award_path(@conn, :create, @user), conn: @conn

View file

@ -81,6 +81,14 @@
.flex__grow.center .flex__grow.center
= pretty_time(award.awarded_on) = pretty_time(award.awarded_on)
= if manages_awards?(@conn) do
.flex__grow.center
a href=Routes.profile_award_path(@conn, :delete, @user, award) data-method="delete" data-confirm="Are you really, really sure?"
' Remove
br
a href=Routes.profile_award_path(@conn, :edit, @user, award)
' Edit
.block .block
.block__header .block__header
= if can?(@conn, :edit_description, @user) do = if can?(@conn, :edit_description, @user) do

View file

@ -0,0 +1,5 @@
defmodule PhilomenaWeb.Admin.BadgeView do
use PhilomenaWeb, :view
import PhilomenaWeb.ProfileView, only: [badge_image: 2]
end

View file

@ -0,0 +1,13 @@
defmodule PhilomenaWeb.Profile.AwardView do
use PhilomenaWeb, :view
def badge_options(badges) do
for badge <- badges do
[
key: badge.title,
value: badge.id,
data: [set_value: badge.description]
]
end
end
end

View file

@ -12,6 +12,9 @@ defmodule PhilomenaWeb.ProfileView do
def current?(%{id: id}, %{id: id}), do: true def current?(%{id: id}, %{id: id}), do: true
def current?(_user1, _user2), do: false def current?(_user1, _user2), do: false
def manages_awards?(conn),
do: can?(conn, :create, Philomena.Badges.Award)
def award_title(%{badge_name: nil} = award), def award_title(%{badge_name: nil} = award),
do: award.badge.title do: award.badge.title
def award_title(%{badge_name: ""} = award), def award_title(%{badge_name: ""} = award),