admin user links

This commit is contained in:
byte[] 2019-12-09 20:21:49 -05:00
parent 16843ec216
commit 7d247b777f
24 changed files with 384 additions and 63 deletions

View file

@ -4,9 +4,12 @@ defmodule Philomena.UserLinks do
"""
import Ecto.Query, warn: false
alias Ecto.Multi
alias Philomena.Repo
alias Philomena.UserLinks.UserLink
alias Philomena.Badges.Badge
alias Philomena.Badges.Award
alias Philomena.Tags.Tag
@doc """
@ -71,8 +74,46 @@ defmodule Philomena.UserLinks do
"""
def update_user_link(%UserLink{} = user_link, attrs) do
tag = Repo.get_by(Tag, name: attrs["tag_name"])
user_link
|> UserLink.changeset(attrs)
|> UserLink.edit_changeset(attrs, tag)
|> Repo.update()
end
def verify_user_link(%UserLink{} = user_link, user) do
user_link_changeset =
user_link
|> UserLink.verify_changeset(user)
Multi.new()
|> Multi.update(:user_link, user_link_changeset)
|> Multi.run(:add_award, fn repo, _changes ->
now = DateTime.utc_now() |> DateTime.truncate(:second)
with badge when not is_nil(badge) <- repo.get_by(Badge, title: "Artist"),
nil <- repo.get_by(Award, badge_id: badge.id, user_id: user_link.user_id)
do
%Award{badge_id: badge.id, user_id: user_link.user_id, awarded_by_id: user.id, awarded_on: now}
|> Award.changeset()
|> repo.insert()
else
_ ->
{:ok, nil}
end
end)
|> Repo.isolated_transaction(:serializable)
end
def reject_user_link(%UserLink{} = user_link) do
user_link
|> UserLink.reject_changeset()
|> Repo.update()
end
def contact_user_link(%UserLink{} = user_link, user) do
user_link
|> UserLink.contact_changeset(user)
|> Repo.update()
end
@ -106,7 +147,7 @@ defmodule Philomena.UserLinks do
end
def count_user_links(user) do
if Canada.Can.can?(user, :edit, UserLink) do
if Canada.Can.can?(user, :index, UserLink) do
UserLink
|> where(aasm_state: "unverified")
|> Repo.aggregate(:count, :id)

View file

@ -30,17 +30,44 @@ defmodule Philomena.UserLinks.UserLink do
|> validate_required([])
end
def edit_changeset(user_link, attrs, tag) do
user_link
|> cast(attrs, [:uri, :public])
|> put_change(:tag_id, tag.id)
|> validate_required([:user, :uri, :public])
|> parse_uri()
end
def creation_changeset(user_link, attrs, user, tag) do
user_link
|> cast(attrs, [:uri])
|> cast(attrs, [:uri, :public])
|> put_assoc(:tag, tag)
|> put_assoc(:user, user)
|> validate_required([:user, :uri])
|> validate_required([:user, :uri, :public])
|> parse_uri()
|> put_verification_code()
|> put_next_check_at()
end
def reject_changeset(user_link) do
change(user_link, aasm_state: "rejected")
end
def verify_changeset(user_link, user) do
change(user_link)
|> put_change(:verified_by_user_id, user.id)
|> put_change(:aasm_state, "verified")
end
def contact_changeset(user_link, user) do
now = DateTime.utc_now() |> DateTime.truncate(:second)
change(user_link)
|> put_change(:contacted_by_user_id, user.id)
|> put_change(:contacted_at, now)
|> put_change(:aasm_state, "contacted")
end
defp parse_uri(changeset) do
string_uri = get_field(changeset, :uri) |> to_string()
uri = URI.parse(string_uri)

View file

@ -47,6 +47,12 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
def can?(%User{role: "moderator"}, :show, %Report{}), do: true
def can?(%User{role: "moderator"}, :edit, %Report{}), do: true
# Manage user links
def can?(%User{role: "moderator"}, :create_links, %User{}), do: true
def can?(%User{role: "moderator"}, :edit_links, %User{}), do: true
def can?(%User{role: "moderator"}, :edit, %UserLink{}), do: true
def can?(%User{role: "moderator"}, :index, UserLink), do: true
#
# Assistants can...
#
@ -107,6 +113,7 @@ defimpl Canada.Can, for: [Atom, Philomena.Users.User] do
def can?(%User{id: id}, action, %Filter{user_id: id}) when action in [:edit, :update], do: true
# View user links they've created
def can?(%User{id: id}, :create_links, %User{id: id}), do: true
def can?(%User{id: id}, :show, %UserLink{user_id: id}), do: true
# Edit their commissions

View file

@ -0,0 +1,17 @@
defmodule PhilomenaWeb.Admin.UserLink.ContactController do
use PhilomenaWeb, :controller
alias Philomena.UserLinks.UserLink
alias Philomena.UserLinks
plug PhilomenaWeb.CanaryMapPlug, create: :edit
plug :load_and_authorize_resource, model: UserLink, id_name: "user_link_id", persisted: true, preload: [:user]
def create(conn, _params) do
{:ok, user_link} = UserLinks.contact_user_link(conn.assigns.user_link, conn.assigns.current_user)
conn
|> put_flash(:info, "User link successfully marked as contacted.")
|> redirect(to: Routes.profile_user_link_path(conn, :show, conn.assigns.user_link.user, user_link))
end
end

View file

@ -0,0 +1,17 @@
defmodule PhilomenaWeb.Admin.UserLink.RejectController do
use PhilomenaWeb, :controller
alias Philomena.UserLinks.UserLink
alias Philomena.UserLinks
plug PhilomenaWeb.CanaryMapPlug, create: :edit
plug :load_and_authorize_resource, model: UserLink, id_name: "user_link_id", persisted: true, preload: [:user]
def create(conn, _params) do
{:ok, user_link} = UserLinks.reject_user_link(conn.assigns.user_link)
conn
|> put_flash(:info, "User link successfully marked as rejected.")
|> redirect(to: Routes.profile_user_link_path(conn, :show, conn.assigns.user_link.user, user_link))
end
end

View file

@ -0,0 +1,17 @@
defmodule PhilomenaWeb.Admin.UserLink.VerificationController do
use PhilomenaWeb, :controller
alias Philomena.UserLinks.UserLink
alias Philomena.UserLinks
plug PhilomenaWeb.CanaryMapPlug, create: :edit
plug :load_and_authorize_resource, model: UserLink, id_name: "user_link_id", persisted: true, preload: [:user]
def create(conn, _params) do
{:ok, %{user_link: user_link}} = UserLinks.verify_user_link(conn.assigns.user_link, conn.assigns.current_user)
conn
|> put_flash(:info, "User link successfully verified.")
|> redirect(to: Routes.profile_user_link_path(conn, :show, conn.assigns.user_link.user, user_link))
end
end

View file

@ -0,0 +1,45 @@
defmodule PhilomenaWeb.Admin.UserLinkController do
use PhilomenaWeb, :controller
alias Philomena.UserLinks.UserLink
alias Philomena.Repo
import Ecto.Query
plug :verify_authorized
def index(conn, %{"all" => _value}) do
load_links(UserLink, conn)
end
def index(conn, %{"q" => query}) do
query = "%#{query}%"
UserLink
|> join(:inner, [ul], _ in assoc(ul, :user))
|> where([ul, u], ilike(u.name, ^query) or ilike(ul.uri, ^query))
|> load_links(conn)
end
def index(conn, _params) do
UserLink
|> where([u], u.aasm_state in ^["unverified", "link_verified", "contacted"])
|> load_links(conn)
end
defp load_links(queryable, conn) do
links =
queryable
|> order_by(desc: :created_at)
|> preload([:tag, :verified_by_user, :contacted_by_user, user: [:linked_tags, awards: :badge]])
|> Repo.paginate(conn.assigns.scrivener)
render(conn, "index.html", user_links: links)
end
defp verify_authorized(conn, _opts) do
case Canada.Can.can?(conn.assigns.current_user, :index, UserLink) do
true -> conn
false -> PhilomenaWeb.NotAuthorizedPlug.call(conn)
end
end
end

View file

@ -0,0 +1,72 @@
defmodule PhilomenaWeb.Profile.UserLinkController do
use PhilomenaWeb, :controller
alias Philomena.UserLinks.UserLink
alias Philomena.UserLinks
alias Philomena.Users.User
alias Philomena.Repo
import Ecto.Query
plug PhilomenaWeb.FilterBannedUsersPlug when action in [:new, :create]
plug :load_and_authorize_resource, model: UserLink, only: [:show, :edit, :update], preload: [:user, :tag, :contacted_by_user]
plug PhilomenaWeb.CanaryMapPlug,
index: :create_links,
new: :create_links,
create: :create_links,
show: :create_links,
edit: :edit_links,
update: :edit_links
plug :load_and_authorize_resource, model: User, id_field: "slug", id_name: "profile_id", persisted: true
def index(conn, _params) do
user = conn.assigns.current_user
user_links =
UserLink
|> where(user_id: ^user.id)
|> Repo.all()
render(conn, "index.html", user_links: user_links)
end
def new(conn, _params) do
changeset = UserLinks.change_user_link(%UserLink{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"user_link" => user_link_params}) do
case UserLinks.create_user_link(conn.assigns.user, user_link_params) do
{:ok, user_link} ->
conn
|> put_flash(:info, "Link submitted! Please put '#{user_link.verification_code}' on your linked webpage now.")
|> redirect(to: Routes.profile_user_link_path(conn, :show, conn.assigns.user_link, user_link))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def show(conn, _params) do
user_link = conn.assigns.user_link
render(conn, "show.html", user_link: user_link)
end
def edit(conn, _params) do
changeset = UserLinks.change_user_link(conn.assigns.user_link)
render(conn, "edit.html", changeset: changeset)
end
def update(conn, %{"user_link" => user_link_params}) do
case UserLinks.update_user_link(conn.assigns.user_link, user_link_params) do
{:ok, user_link} ->
conn
|> put_flash(:info, "Link successfully updated.")
|> redirect(to: Routes.profile_user_link_path(conn, :show, conn.assigns.user, user_link))
{:error, changeset} ->
render(conn, "edit.html", changeset: changeset)
end
end
end

View file

@ -14,7 +14,7 @@ defmodule PhilomenaWeb.ProfileController do
import Ecto.Query
plug :load_and_authorize_resource, model: User, only: :show, id_field: "slug", preload: [
awards: :badge, public_links: :tag, commission: [sheet_image: :tags, items: [example_image: :tags]]
awards: :badge, public_links: :tag, verified_links: :tag, commission: [sheet_image: :tags, items: [example_image: :tags]]
]
def show(conn, _params) do

View file

@ -1,45 +0,0 @@
defmodule PhilomenaWeb.UserLinkController do
use PhilomenaWeb, :controller
alias Philomena.UserLinks
alias Philomena.UserLinks.UserLink
alias Philomena.Repo
import Ecto.Query
plug PhilomenaWeb.FilterBannedUsersPlug when action in [:new, :create]
plug :load_and_authorize_resource, model: UserLink, only: [:show], preload: [:user, :tag, :contacted_by_user]
def index(conn, _params) do
user = conn.assigns.current_user
user_links =
UserLink
|> where(user_id: ^user.id)
|> Repo.all()
render(conn, "index.html", user_links: user_links)
end
def new(conn, _params) do
changeset = UserLinks.change_user_link(%UserLink{})
render(conn, "new.html", changeset: changeset)
end
def create(conn, %{"user_link" => user_link_params}) do
user = conn.assigns.current_user
case UserLinks.create_user_link(user, user_link_params) do
{:ok, user_link} ->
conn
|> put_flash(:info, "Link submitted! Please put '#{user_link.verification_code}' on your linked webpage now.")
|> redirect(to: Routes.user_link_path(conn, :show, user_link))
{:error, %Ecto.Changeset{} = changeset} ->
render(conn, "new.html", changeset: changeset)
end
end
def show(conn, _params) do
user_link = conn.assigns.user_link
render(conn, "show.html", user_link: user_link)
end
end

View file

@ -136,6 +136,7 @@ defmodule PhilomenaWeb.Router do
resources "/reports", Profile.Commission.ReportController, only: [:new, :create]
end
resources "/description", Profile.DescriptionController, only: [:edit, :update], singleton: true
resources "/user_links", Profile.UserLinkController
end
scope "/filters", Filter, as: :filter do
@ -151,7 +152,6 @@ defmodule PhilomenaWeb.Router do
resources "/avatar", AvatarController, only: [:edit, :update, :delete], singleton: true
resources "/reports", ReportController, only: [:index]
resources "/user_links", UserLinkController, only: [:index, :new, :create, :show]
resources "/galleries", GalleryController, only: [:new, :create, :edit, :update, :delete] do
resources "/images", Gallery.ImageController, only: [:create, :delete], singleton: true
resources "/order", Gallery.OrderController, only: [:update], singleton: true
@ -172,6 +172,12 @@ defmodule PhilomenaWeb.Router do
resources "/claim", Report.ClaimController, only: [:create, :delete], singleton: true
resources "/close", Report.CloseController, only: [:create], singleton: true
end
resources "/user_links", UserLinkController, only: [:index] do
resources "/verification", UserLink.VerificationController, only: [:create], singleton: true
resources "/contact", UserLink.ContactController, only: [:create], singleton: true
resources "/reject", UserLink.RejectController, only: [:create], singleton: true
end
end
resources "/duplicate_reports", DuplicateReportController, only: [] do

View file

@ -0,0 +1,78 @@
h1 User Links
p Link creation is done via the Users menu.
p Verifying a link will automatically award an artist badge if the link is public, no artist badge exists, and an "artist:" tag is specified.
= form_for :user_link, Routes.admin_user_link_path(@conn, :index), [method: "get", class: "hform"], fn f ->
.field
= text_input f, :q, name: :q, value: @conn.params["q"], class: "input hform__text", placeholder: "Search query", autocapitalize: "none"
= submit "Search", class: "hform__button button"
- route = fn p -> Routes.admin_user_link_path(@conn, :index, p) end
- pagination = render PhilomenaWeb.PaginationView, "_pagination.html", page: @user_links, route: route, params: link_scope(@conn), conn: @conn
.block
.block__header
= if @conn.params["all"] do
= link "Show unverified only", to: Routes.admin_user_link_path(@conn, :index)
- else
= link "Show all", to: Routes.admin_user_link_path(@conn, :index, all: "true")
= pagination
.block__content
table.table
thead
tr
th State
th User
th URL
th Options
th Mark
th Public
tbody
= for link <- @user_links do
tr
td class=link_state_class(link)
strong
= link_state_name(link)
= if contacted?(link) do
br
' by
= link.contacted_by_user.name
br
| (
= pretty_time link.contacted_at
| )
td
= render PhilomenaWeb.UserAttributionView, "_user.html", object: link, awards: true, conn: @conn
= render PhilomenaWeb.TagView, "_tag_list.html", tags: display_order(link.user.linked_tags), conn: @conn
td
= link String.slice(link.uri, 0, 100), to: link.uri
= if link.tag do
br
= render PhilomenaWeb.TagView, "_tag.html", tag: link.tag, conn: @conn
td
=> link "View", to: Routes.profile_user_link_path(@conn, :show, link.user, link)
' &bull;
= link "Edit", to: Routes.profile_user_link_path(@conn, :edit, link.user, link)
td
=> link "Verify", to: Routes.admin_user_link_verification_path(@conn, :create, link), method: :post
' &bull;
=> link "Reject", to: Routes.admin_user_link_reject_path(@conn, :create, link), method: :post
br
= if not verified?(link) do
= if contacted?(link) do
' Artist contacted
- else
= link "Artist contacted", to: Routes.admin_user_link_contact_path(@conn, :create, link), method: :post
td
= public_text(link)
.block__header.block__header--light
= pagination

View file

@ -75,7 +75,7 @@ header.header
a.header__link href="/posts?pq=my:posts"
i.fas.fa-fw.fa-pen-square>
| Posts
a.header__link href='/user_links'
a.header__link href=Routes.profile_user_link_path(@conn, :index, @current_user)
i.fa.fa-fw.fa-link>
| Links
a.header__link href='/settings/edit'

View file

@ -57,7 +57,7 @@
span.header__counter__admin
= @report_count
= if @user_link_count do
= link to: "#", class: "header__link", title: "User Links" do
= link to: Routes.admin_user_link_path(@conn, :index), class: "header__link", title: "User Links" do
' L
span.header__counter__admin
= @user_link_count

View file

@ -52,7 +52,7 @@
.block
.block__header
span.block__header__title User Links
= for link <- @user.public_links do
= for link <- @user.verified_links, link.public or can?(@conn, :edit, link) do
.block__content.alternating-color.break-word
.center
= if link.tag do

View file

@ -3,7 +3,7 @@ h1
a href=Routes.profile_path(@conn, :show, @user)
= @user.name
- route = fn p -> Routes.profile_source_change_path(@conn, :index, @image, p) end
- route = fn p -> Routes.profile_source_change_path(@conn, :index, @user, p) end
- pagination = render PhilomenaWeb.PaginationView, "_pagination.html", page: @source_changes, route: route, conn: @conn
= render PhilomenaWeb.SourceChangeView, "index.html", conn: @conn, source_changes: @source_changes, pagination: pagination

View file

@ -15,15 +15,18 @@
em artist name here
' or a series name
p Should be blank only if your content isn't on the site, generally
= text_input f, :tag_name, class: "input", autocomplete: "off", placeholder: "artist:name", data: [ac: "true", ac_min_length: "3", ac_source: "/tags/autocomplete?term="]
= text_input f, :tag_name, value: assigns[:tag_name], class: "input", autocomplete: "off", placeholder: "artist:name", data: [ac: "true", ac_min_length: "3", ac_source: "/tags/autocomplete?term="]
.field
label for="uri"
' URL of your art webpage
= url_input f, :uri, class: "input input--wide", placeholder: "https://www.deviantart.com/your-name"#, required: true
= url_input f, :uri, class: "input input--wide", placeholder: "https://www.deviantart.com/your-name", required: true
= error_tag f, :uri
.field
=> radio_button f, :public, "true"
=> label f, :public, "Visible to everyone"
.field
=> radio_button f, :public, "false"
=> label f, :public, "Visible only to site staff"

View file

@ -0,0 +1,2 @@
h1 Edit Link
= render PhilomenaWeb.Profile.UserLinkView, "_form.html", conn: @conn, changeset: @changeset, tag_name: @user_link.tag.name, action: Routes.profile_user_link_path(@conn, :update, @user_link.user, @user_link)

View file

@ -1,6 +1,6 @@
h1 Your Links
p
a.button href=Routes.user_link_path(@conn, :new)
a.button href=Routes.profile_user_link_path(@conn, :new, @user)
' Create a link
p
' User links associate your account on Derpibooru with tags about content you create and with accounts on sites elsewhere. This allows users to easily identify artists and admins to act more rapidly on takedown requests.
@ -17,7 +17,7 @@ table.table
= for link <- @user_links do
tr
td = link link.uri, to: link.uri
td = link "View Details", to: Routes.user_link_path(@conn, :show, link)
td = link "View Details", to: Routes.profile_user_link_path(@conn, :show, @user, link)
td = link.verification_code
th = verified_as_string(link)
th = public_as_string(link)

View file

@ -0,0 +1,2 @@
h1 Create Link
= render PhilomenaWeb.UserLinkView, "_form.html", changeset: @changeset, action: Routes.profile_user_link_path(@conn, :create, @user), conn: @conn

View file

@ -56,4 +56,4 @@ h3 Associated tag
- else
p There is no tag associated with this link.
= link "Back", to: Routes.user_link_path(@conn, :index)
= link "Back", to: Routes.profile_user_link_path(@conn, :index, @user)

View file

@ -1,2 +0,0 @@
h1 Create Link
= render PhilomenaWeb.UserLinkView, "_form.html", changeset: @changeset, action: Routes.user_link_path(@conn, :create)

View file

@ -0,0 +1,34 @@
defmodule PhilomenaWeb.Admin.UserLinkView do
use PhilomenaWeb, :view
import Philomena.Tags.Tag, only: [display_order: 1]
def link_state_class(%{aasm_state: state}) when state in ["verified", "link_verified"], do: "success"
def link_state_class(%{aasm_state: state}) when state in ["unverified", "rejected"], do: "danger"
def link_state_class(%{aasm_state: "contacted"}), do: "warning"
def link_state_class(_link), do: nil
def link_state_name(%{aasm_state: state}) do
state
|> String.replace("_", " ")
|> String.capitalize()
end
def link_scope(conn) do
case conn.params["all"] do
nil -> []
_val -> [all: true]
end
end
def contacted?(%{aasm_state: state}), do: state == "contacted"
def verified?(%{aasm_state: state}), do: state == "verified"
def link_verified?(%{aasm_state: state}), do: state == "link_verified"
def unverified?(%{aasm_state: state}), do: state == "unverified"
def rejected?(%{aasm_state: state}), do: state == "rejected"
def public_text(%{public: true}), do: "Yes"
def public_text(_user_link), do: "No"
def public?(%{public: public}), do: !!public
end

View file

@ -1,4 +1,4 @@
defmodule PhilomenaWeb.UserLinkView do
defmodule PhilomenaWeb.Profile.UserLinkView do
use PhilomenaWeb, :view
def verified?(%{aasm_state: state}), do: state == "verified"