mirror of
https://github.com/philomena-dev/philomena.git
synced 2025-02-17 11:04:22 +01:00
Merge pull request #302 from philomena-dev/artist-link-extraction
Artist links logic cleanup
This commit is contained in:
commit
965e660e8b
6 changed files with 204 additions and 69 deletions
|
@ -9,39 +9,19 @@ defmodule Philomena.ArtistLinks do
|
||||||
|
|
||||||
alias Philomena.ArtistLinks.ArtistLink
|
alias Philomena.ArtistLinks.ArtistLink
|
||||||
alias Philomena.ArtistLinks.AutomaticVerifier
|
alias Philomena.ArtistLinks.AutomaticVerifier
|
||||||
alias Philomena.Badges.Badge
|
alias Philomena.ArtistLinks.BadgeAwarder
|
||||||
alias Philomena.Badges.Award
|
alias Philomena.Tags
|
||||||
alias Philomena.Tags.Tag
|
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Check links pending verification to see if the user placed
|
Updates all artist links pending verification, by transitioning to link verified state
|
||||||
the appropriate code on the page.
|
or resetting next update time.
|
||||||
"""
|
"""
|
||||||
def automatic_verify! do
|
def automatic_verify! do
|
||||||
now = DateTime.utc_now() |> DateTime.truncate(:second)
|
Enum.each(AutomaticVerifier.generate_updates(), &Repo.update!/1)
|
||||||
|
|
||||||
# Automatically retry in an hour if we don't manage to
|
|
||||||
# successfully verify any given link
|
|
||||||
recheck_time = DateTime.add(now, 3600, :second)
|
|
||||||
|
|
||||||
recheck_query =
|
|
||||||
from ul in ArtistLink,
|
|
||||||
where: ul.aasm_state == "unverified",
|
|
||||||
where: ul.next_check_at < ^now
|
|
||||||
|
|
||||||
recheck_query
|
|
||||||
|> Repo.all()
|
|
||||||
|> Enum.map(fn link ->
|
|
||||||
ArtistLink.automatic_verify_changeset(
|
|
||||||
link,
|
|
||||||
AutomaticVerifier.check_link(link, recheck_time)
|
|
||||||
)
|
|
||||||
end)
|
|
||||||
|> Enum.map(&Repo.update!/1)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Gets a single artist_link.
|
Gets a single artist link.
|
||||||
|
|
||||||
Raises `Ecto.NoResultsError` if the Artist link does not exist.
|
Raises `Ecto.NoResultsError` if the Artist link does not exist.
|
||||||
|
|
||||||
|
@ -57,7 +37,7 @@ defmodule Philomena.ArtistLinks do
|
||||||
def get_artist_link!(id), do: Repo.get!(ArtistLink, id)
|
def get_artist_link!(id), do: Repo.get!(ArtistLink, id)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates a artist_link.
|
Creates an artist link.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
@ -69,7 +49,7 @@ defmodule Philomena.ArtistLinks do
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def create_artist_link(user, attrs \\ %{}) do
|
def create_artist_link(user, attrs \\ %{}) do
|
||||||
tag = fetch_tag(attrs["tag_name"])
|
tag = Tags.get_tag_or_alias_by_name(attrs["tag_name"])
|
||||||
|
|
||||||
%ArtistLink{}
|
%ArtistLink{}
|
||||||
|> ArtistLink.creation_changeset(attrs, user, tag)
|
|> ArtistLink.creation_changeset(attrs, user, tag)
|
||||||
|
@ -77,7 +57,7 @@ defmodule Philomena.ArtistLinks do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Updates a artist_link.
|
Updates an artist link.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
@ -89,47 +69,71 @@ defmodule Philomena.ArtistLinks do
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def update_artist_link(%ArtistLink{} = artist_link, attrs) do
|
def update_artist_link(%ArtistLink{} = artist_link, attrs) do
|
||||||
tag = fetch_tag(attrs["tag_name"])
|
tag = Tags.get_tag_or_alias_by_name(attrs["tag_name"])
|
||||||
|
|
||||||
artist_link
|
artist_link
|
||||||
|> ArtistLink.edit_changeset(attrs, tag)
|
|> ArtistLink.edit_changeset(attrs, tag)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
def verify_artist_link(%ArtistLink{} = artist_link, user) do
|
@doc """
|
||||||
artist_link_changeset =
|
Transitions an artist link to the verified state.
|
||||||
artist_link
|
|
||||||
|> ArtistLink.verify_changeset(user)
|
## Examples
|
||||||
|
|
||||||
|
iex> verify_artist_link(artist_link, verifying_user)
|
||||||
|
{:ok, %ArtistLink{}}
|
||||||
|
|
||||||
|
iex> verify_artist_link(artist_link, verifying_user)
|
||||||
|
:error
|
||||||
|
|
||||||
|
"""
|
||||||
|
def verify_artist_link(%ArtistLink{} = artist_link, verifying_user) do
|
||||||
|
artist_link_changeset = ArtistLink.verify_changeset(artist_link, verifying_user)
|
||||||
|
|
||||||
Multi.new()
|
Multi.new()
|
||||||
|> Multi.update(:artist_link, artist_link_changeset)
|
|> Multi.update(:artist_link, artist_link_changeset)
|
||||||
|> Multi.run(:add_award, fn repo, _changes ->
|
|> Multi.run(:add_award, fn _repo, _changes -> BadgeAwarder.award_badge(artist_link) end)
|
||||||
now = DateTime.utc_now() |> DateTime.truncate(:second)
|
|
||||||
|
|
||||||
with badge when not is_nil(badge) <- repo.get_by(limit(Badge, 1), title: "Artist"),
|
|
||||||
nil <- repo.get_by(limit(Award, 1), badge_id: badge.id, user_id: artist_link.user_id) do
|
|
||||||
%Award{
|
|
||||||
badge_id: badge.id,
|
|
||||||
user_id: artist_link.user_id,
|
|
||||||
awarded_by_id: user.id,
|
|
||||||
awarded_on: now
|
|
||||||
}
|
|
||||||
|> Award.changeset(%{})
|
|
||||||
|> repo.insert()
|
|
||||||
else
|
|
||||||
_ ->
|
|
||||||
{:ok, nil}
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
|> Repo.transaction()
|
|> Repo.transaction()
|
||||||
|
|> case do
|
||||||
|
{:ok, %{artist_link: artist_link}} ->
|
||||||
|
{:ok, artist_link}
|
||||||
|
|
||||||
|
{:error, _operation, _value, _changes} ->
|
||||||
|
:error
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Transitions an artist link to the rejected state.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> reject_artist_link(artist_link)
|
||||||
|
{:ok, %ArtistLink{}}
|
||||||
|
|
||||||
|
iex> reject_artist_link(artist_link)
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
def reject_artist_link(%ArtistLink{} = artist_link) do
|
def reject_artist_link(%ArtistLink{} = artist_link) do
|
||||||
artist_link
|
artist_link
|
||||||
|> ArtistLink.reject_changeset()
|
|> ArtistLink.reject_changeset()
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Transitions an artist link to the contacted state.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> contact_artist_link(artist_link)
|
||||||
|
{:ok, %ArtistLink{}}
|
||||||
|
|
||||||
|
iex> contact_artist_link(artist_link)
|
||||||
|
{:error, %Ecto.Changeset{}}
|
||||||
|
|
||||||
|
"""
|
||||||
def contact_artist_link(%ArtistLink{} = artist_link, user) do
|
def contact_artist_link(%ArtistLink{} = artist_link, user) do
|
||||||
artist_link
|
artist_link
|
||||||
|> ArtistLink.contact_changeset(user)
|
|> ArtistLink.contact_changeset(user)
|
||||||
|
@ -137,7 +141,7 @@ defmodule Philomena.ArtistLinks do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Deletes a ArtistLink.
|
Deletes an artist link.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
@ -153,7 +157,7 @@ defmodule Philomena.ArtistLinks do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Returns an `%Ecto.Changeset{}` for tracking artist_link changes.
|
Returns an `%Ecto.Changeset{}` for tracking artist link changes.
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
|
@ -165,24 +169,26 @@ defmodule Philomena.ArtistLinks do
|
||||||
ArtistLink.changeset(artist_link, %{})
|
ArtistLink.changeset(artist_link, %{})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Counts the number of artist links which are pending moderation action, or
|
||||||
|
nil if the user is not permitted to moderate artist links.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> count_artist_links(normal_user)
|
||||||
|
nil
|
||||||
|
|
||||||
|
iex> count_artist_links(admin_user)
|
||||||
|
0
|
||||||
|
|
||||||
|
"""
|
||||||
def count_artist_links(user) do
|
def count_artist_links(user) do
|
||||||
if Canada.Can.can?(user, :index, %ArtistLink{}) do
|
if Canada.Can.can?(user, :index, %ArtistLink{}) do
|
||||||
ArtistLink
|
ArtistLink
|
||||||
|> where([ul], ul.aasm_state in ^["unverified", "link_verified"])
|
|> where([ul], ul.aasm_state in ^["unverified", "link_verified"])
|
||||||
|> Repo.aggregate(:count, :id)
|
|> Repo.aggregate(:count)
|
||||||
else
|
else
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp fetch_tag(name) do
|
|
||||||
Tag
|
|
||||||
|> preload(:aliased_tag)
|
|
||||||
|> where(name: ^name)
|
|
||||||
|> Repo.one()
|
|
||||||
|> case do
|
|
||||||
nil -> nil
|
|
||||||
tag -> tag.aliased_tag || tag
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,5 +1,47 @@
|
||||||
defmodule Philomena.ArtistLinks.AutomaticVerifier do
|
defmodule Philomena.ArtistLinks.AutomaticVerifier do
|
||||||
def check_link(artist_link, recheck_time) do
|
@moduledoc """
|
||||||
|
Artist link automatic verification.
|
||||||
|
|
||||||
|
Artist links contain a random code which is generated when the link is created. If the user
|
||||||
|
places the code on their linked page and this verifier finds it, this expedites the process
|
||||||
|
of verifying a link for the moderator, as they can simply use the presence of the code in a
|
||||||
|
field controlled by the artist to ascertain the validity of the artist link.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Philomena.ArtistLinks.ArtistLink
|
||||||
|
alias Philomena.Repo
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Check links pending verification to see if the user placed the appropriate code on the page.
|
||||||
|
|
||||||
|
Polls each artist link in unverified state and generates a changeset to either set it to
|
||||||
|
link verified, if the code was found on the page, or reset the next check time, if the code
|
||||||
|
was not found.
|
||||||
|
|
||||||
|
Returns a list of changesets with updated links.
|
||||||
|
"""
|
||||||
|
def generate_updates do
|
||||||
|
# Automatically retry in an hour if we don't manage to
|
||||||
|
# successfully verify any given link
|
||||||
|
now = DateTime.utc_now(:second)
|
||||||
|
recheck_time = DateTime.add(now, 3600, :second)
|
||||||
|
|
||||||
|
Enum.map(links_to_check(now), fn link ->
|
||||||
|
ArtistLink.automatic_verify_changeset(link, check_link(link, recheck_time))
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp links_to_check(now) do
|
||||||
|
recheck_query =
|
||||||
|
from ul in ArtistLink,
|
||||||
|
where: ul.aasm_state == "unverified",
|
||||||
|
where: ul.next_check_at < ^now
|
||||||
|
|
||||||
|
Repo.all(recheck_query)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_link(artist_link, recheck_time) do
|
||||||
artist_link.uri
|
artist_link.uri
|
||||||
|> PhilomenaProxy.Http.get()
|
|> PhilomenaProxy.Http.get()
|
||||||
|> contains_verification_code?(artist_link.verification_code)
|
|> contains_verification_code?(artist_link.verification_code)
|
||||||
|
|
28
lib/philomena/artist_links/badge_awarder.ex
Normal file
28
lib/philomena/artist_links/badge_awarder.ex
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
defmodule Philomena.ArtistLinks.BadgeAwarder do
|
||||||
|
@moduledoc """
|
||||||
|
Handles awarding a badge to the user of an associated artist link.
|
||||||
|
"""
|
||||||
|
|
||||||
|
alias Philomena.Badges
|
||||||
|
|
||||||
|
@badge_title "Artist"
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Awards a badge to an artist with a verified link.
|
||||||
|
|
||||||
|
If the badge with the title `"Artist"` does not exist, no award will be created.
|
||||||
|
If the user already has an award with that badge title, no award will be created.
|
||||||
|
|
||||||
|
Returns `{:ok, award}`, `{:ok, nil}`, or `{:error, changeset}`. The return value is
|
||||||
|
suitable for use as the return value to an `Ecto.Multi.run/3` callback.
|
||||||
|
"""
|
||||||
|
def award_badge(artist_link) do
|
||||||
|
with badge when not is_nil(badge) <- Badges.get_badge_by_title(@badge_title),
|
||||||
|
award when is_nil(award) <- Badges.get_badge_award_for(badge, artist_link.user) do
|
||||||
|
Badges.create_badge_award(artist_link.user, artist_link.user, %{badge_id: badge.id})
|
||||||
|
else
|
||||||
|
_ ->
|
||||||
|
{:ok, nil}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -38,6 +38,22 @@ defmodule Philomena.Badges do
|
||||||
"""
|
"""
|
||||||
def get_badge!(id), do: Repo.get!(Badge, id)
|
def get_badge!(id), do: Repo.get!(Badge, id)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single badge by its title.
|
||||||
|
|
||||||
|
Returns nil if the Badge does not exist.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> get_badge_by_title("Artist")
|
||||||
|
%Badge{}
|
||||||
|
|
||||||
|
iex> get_badge_by_title("Nonexistent")
|
||||||
|
nil
|
||||||
|
|
||||||
|
"""
|
||||||
|
def get_badge_by_title(title), do: Repo.get_by(Badge, title: title)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates a badge.
|
Creates a badge.
|
||||||
|
|
||||||
|
@ -162,6 +178,24 @@ defmodule Philomena.Badges do
|
||||||
"""
|
"""
|
||||||
def get_badge_award!(id), do: Repo.get!(Award, id)
|
def get_badge_award!(id), do: Repo.get!(Award, id)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a the badge_award with the given badge type belonging to the user.
|
||||||
|
|
||||||
|
Raises nil if the Badge award does not exist.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> get_badge_award_for(badge, user)
|
||||||
|
%Award{}
|
||||||
|
|
||||||
|
iex> get_badge_award_for(badge, user)
|
||||||
|
nil
|
||||||
|
|
||||||
|
"""
|
||||||
|
def get_badge_award_for(badge, user) do
|
||||||
|
Repo.get_by(Award, badge_id: badge.id, user_id: user.id)
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates a badge_award.
|
Creates a badge_award.
|
||||||
|
|
||||||
|
|
|
@ -81,6 +81,31 @@ defmodule Philomena.Tags do
|
||||||
"""
|
"""
|
||||||
def get_tag!(id), do: Repo.get!(Tag, id)
|
def get_tag!(id), do: Repo.get!(Tag, id)
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Gets a single tag by its name, or the tag it is aliased to, if it is aliased.
|
||||||
|
|
||||||
|
Returns nil if the tag does not exist.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> get_tag_or_alias_by_name("safe")
|
||||||
|
%Tag{}
|
||||||
|
|
||||||
|
iex> get_tag_or_alias_by_name("nonexistent")
|
||||||
|
nil
|
||||||
|
|
||||||
|
"""
|
||||||
|
def get_tag_or_alias_by_name(name) do
|
||||||
|
Tag
|
||||||
|
|> where(name: ^name)
|
||||||
|
|> preload(:aliased_tag)
|
||||||
|
|> Repo.one()
|
||||||
|
|> case do
|
||||||
|
nil -> nil
|
||||||
|
tag -> tag.aliased_tag || tag
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Creates a tag.
|
Creates a tag.
|
||||||
|
|
||||||
|
|
|
@ -13,12 +13,12 @@ defmodule PhilomenaWeb.Admin.ArtistLink.VerificationController do
|
||||||
preload: [:user]
|
preload: [:user]
|
||||||
|
|
||||||
def create(conn, _params) do
|
def create(conn, _params) do
|
||||||
{:ok, result} =
|
{:ok, artist_link} =
|
||||||
ArtistLinks.verify_artist_link(conn.assigns.artist_link, conn.assigns.current_user)
|
ArtistLinks.verify_artist_link(conn.assigns.artist_link, conn.assigns.current_user)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_flash(:info, "Artist link successfully verified.")
|
|> put_flash(:info, "Artist link successfully verified.")
|
||||||
|> moderation_log(details: &log_details/2, data: result.artist_link)
|
|> moderation_log(details: &log_details/2, data: artist_link)
|
||||||
|> redirect(to: ~p"/admin/artist_links")
|
|> redirect(to: ~p"/admin/artist_links")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue