mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-27 13:47:58 +01:00
badges and awards
This commit is contained in:
parent
2a6b596765
commit
e37ab2849e
21 changed files with 361 additions and 17 deletions
|
@ -4,9 +4,11 @@ defmodule Philomena.Badges do
|
|||
"""
|
||||
|
||||
import Ecto.Query, warn: false
|
||||
alias Ecto.Multi
|
||||
alias Philomena.Repo
|
||||
|
||||
alias Philomena.Badges.Badge
|
||||
alias Philomena.Badges.Uploader
|
||||
|
||||
@doc """
|
||||
Returns the list of badges.
|
||||
|
@ -50,9 +52,20 @@ defmodule Philomena.Badges do
|
|||
|
||||
"""
|
||||
def create_badge(attrs \\ %{}) do
|
||||
badge =
|
||||
%Badge{}
|
||||
|> Badge.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
|> 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
|
||||
|
||||
@doc """
|
||||
|
@ -68,9 +81,20 @@ defmodule Philomena.Badges do
|
|||
|
||||
"""
|
||||
def update_badge(%Badge{} = badge, attrs) do
|
||||
badge =
|
||||
badge
|
||||
|> Badge.changeset(attrs)
|
||||
|> Repo.update()
|
||||
|> 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
|
||||
|
||||
@doc """
|
||||
|
@ -145,8 +169,8 @@ defmodule Philomena.Badges do
|
|||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def create_badge_award(attrs \\ %{}) do
|
||||
%Award{}
|
||||
def create_badge_award(creator, user, attrs \\ %{}) do
|
||||
%Award{awarded_by_id: creator.id, user_id: user.id}
|
||||
|> Award.changeset(attrs)
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
|
|
@ -21,7 +21,14 @@ defmodule Philomena.Badges.Award do
|
|||
@doc false
|
||||
def changeset(badge_award, attrs) do
|
||||
badge_award
|
||||
|> cast(attrs, [])
|
||||
|> validate_required([])
|
||||
|> cast(attrs, [:badge_id, :label, :reason, :badge_name])
|
||||
|> put_awarded_on()
|
||||
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
|
||||
|
|
|
@ -4,18 +4,29 @@ defmodule Philomena.Badges.Badge do
|
|||
|
||||
schema "badges" do
|
||||
field :title, :string
|
||||
field :description, :string
|
||||
field :description, :string, default: ""
|
||||
field :image, :string
|
||||
field :disable_award, :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)
|
||||
end
|
||||
|
||||
@doc false
|
||||
def changeset(badge, attrs) do
|
||||
badge
|
||||
|> cast(attrs, [])
|
||||
|> validate_required([])
|
||||
|> cast(attrs, [:title, :description, :disable_award, :priority])
|
||||
|> 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
|
||||
|
|
24
lib/philomena/badges/uploader.ex
Normal file
24
lib/philomena/badges/uploader.ex
Normal 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
|
|
@ -1,5 +1,6 @@
|
|||
defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
|
||||
alias Philomena.Users.User
|
||||
alias Philomena.Badges.Award
|
||||
alias Philomena.Comments.Comment
|
||||
alias Philomena.Commissions.Commission
|
||||
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"}, :alias, %Tag{}), do: true
|
||||
|
||||
# Award badges
|
||||
def can?(%User{role: "moderator"}, :create, %Award{}), do: true
|
||||
|
||||
#
|
||||
# Assistants can...
|
||||
#
|
||||
|
|
61
lib/philomena_web/controllers/admin/badge_controller.ex
Normal file
61
lib/philomena_web/controllers/admin/badge_controller.ex
Normal 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
|
|
@ -18,7 +18,7 @@ defmodule PhilomenaWeb.Admin.ForumController do
|
|||
|
||||
def create(conn, %{"forum" => forum_params}) do
|
||||
case Forums.create_forum(forum_params) do
|
||||
{:ok, forum} ->
|
||||
{:ok, _forum} ->
|
||||
conn
|
||||
|> put_flash(:info, "Forum created successfully.")
|
||||
|> redirect(to: Routes.admin_forum_path(conn, :index))
|
||||
|
@ -35,7 +35,7 @@ defmodule PhilomenaWeb.Admin.ForumController do
|
|||
|
||||
def update(conn, %{"forum" => forum_params}) do
|
||||
case Forums.update_forum(conn.assigns.forum, forum_params) do
|
||||
{:ok, forum} ->
|
||||
{:ok, _forum} ->
|
||||
conn
|
||||
|> put_flash(:info, "Forum updated successfully.")
|
||||
|> redirect(to: Routes.admin_forum_path(conn, :index))
|
||||
|
|
74
lib/philomena_web/controllers/profile/award_controller.ex
Normal file
74
lib/philomena_web/controllers/profile/award_controller.ex
Normal 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
|
|
@ -147,6 +147,7 @@ defmodule PhilomenaWeb.Router do
|
|||
end
|
||||
resources "/description", Profile.DescriptionController, only: [:edit, :update], singleton: true
|
||||
resources "/user_links", Profile.UserLinkController
|
||||
resources "/awards", Profile.AwardController, except: [:index, :show]
|
||||
end
|
||||
|
||||
scope "/filters", Filter, as: :filter do
|
||||
|
@ -203,6 +204,7 @@ defmodule PhilomenaWeb.Router do
|
|||
resources "/adverts", AdvertController, except: [:show]
|
||||
|
||||
resources "/forums", ForumController, except: [:show, :delete]
|
||||
resources "/badges", BadgeController, except: [:show, :delete]
|
||||
end
|
||||
|
||||
resources "/duplicate_reports", DuplicateReportController, only: [] do
|
||||
|
|
31
lib/philomena_web/templates/admin/badge/_form.html.slime
Normal file
31
lib/philomena_web/templates/admin/badge/_form.html.slime
Normal 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…")]
|
5
lib/philomena_web/templates/admin/badge/edit.html.slime
Normal file
5
lib/philomena_web/templates/admin/badge/edit.html.slime
Normal 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)
|
34
lib/philomena_web/templates/admin/badge/index.html.slime
Normal file
34
lib/philomena_web/templates/admin/badge/index.html.slime
Normal 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
|
5
lib/philomena_web/templates/admin/badge/new.html.slime
Normal file
5
lib/philomena_web/templates/admin/badge/new.html.slime
Normal 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)
|
|
@ -17,7 +17,7 @@
|
|||
' Users
|
||||
|
||||
= 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>
|
||||
' Forums
|
||||
|
||||
|
@ -27,7 +27,7 @@
|
|||
' Adverts
|
||||
|
||||
= 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>
|
||||
' Badges
|
||||
|
||||
|
|
27
lib/philomena_web/templates/profile/award/_form.html.slime
Normal file
27
lib/philomena_web/templates/profile/award/_form.html.slime
Normal 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…")]
|
|
@ -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
|
3
lib/philomena_web/templates/profile/award/new.html.slime
Normal file
3
lib/philomena_web/templates/profile/award/new.html.slime
Normal 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
|
|
@ -81,6 +81,14 @@
|
|||
.flex__grow.center
|
||||
= 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__header
|
||||
= if can?(@conn, :edit_description, @user) do
|
||||
|
|
5
lib/philomena_web/views/admin/badge_view.ex
Normal file
5
lib/philomena_web/views/admin/badge_view.ex
Normal file
|
@ -0,0 +1,5 @@
|
|||
defmodule PhilomenaWeb.Admin.BadgeView do
|
||||
use PhilomenaWeb, :view
|
||||
|
||||
import PhilomenaWeb.ProfileView, only: [badge_image: 2]
|
||||
end
|
13
lib/philomena_web/views/profile/award_view.ex
Normal file
13
lib/philomena_web/views/profile/award_view.ex
Normal 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
|
|
@ -12,6 +12,9 @@ defmodule PhilomenaWeb.ProfileView do
|
|||
def current?(%{id: id}, %{id: id}), do: true
|
||||
def current?(_user1, _user2), do: false
|
||||
|
||||
def manages_awards?(conn),
|
||||
do: can?(conn, :create, Philomena.Badges.Award)
|
||||
|
||||
def award_title(%{badge_name: nil} = award),
|
||||
do: award.badge.title
|
||||
def award_title(%{badge_name: ""} = award),
|
||||
|
|
Loading…
Reference in a new issue