mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-23 20:18:00 +01:00
add tag alias validations
This commit is contained in:
parent
6670b050a1
commit
0d359ee81e
4 changed files with 129 additions and 58 deletions
|
@ -180,64 +180,78 @@ defmodule Philomena.Tags do
|
||||||
end
|
end
|
||||||
|
|
||||||
def alias_tag(%Tag{} = tag, attrs) do
|
def alias_tag(%Tag{} = tag, attrs) do
|
||||||
target_tag = Repo.get_by!(Tag, name: attrs["target_tag"])
|
target_tag = Repo.get_by(Tag, name: String.downcase(attrs["target_tag"]))
|
||||||
|
|
||||||
if tag.id == target_tag.id do
|
tag
|
||||||
tag
|
|> Repo.preload(:aliased_tag)
|
||||||
else
|
|> Tag.alias_changeset(target_tag)
|
||||||
filters_hidden =
|
|> Repo.update()
|
||||||
where(Filter, [f], fragment("? @> ARRAY[?]::integer[]", f.hidden_tag_ids, ^tag.id))
|
|> case do
|
||||||
|
{:ok, tag} ->
|
||||||
|
spawn(fn ->
|
||||||
|
perform_alias(tag, target_tag)
|
||||||
|
end)
|
||||||
|
|
||||||
filters_spoilered =
|
{:ok, tag}
|
||||||
where(Filter, [f], fragment("? @> ARRAY[?]::integer[]", f.spoilered_tag_ids, ^tag.id))
|
|
||||||
|
|
||||||
users_watching =
|
error ->
|
||||||
where(User, [u], fragment("? @> ARRAY[?]::integer[]", u.watched_tag_ids, ^tag.id))
|
error
|
||||||
|
|
||||||
array_replace(filters_hidden, :hidden_tag_ids, tag.id, target_tag.id)
|
|
||||||
array_replace(filters_spoilered, :spoilered_tag_ids, tag.id, target_tag.id)
|
|
||||||
array_replace(users_watching, :watched_tag_ids, tag.id, target_tag.id)
|
|
||||||
|
|
||||||
# Manual insert all because ecto won't do it for us
|
|
||||||
Repo.query!(
|
|
||||||
"INSERT INTO image_taggings (image_id, tag_id) " <>
|
|
||||||
"SELECT i.id, #{target_tag.id} FROM images i " <>
|
|
||||||
"INNER JOIN image_taggings it on it.image_id = i.id " <>
|
|
||||||
"WHERE it.tag_id = #{tag.id} " <>
|
|
||||||
"ON CONFLICT DO NOTHING"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Delete taggings on the source tag
|
|
||||||
Tagging
|
|
||||||
|> where(tag_id: ^tag.id)
|
|
||||||
|> Repo.delete_all()
|
|
||||||
|
|
||||||
# Update other assocations
|
|
||||||
UserLink
|
|
||||||
|> where(tag_id: ^tag.id)
|
|
||||||
|> Repo.update_all(set: [tag_id: target_tag.id])
|
|
||||||
|
|
||||||
DnpEntry
|
|
||||||
|> where(tag_id: ^tag.id)
|
|
||||||
|> Repo.update_all(set: [tag_id: target_tag.id])
|
|
||||||
|
|
||||||
Channel
|
|
||||||
|> where(associated_artist_tag_id: ^tag.id)
|
|
||||||
|> Repo.update_all(set: [associated_artist_tag_id: target_tag.id])
|
|
||||||
|
|
||||||
# Update counter
|
|
||||||
Tag
|
|
||||||
|> where(id: ^tag.id)
|
|
||||||
|> Repo.update_all(
|
|
||||||
set: [images_count: 0, aliased_tag_id: target_tag.id, updated_at: DateTime.utc_now()]
|
|
||||||
)
|
|
||||||
|
|
||||||
# Finally, reindex
|
|
||||||
reindex_tag_images(target_tag)
|
|
||||||
reindex_tags([tag, target_tag])
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp perform_alias(tag, target_tag) do
|
||||||
|
filters_hidden =
|
||||||
|
where(Filter, [f], fragment("? @> ARRAY[?]::integer[]", f.hidden_tag_ids, ^tag.id))
|
||||||
|
|
||||||
|
filters_spoilered =
|
||||||
|
where(Filter, [f], fragment("? @> ARRAY[?]::integer[]", f.spoilered_tag_ids, ^tag.id))
|
||||||
|
|
||||||
|
users_watching =
|
||||||
|
where(User, [u], fragment("? @> ARRAY[?]::integer[]", u.watched_tag_ids, ^tag.id))
|
||||||
|
|
||||||
|
array_replace(filters_hidden, :hidden_tag_ids, tag.id, target_tag.id)
|
||||||
|
array_replace(filters_spoilered, :spoilered_tag_ids, tag.id, target_tag.id)
|
||||||
|
array_replace(users_watching, :watched_tag_ids, tag.id, target_tag.id)
|
||||||
|
|
||||||
|
# Manual insert all because ecto won't do it for us
|
||||||
|
Repo.query!(
|
||||||
|
"INSERT INTO image_taggings (image_id, tag_id) " <>
|
||||||
|
"SELECT i.id, #{target_tag.id} FROM images i " <>
|
||||||
|
"INNER JOIN image_taggings it on it.image_id = i.id " <>
|
||||||
|
"WHERE it.tag_id = #{tag.id} " <>
|
||||||
|
"ON CONFLICT DO NOTHING"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Delete taggings on the source tag
|
||||||
|
Tagging
|
||||||
|
|> where(tag_id: ^tag.id)
|
||||||
|
|> Repo.delete_all()
|
||||||
|
|
||||||
|
# Update other assocations
|
||||||
|
UserLink
|
||||||
|
|> where(tag_id: ^tag.id)
|
||||||
|
|> Repo.update_all(set: [tag_id: target_tag.id])
|
||||||
|
|
||||||
|
DnpEntry
|
||||||
|
|> where(tag_id: ^tag.id)
|
||||||
|
|> Repo.update_all(set: [tag_id: target_tag.id])
|
||||||
|
|
||||||
|
Channel
|
||||||
|
|> where(associated_artist_tag_id: ^tag.id)
|
||||||
|
|> Repo.update_all(set: [associated_artist_tag_id: target_tag.id])
|
||||||
|
|
||||||
|
# Update counter
|
||||||
|
Tag
|
||||||
|
|> where(id: ^tag.id)
|
||||||
|
|> Repo.update_all(
|
||||||
|
set: [images_count: 0, aliased_tag_id: target_tag.id, updated_at: DateTime.utc_now()]
|
||||||
|
)
|
||||||
|
|
||||||
|
# Finally, reindex
|
||||||
|
reindex_tag_images(target_tag)
|
||||||
|
reindex_tags([tag, target_tag])
|
||||||
|
end
|
||||||
|
|
||||||
def reindex_tag_images(%Tag{} = tag) do
|
def reindex_tag_images(%Tag{} = tag) do
|
||||||
# First recount the tag
|
# First recount the tag
|
||||||
image_count =
|
image_count =
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
defmodule Philomena.Tags.Tag do
|
defmodule Philomena.Tags.Tag do
|
||||||
use Ecto.Schema
|
use Ecto.Schema
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
import Ecto.Query
|
||||||
|
|
||||||
alias Philomena.Channels.Channel
|
alias Philomena.Channels.Channel
|
||||||
alias Philomena.DnpEntries.DnpEntry
|
alias Philomena.DnpEntries.DnpEntry
|
||||||
alias Philomena.UserLinks.UserLink
|
alias Philomena.UserLinks.UserLink
|
||||||
alias Philomena.Tags.Tag
|
alias Philomena.Tags.Tag
|
||||||
alias Philomena.Slug
|
alias Philomena.Slug
|
||||||
|
alias Philomena.Repo
|
||||||
|
|
||||||
@namespaces [
|
@namespaces [
|
||||||
"artist",
|
"artist",
|
||||||
|
@ -111,6 +113,15 @@ defmodule Philomena.Tags.Tag do
|
||||||
|> put_change(:image, nil)
|
|> put_change(:image, nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def alias_changeset(tag, target_tag) do
|
||||||
|
change(tag)
|
||||||
|
|> put_assoc(:aliased_tag, target_tag)
|
||||||
|
|> validate_required([:aliased_tag])
|
||||||
|
|> validate_not_aliased_to_self()
|
||||||
|
|> validate_alias_not_transitive()
|
||||||
|
|> validate_incoming_aliases()
|
||||||
|
end
|
||||||
|
|
||||||
def unalias_changeset(tag) do
|
def unalias_changeset(tag) do
|
||||||
change(tag, aliased_tag_id: nil)
|
change(tag, aliased_tag_id: nil)
|
||||||
end
|
end
|
||||||
|
@ -245,4 +256,46 @@ defmodule Philomena.Tags.Tag do
|
||||||
category -> change(changeset, category: category)
|
category -> change(changeset, category: category)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp validate_not_aliased_to_self(changeset) do
|
||||||
|
aliased_tag = get_field(changeset, :aliased_tag)
|
||||||
|
id = get_field(changeset, :id)
|
||||||
|
|
||||||
|
case aliased_tag do
|
||||||
|
%{id: ^id} ->
|
||||||
|
add_error(changeset, :aliased_tag, "is the same tag as the source")
|
||||||
|
|
||||||
|
_tag ->
|
||||||
|
changeset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_alias_not_transitive(changeset) do
|
||||||
|
case get_field(changeset, :aliased_tag) do
|
||||||
|
%{aliased_tag_id: tag} when not is_nil(tag) ->
|
||||||
|
add_error(
|
||||||
|
changeset,
|
||||||
|
:aliased_tag,
|
||||||
|
"is itself aliased and would create a transitive alias"
|
||||||
|
)
|
||||||
|
|
||||||
|
_tag ->
|
||||||
|
changeset
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp validate_incoming_aliases(changeset) do
|
||||||
|
id = get_field(changeset, :id)
|
||||||
|
|
||||||
|
count =
|
||||||
|
Tag
|
||||||
|
|> where(aliased_tag_id: ^id)
|
||||||
|
|> Repo.aggregate(:count, :id)
|
||||||
|
|
||||||
|
if count > 0 do
|
||||||
|
add_error(changeset, :tag, "has incoming aliases and cannot be aliased")
|
||||||
|
else
|
||||||
|
changeset
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,13 +19,15 @@ defmodule PhilomenaWeb.Tag.AliasController do
|
||||||
end
|
end
|
||||||
|
|
||||||
def update(conn, %{"tag" => tag_params}) do
|
def update(conn, %{"tag" => tag_params}) do
|
||||||
spawn(fn ->
|
case Tags.alias_tag(conn.assigns.tag, tag_params) do
|
||||||
Tags.alias_tag(conn.assigns.tag, tag_params)
|
{:ok, tag} ->
|
||||||
end)
|
conn
|
||||||
|
|> put_flash(:info, "Tag alias queued.")
|
||||||
|
|> redirect(to: Routes.tag_alias_path(conn, :edit, tag))
|
||||||
|
|
||||||
conn
|
{:error, changeset} ->
|
||||||
|> put_flash(:info, "Tag alias queued.")
|
render(conn, "edit.html", changeset: changeset)
|
||||||
|> redirect(to: Routes.tag_alias_path(conn, :edit, conn.assigns.tag))
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete(conn, _params) do
|
def delete(conn, _params) do
|
||||||
|
|
|
@ -10,6 +10,8 @@ h1
|
||||||
.field
|
.field
|
||||||
=> label f, "Alias target:"
|
=> label f, "Alias target:"
|
||||||
= text_input f, :target_tag, value: alias_target(@tag), class: "input"
|
= text_input f, :target_tag, value: alias_target(@tag), class: "input"
|
||||||
|
= error_tag f, :tag
|
||||||
|
= error_tag f, :aliased_tag
|
||||||
|
|
||||||
.field
|
.field
|
||||||
=> submit "Alias tag", class: "button"
|
=> submit "Alias tag", class: "button"
|
||||||
|
|
Loading…
Reference in a new issue