2019-08-18 18:17:05 +02:00
|
|
|
defmodule Philomena.Images do
|
|
|
|
@moduledoc """
|
|
|
|
The Images context.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import Ecto.Query, warn: false
|
2022-07-18 16:13:24 +02:00
|
|
|
require Logger
|
2019-11-24 19:36:21 +01:00
|
|
|
|
|
|
|
alias Ecto.Multi
|
2019-08-18 18:17:05 +02:00
|
|
|
alias Philomena.Repo
|
|
|
|
|
2024-05-25 20:03:45 +02:00
|
|
|
alias PhilomenaQuery.Search
|
2020-05-28 01:29:23 +02:00
|
|
|
alias Philomena.ThumbnailWorker
|
2020-12-16 16:53:26 +01:00
|
|
|
alias Philomena.ImagePurgeWorker
|
2019-12-31 00:37:41 +01:00
|
|
|
alias Philomena.DuplicateReports.DuplicateReport
|
2019-08-18 18:17:05 +02:00
|
|
|
alias Philomena.Images.Image
|
2019-12-07 06:49:20 +01:00
|
|
|
alias Philomena.Images.Uploader
|
2019-12-16 23:11:16 +01:00
|
|
|
alias Philomena.Images.Tagging
|
2020-12-16 16:53:26 +01:00
|
|
|
alias Philomena.Images.Thumbnailer
|
2023-05-29 14:20:57 +02:00
|
|
|
alias Philomena.Images.Source
|
2024-05-25 20:03:45 +02:00
|
|
|
alias Philomena.Images.SearchIndex, as: ImageIndex
|
2020-12-06 17:42:14 +01:00
|
|
|
alias Philomena.IndexWorker
|
2019-12-16 06:25:06 +01:00
|
|
|
alias Philomena.ImageFeatures.ImageFeature
|
2019-11-24 19:36:21 +01:00
|
|
|
alias Philomena.SourceChanges.SourceChange
|
2020-12-01 05:46:33 +01:00
|
|
|
alias Philomena.Notifications.Notification
|
2020-12-06 17:42:14 +01:00
|
|
|
alias Philomena.NotificationWorker
|
2019-11-24 19:36:21 +01:00
|
|
|
alias Philomena.TagChanges.TagChange
|
|
|
|
alias Philomena.Tags
|
2020-05-08 05:02:48 +02:00
|
|
|
alias Philomena.UserStatistics
|
2019-11-25 03:16:22 +01:00
|
|
|
alias Philomena.Tags.Tag
|
2019-11-29 20:29:01 +01:00
|
|
|
alias Philomena.Notifications
|
2019-12-08 05:53:18 +01:00
|
|
|
alias Philomena.Interactions
|
2020-09-06 18:37:31 +02:00
|
|
|
alias Philomena.Reports
|
2019-12-17 17:45:22 +01:00
|
|
|
alias Philomena.Reports.Report
|
2019-12-21 22:37:06 +01:00
|
|
|
alias Philomena.Comments
|
2020-10-27 02:58:58 +01:00
|
|
|
alias Philomena.Galleries.Gallery
|
|
|
|
alias Philomena.Galleries.Interaction
|
2021-02-09 23:21:30 +01:00
|
|
|
alias Philomena.Users.User
|
2019-08-18 18:17:05 +02:00
|
|
|
|
|
|
|
@doc """
|
|
|
|
Gets a single image.
|
|
|
|
|
|
|
|
Raises `Ecto.NoResultsError` if the Image does not exist.
|
|
|
|
|
|
|
|
## Examples
|
|
|
|
|
|
|
|
iex> get_image!(123)
|
|
|
|
%Image{}
|
|
|
|
|
|
|
|
iex> get_image!(456)
|
|
|
|
** (Ecto.NoResultsError)
|
|
|
|
|
|
|
|
"""
|
2019-08-18 20:14:36 +02:00
|
|
|
def get_image!(id) do
|
|
|
|
Repo.one!(Image |> where(id: ^id) |> preload(:tags))
|
|
|
|
end
|
2019-08-18 18:17:05 +02:00
|
|
|
|
2021-07-16 02:14:41 +02:00
|
|
|
@doc """
|
|
|
|
Gets the tag list for a single image.
|
|
|
|
"""
|
|
|
|
def tag_list(%Image{tags: tags}) do
|
|
|
|
tags
|
|
|
|
|> Tag.display_order()
|
|
|
|
|> Enum.map_join(", ", & &1.name)
|
|
|
|
end
|
|
|
|
|
2019-08-18 18:17:05 +02:00
|
|
|
@doc """
|
|
|
|
Creates a image.
|
|
|
|
|
|
|
|
## Examples
|
|
|
|
|
|
|
|
iex> create_image(%{field: value})
|
|
|
|
{:ok, %Image{}}
|
|
|
|
|
|
|
|
iex> create_image(%{field: bad_value})
|
|
|
|
{:error, %Ecto.Changeset{}}
|
|
|
|
|
|
|
|
"""
|
2019-11-26 05:51:17 +01:00
|
|
|
def create_image(attribution, attrs \\ %{}) do
|
|
|
|
tags = Tags.get_or_create_tags(attrs["tag_input"])
|
2021-10-10 00:50:57 +02:00
|
|
|
sources = attrs["sources"]
|
2019-11-26 05:51:17 +01:00
|
|
|
|
|
|
|
image =
|
|
|
|
%Image{}
|
|
|
|
|> Image.creation_changeset(attrs, attribution)
|
2021-10-10 00:50:57 +02:00
|
|
|
|> Image.source_changeset(attrs, [], sources)
|
2019-11-26 05:51:17 +01:00
|
|
|
|> Image.tag_changeset(attrs, [], tags)
|
2020-05-29 02:35:52 +02:00
|
|
|
|> Image.dnp_changeset(attribution[:user])
|
2019-12-07 06:49:20 +01:00
|
|
|
|> Uploader.analyze_upload(attrs)
|
2019-11-26 05:51:17 +01:00
|
|
|
|
2020-01-11 05:20:19 +01:00
|
|
|
Multi.new()
|
2019-11-26 05:51:17 +01:00
|
|
|
|> Multi.insert(:image, image)
|
2019-11-27 06:51:20 +01:00
|
|
|
|> Multi.run(:name_caches, fn repo, %{image: image} ->
|
|
|
|
image
|
|
|
|
|> Image.cache_changeset()
|
|
|
|
|> repo.update()
|
|
|
|
end)
|
2019-11-27 02:45:57 +01:00
|
|
|
|> Multi.run(:added_tag_count, fn repo, %{image: image} ->
|
|
|
|
tag_ids = image.added_tags |> Enum.map(& &1.id)
|
|
|
|
tags = Tag |> where([t], t.id in ^tag_ids)
|
|
|
|
|
|
|
|
{count, nil} = repo.update_all(tags, inc: [images_count: 1])
|
|
|
|
|
|
|
|
{:ok, count}
|
|
|
|
end)
|
2021-02-09 23:21:30 +01:00
|
|
|
|> maybe_create_subscription_on_upload(attribution[:user])
|
2020-09-06 07:30:53 +02:00
|
|
|
|> Repo.transaction()
|
2020-05-08 05:02:48 +02:00
|
|
|
|> case do
|
|
|
|
{:ok, %{image: image}} = result ->
|
2022-07-18 16:13:24 +02:00
|
|
|
async_upload(image, attrs["image"])
|
2020-05-08 05:02:48 +02:00
|
|
|
reindex_image(image)
|
|
|
|
Tags.reindex_tags(image.added_tags)
|
2022-03-24 17:31:57 +01:00
|
|
|
maybe_approve_image(image, attribution[:user])
|
2020-05-08 05:02:48 +02:00
|
|
|
|
|
|
|
result
|
|
|
|
|
|
|
|
result ->
|
|
|
|
result
|
|
|
|
end
|
2019-08-18 18:17:05 +02:00
|
|
|
end
|
|
|
|
|
2022-07-18 16:13:24 +02:00
|
|
|
defp async_upload(image, plug_upload) do
|
|
|
|
linked_pid =
|
|
|
|
spawn(fn ->
|
|
|
|
# Make sure task will finish before VM exit
|
|
|
|
Process.flag(:trap_exit, true)
|
|
|
|
|
|
|
|
# Wait to be freed up by the caller
|
|
|
|
receive do
|
|
|
|
:ready -> nil
|
|
|
|
end
|
|
|
|
|
|
|
|
# Start trying to upload
|
|
|
|
try_upload(image, 0)
|
|
|
|
end)
|
|
|
|
|
|
|
|
# Give the upload to the linked process
|
|
|
|
Plug.Upload.give_away(plug_upload, linked_pid, self())
|
|
|
|
|
|
|
|
# Free up the linked process
|
|
|
|
send(linked_pid, :ready)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp try_upload(image, retry_count) when retry_count < 100 do
|
|
|
|
try do
|
|
|
|
Uploader.persist_upload(image)
|
|
|
|
repair_image(image)
|
|
|
|
rescue
|
|
|
|
e ->
|
|
|
|
Logger.error("Upload failed: #{inspect(e)} [try ##{retry_count}]")
|
|
|
|
Process.sleep(5000)
|
|
|
|
try_upload(image, retry_count + 1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defp try_upload(image, retry_count) do
|
|
|
|
Logger.error("Aborting upload of #{image.id} after #{retry_count} retries")
|
|
|
|
end
|
|
|
|
|
2021-02-09 23:21:30 +01:00
|
|
|
defp maybe_create_subscription_on_upload(multi, %User{watch_on_upload: true} = user) do
|
|
|
|
multi
|
|
|
|
|> Multi.run(:subscribe, fn _repo, %{image: image} ->
|
|
|
|
create_subscription(image, user)
|
|
|
|
end)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp maybe_create_subscription_on_upload(multi, _user) do
|
|
|
|
multi
|
|
|
|
end
|
|
|
|
|
2022-03-24 17:31:57 +01:00
|
|
|
def approve_image(image) do
|
|
|
|
image
|
|
|
|
|> Repo.preload(:user)
|
|
|
|
|> Image.approve_changeset()
|
|
|
|
|> Repo.update()
|
|
|
|
|> case do
|
|
|
|
{:ok, image} ->
|
|
|
|
reindex_image(image)
|
|
|
|
increment_user_stats(image.user)
|
|
|
|
maybe_suggest_user_verification(image.user)
|
|
|
|
|
|
|
|
{:ok, image}
|
|
|
|
|
|
|
|
error ->
|
|
|
|
error
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2022-03-24 18:36:49 +01:00
|
|
|
defp maybe_approve_image(_image, nil), do: false
|
2022-03-24 17:31:57 +01:00
|
|
|
|
2022-03-24 18:36:49 +01:00
|
|
|
defp maybe_approve_image(_image, %User{verified: false, role: role}) when role == "user",
|
2022-03-24 17:31:57 +01:00
|
|
|
do: false
|
|
|
|
|
|
|
|
defp maybe_approve_image(image, _user), do: approve_image(image)
|
|
|
|
|
|
|
|
defp increment_user_stats(nil), do: false
|
|
|
|
|
|
|
|
defp increment_user_stats(%User{} = user) do
|
|
|
|
UserStatistics.inc_stat(user, :uploads)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp maybe_suggest_user_verification(%User{id: id, uploads_count: 5, verified: false}) do
|
|
|
|
Reports.create_system_report(
|
|
|
|
id,
|
|
|
|
"User",
|
|
|
|
"Verification",
|
|
|
|
"User has uploaded enough approved images to be considered for verification."
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
|
|
|
defp maybe_suggest_user_verification(_user), do: false
|
|
|
|
|
2023-12-04 02:56:32 +01:00
|
|
|
def count_pending_approvals(user) do
|
|
|
|
if Canada.Can.can?(user, :approve, %Image{}) do
|
|
|
|
Image
|
|
|
|
|> where(hidden_from_users: false)
|
|
|
|
|> where(approved: false)
|
|
|
|
|> Repo.aggregate(:count)
|
|
|
|
else
|
|
|
|
nil
|
|
|
|
end
|
2022-03-24 17:31:57 +01:00
|
|
|
end
|
|
|
|
|
2019-12-16 06:25:06 +01:00
|
|
|
def feature_image(featurer, %Image{} = image) do
|
2020-09-15 02:20:10 +02:00
|
|
|
%ImageFeature{user_id: featurer.id, image_id: image.id}
|
|
|
|
|> ImageFeature.changeset(%{})
|
|
|
|
|> Repo.insert()
|
2019-12-16 06:25:06 +01:00
|
|
|
end
|
|
|
|
|
2020-08-05 20:23:11 +02:00
|
|
|
def destroy_image(%Image{} = image) do
|
2020-09-06 07:30:53 +02:00
|
|
|
image
|
|
|
|
|> Image.remove_image_changeset()
|
|
|
|
|> Repo.update()
|
|
|
|
|> case do
|
|
|
|
{:ok, image} ->
|
2020-12-16 16:53:26 +01:00
|
|
|
purge_files(image, image.hidden_image_key)
|
2022-02-09 01:08:53 +01:00
|
|
|
Thumbnailer.destroy_thumbnails(image)
|
2020-08-05 20:23:11 +02:00
|
|
|
|
2020-09-06 07:30:53 +02:00
|
|
|
{:ok, image}
|
2020-08-05 20:23:11 +02:00
|
|
|
|
2020-09-06 07:30:53 +02:00
|
|
|
error ->
|
|
|
|
error
|
|
|
|
end
|
2020-08-05 20:23:11 +02:00
|
|
|
end
|
|
|
|
|
2019-12-16 06:25:06 +01:00
|
|
|
def lock_comments(%Image{} = image, locked) do
|
|
|
|
image
|
|
|
|
|> Image.lock_comments_changeset(locked)
|
|
|
|
|> Repo.update()
|
|
|
|
end
|
|
|
|
|
|
|
|
def lock_description(%Image{} = image, locked) do
|
|
|
|
image
|
|
|
|
|> Image.lock_description_changeset(locked)
|
|
|
|
|> Repo.update()
|
|
|
|
end
|
|
|
|
|
|
|
|
def lock_tags(%Image{} = image, locked) do
|
|
|
|
image
|
|
|
|
|> Image.lock_tags_changeset(locked)
|
|
|
|
|> Repo.update()
|
|
|
|
end
|
|
|
|
|
|
|
|
def remove_hash(%Image{} = image) do
|
|
|
|
image
|
|
|
|
|> Image.remove_hash_changeset()
|
|
|
|
|> Repo.update()
|
|
|
|
end
|
|
|
|
|
|
|
|
def update_scratchpad(%Image{} = image, attrs) do
|
|
|
|
image
|
|
|
|
|> Image.scratchpad_changeset(attrs)
|
|
|
|
|> Repo.update()
|
|
|
|
end
|
|
|
|
|
|
|
|
def remove_source_history(%Image{} = image) do
|
|
|
|
image
|
|
|
|
|> Repo.preload(:source_changes)
|
|
|
|
|> Image.remove_source_history_changeset()
|
|
|
|
|> Repo.update()
|
|
|
|
end
|
|
|
|
|
|
|
|
def repair_image(%Image{} = image) do
|
2019-12-31 00:34:59 +01:00
|
|
|
Image
|
|
|
|
|> where(id: ^image.id)
|
|
|
|
|> Repo.update_all(set: [thumbnails_generated: false, processed: false])
|
|
|
|
|
2020-05-28 01:29:23 +02:00
|
|
|
Exq.enqueue(Exq, queue(image.image_mime_type), ThumbnailWorker, [image.id])
|
2019-12-16 06:25:06 +01:00
|
|
|
end
|
|
|
|
|
2020-05-28 01:29:23 +02:00
|
|
|
defp queue("video/webm"), do: "videos"
|
|
|
|
defp queue(_mime_type), do: "images"
|
|
|
|
|
2019-12-16 06:25:06 +01:00
|
|
|
def update_file(%Image{} = image, attrs) do
|
2020-09-06 07:30:53 +02:00
|
|
|
image
|
|
|
|
|> Image.changeset(attrs)
|
|
|
|
|> Uploader.analyze_upload(attrs)
|
|
|
|
|> Repo.update()
|
|
|
|
|> case do
|
|
|
|
{:ok, image} ->
|
|
|
|
Uploader.persist_upload(image)
|
2019-12-16 06:25:06 +01:00
|
|
|
|
2020-09-06 07:30:53 +02:00
|
|
|
repair_image(image)
|
2020-12-16 16:53:26 +01:00
|
|
|
purge_files(image, image.hidden_image_key)
|
2020-09-06 07:30:53 +02:00
|
|
|
reindex_image(image)
|
2019-12-16 06:25:06 +01:00
|
|
|
|
2020-09-06 07:30:53 +02:00
|
|
|
{:ok, image}
|
|
|
|
|
|
|
|
error ->
|
|
|
|
error
|
|
|
|
end
|
2019-12-16 06:25:06 +01:00
|
|
|
end
|
|
|
|
|
2019-08-18 18:17:05 +02:00
|
|
|
@doc """
|
|
|
|
Updates a image.
|
|
|
|
|
|
|
|
## Examples
|
|
|
|
|
|
|
|
iex> update_image(image, %{field: new_value})
|
|
|
|
{:ok, %Image{}}
|
|
|
|
|
|
|
|
iex> update_image(image, %{field: bad_value})
|
|
|
|
{:error, %Ecto.Changeset{}}
|
|
|
|
|
|
|
|
"""
|
|
|
|
def update_image(%Image{} = image, attrs) do
|
|
|
|
image
|
|
|
|
|> Image.changeset(attrs)
|
|
|
|
|> Repo.update()
|
|
|
|
end
|
|
|
|
|
2019-11-29 06:39:15 +01:00
|
|
|
def update_description(%Image{} = image, attrs) do
|
|
|
|
image
|
|
|
|
|> Image.description_changeset(attrs)
|
|
|
|
|> Repo.update()
|
|
|
|
end
|
|
|
|
|
2021-10-09 03:33:16 +02:00
|
|
|
def update_sources(%Image{} = image, attribution, attrs) do
|
2021-10-10 00:50:57 +02:00
|
|
|
old_sources = attrs["old_sources"]
|
|
|
|
new_sources = attrs["sources"]
|
2019-11-24 19:36:21 +01:00
|
|
|
|
2020-01-11 05:20:19 +01:00
|
|
|
Multi.new()
|
2021-10-10 14:22:30 +02:00
|
|
|
|> Multi.run(:image, fn repo, _chg ->
|
|
|
|
image = repo.preload(image, [:sources])
|
|
|
|
|
|
|
|
image
|
|
|
|
|> Image.source_changeset(%{}, old_sources, new_sources)
|
|
|
|
|> repo.update()
|
|
|
|
|> case do
|
2021-11-13 18:53:35 +01:00
|
|
|
{:ok, image} ->
|
|
|
|
{:ok, {image, image.added_sources, image.removed_sources}}
|
2021-10-10 14:22:30 +02:00
|
|
|
|
2021-11-13 18:53:35 +01:00
|
|
|
error ->
|
|
|
|
error
|
|
|
|
end
|
2019-12-11 01:41:29 +01:00
|
|
|
end)
|
2021-10-09 03:33:16 +02:00
|
|
|
|> Multi.run(:added_source_changes, fn repo, %{image: {image, added_sources, _removed}} ->
|
|
|
|
source_changes =
|
|
|
|
added_sources
|
|
|
|
|> Enum.map(&source_change_attributes(attribution, image, &1, true, attribution[:user]))
|
|
|
|
|
|
|
|
{count, nil} = repo.insert_all(SourceChange, source_changes)
|
|
|
|
|
|
|
|
{:ok, count}
|
|
|
|
end)
|
|
|
|
|> Multi.run(:removed_source_changes, fn repo, %{image: {image, _added, removed_sources}} ->
|
|
|
|
source_changes =
|
|
|
|
removed_sources
|
|
|
|
|> Enum.map(&source_change_attributes(attribution, image, &1, false, attribution[:user]))
|
|
|
|
|
|
|
|
{count, nil} = repo.insert_all(SourceChange, source_changes)
|
|
|
|
|
|
|
|
{:ok, count}
|
|
|
|
end)
|
2020-09-06 07:30:53 +02:00
|
|
|
|> Repo.transaction()
|
2019-11-24 19:36:21 +01:00
|
|
|
end
|
|
|
|
|
2021-10-09 03:33:16 +02:00
|
|
|
defp source_change_attributes(attribution, image, source, added, user) do
|
|
|
|
now = DateTime.utc_now() |> DateTime.truncate(:second)
|
|
|
|
|
|
|
|
user_id =
|
|
|
|
case user do
|
|
|
|
nil -> nil
|
|
|
|
user -> user.id
|
|
|
|
end
|
|
|
|
|
|
|
|
%{
|
|
|
|
image_id: image.id,
|
2021-10-10 14:22:30 +02:00
|
|
|
source_url: source,
|
2021-10-09 03:33:16 +02:00
|
|
|
user_id: user_id,
|
|
|
|
created_at: now,
|
|
|
|
updated_at: now,
|
|
|
|
ip: attribution[:ip],
|
|
|
|
fingerprint: attribution[:fingerprint],
|
|
|
|
user_agent: attribution[:user_agent],
|
|
|
|
referrer: attribution[:referrer],
|
|
|
|
added: added
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2021-03-01 18:01:27 +01:00
|
|
|
def update_locked_tags(%Image{} = image, attrs) do
|
|
|
|
new_tags = Tags.get_or_create_tags(attrs["tag_input"])
|
|
|
|
|
|
|
|
image
|
|
|
|
|> Repo.preload(:locked_tags)
|
|
|
|
|> Image.locked_tags_changeset(attrs, new_tags)
|
|
|
|
|> Repo.update()
|
|
|
|
end
|
|
|
|
|
2019-11-24 19:36:21 +01:00
|
|
|
def update_tags(%Image{} = image, attribution, attrs) do
|
|
|
|
old_tags = Tags.get_or_create_tags(attrs["old_tag_input"])
|
|
|
|
new_tags = Tags.get_or_create_tags(attrs["tag_input"])
|
|
|
|
|
2020-01-11 05:20:19 +01:00
|
|
|
Multi.new()
|
2019-11-24 19:36:21 +01:00
|
|
|
|> Multi.run(:image, fn repo, _chg ->
|
2021-03-01 18:01:27 +01:00
|
|
|
image = repo.preload(image, [:tags, :locked_tags])
|
|
|
|
|
2019-11-24 19:36:21 +01:00
|
|
|
image
|
2021-03-01 18:01:27 +01:00
|
|
|
|> Image.tag_changeset(%{}, old_tags, new_tags, image.locked_tags)
|
2019-11-24 19:36:21 +01:00
|
|
|
|> repo.update()
|
|
|
|
|> case do
|
|
|
|
{:ok, image} ->
|
|
|
|
{:ok, {image, image.added_tags, image.removed_tags}}
|
|
|
|
|
|
|
|
error ->
|
|
|
|
error
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|> Multi.run(:added_tag_changes, fn repo, %{image: {image, added_tags, _removed}} ->
|
|
|
|
tag_changes =
|
|
|
|
added_tags
|
|
|
|
|> Enum.map(&tag_change_attributes(attribution, image, &1, true, attribution[:user]))
|
2020-01-11 05:20:19 +01:00
|
|
|
|
2019-11-24 19:36:21 +01:00
|
|
|
{count, nil} = repo.insert_all(TagChange, tag_changes)
|
|
|
|
|
|
|
|
{:ok, count}
|
|
|
|
end)
|
|
|
|
|> Multi.run(:removed_tag_changes, fn repo, %{image: {image, _added, removed_tags}} ->
|
|
|
|
tag_changes =
|
|
|
|
removed_tags
|
|
|
|
|> Enum.map(&tag_change_attributes(attribution, image, &1, false, attribution[:user]))
|
|
|
|
|
|
|
|
{count, nil} = repo.insert_all(TagChange, tag_changes)
|
|
|
|
|
|
|
|
{:ok, count}
|
|
|
|
end)
|
2019-12-23 15:46:48 +01:00
|
|
|
|> Multi.run(:added_tag_count, fn
|
|
|
|
_repo, %{image: {%{hidden_from_users: true}, _added, _removed}} ->
|
|
|
|
{:ok, 0}
|
2019-11-25 03:16:22 +01:00
|
|
|
|
2019-12-23 15:46:48 +01:00
|
|
|
repo, %{image: {_image, added_tags, _removed}} ->
|
|
|
|
tag_ids = added_tags |> Enum.map(& &1.id)
|
|
|
|
tags = Tag |> where([t], t.id in ^tag_ids)
|
2019-11-25 03:16:22 +01:00
|
|
|
|
2019-12-23 15:46:48 +01:00
|
|
|
{count, nil} = repo.update_all(tags, inc: [images_count: 1])
|
|
|
|
|
|
|
|
{:ok, count}
|
2019-11-25 03:16:22 +01:00
|
|
|
end)
|
2019-12-23 15:46:48 +01:00
|
|
|
|> Multi.run(:removed_tag_count, fn
|
|
|
|
_repo, %{image: {%{hidden_from_users: true}, _added, _removed}} ->
|
|
|
|
{:ok, 0}
|
2019-11-25 03:16:22 +01:00
|
|
|
|
2019-12-23 15:46:48 +01:00
|
|
|
repo, %{image: {_image, _added, removed_tags}} ->
|
|
|
|
tag_ids = removed_tags |> Enum.map(& &1.id)
|
|
|
|
tags = Tag |> where([t], t.id in ^tag_ids)
|
2019-11-25 03:16:22 +01:00
|
|
|
|
2019-12-23 15:46:48 +01:00
|
|
|
{count, nil} = repo.update_all(tags, inc: [images_count: -1])
|
|
|
|
|
|
|
|
{:ok, count}
|
2019-11-25 03:16:22 +01:00
|
|
|
end)
|
2020-09-06 07:30:53 +02:00
|
|
|
|> Repo.transaction()
|
2019-11-24 19:36:21 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
defp tag_change_attributes(attribution, image, tag, added, user) do
|
2021-01-18 19:01:03 +01:00
|
|
|
now = DateTime.utc_now() |> DateTime.truncate(:second)
|
2020-01-11 05:20:19 +01:00
|
|
|
|
2019-11-24 19:36:21 +01:00
|
|
|
user_id =
|
|
|
|
case user do
|
2020-01-11 05:20:19 +01:00
|
|
|
nil -> nil
|
2019-11-24 19:36:21 +01:00
|
|
|
user -> user.id
|
|
|
|
end
|
|
|
|
|
|
|
|
%{
|
|
|
|
image_id: image.id,
|
|
|
|
tag_id: tag.id,
|
|
|
|
user_id: user_id,
|
|
|
|
created_at: now,
|
2019-11-25 03:16:22 +01:00
|
|
|
updated_at: now,
|
2019-11-24 19:36:21 +01:00
|
|
|
tag_name_cache: tag.name,
|
|
|
|
ip: attribution[:ip],
|
|
|
|
fingerprint: attribution[:fingerprint],
|
|
|
|
user_agent: attribution[:user_agent],
|
|
|
|
referrer: attribution[:referrer],
|
|
|
|
added: added
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2019-12-17 19:53:41 +01:00
|
|
|
def update_uploader(%Image{} = image, attrs) do
|
|
|
|
image
|
|
|
|
|> Image.uploader_changeset(attrs)
|
|
|
|
|> Repo.update()
|
|
|
|
end
|
|
|
|
|
2020-04-30 06:14:14 +02:00
|
|
|
def update_anonymous(%Image{} = image, attrs) do
|
|
|
|
image
|
|
|
|
|> Image.anonymous_changeset(attrs)
|
|
|
|
|> Repo.update()
|
|
|
|
end
|
|
|
|
|
2020-02-01 00:50:50 +01:00
|
|
|
def update_hide_reason(%Image{} = image, attrs) do
|
|
|
|
image
|
|
|
|
|> Image.hide_reason_changeset(attrs)
|
|
|
|
|> Repo.update()
|
2020-09-06 18:37:31 +02:00
|
|
|
|> case do
|
|
|
|
{:ok, image} ->
|
|
|
|
reindex_image(image)
|
2019-12-08 05:53:18 +01:00
|
|
|
|
2020-09-06 18:37:31 +02:00
|
|
|
{:ok, image}
|
2020-01-11 05:20:19 +01:00
|
|
|
|
2020-09-06 18:37:31 +02:00
|
|
|
error ->
|
|
|
|
error
|
|
|
|
end
|
|
|
|
end
|
2019-12-17 18:13:05 +01:00
|
|
|
|
2020-09-06 18:37:31 +02:00
|
|
|
def hide_image(%Image{} = image, user, attrs) do
|
|
|
|
duplicate_reports =
|
|
|
|
DuplicateReport
|
|
|
|
|> where(state: "open")
|
|
|
|
|> where([d], d.image_id == ^image.id or d.duplicate_of_image_id == ^image.id)
|
|
|
|
|> update(set: [state: "rejected"])
|
2019-12-08 05:53:18 +01:00
|
|
|
|
2020-09-06 18:37:31 +02:00
|
|
|
image
|
|
|
|
|> Image.hide_changeset(attrs, user)
|
2020-09-07 20:50:34 +02:00
|
|
|
|> hide_image_multi(image, user, Multi.new())
|
2020-09-06 18:37:31 +02:00
|
|
|
|> Multi.update_all(:duplicate_reports, duplicate_reports, [])
|
|
|
|
|> Repo.transaction()
|
|
|
|
|> process_after_hide()
|
2019-12-08 05:53:18 +01:00
|
|
|
end
|
|
|
|
|
2020-09-07 20:50:34 +02:00
|
|
|
def merge_image(multi \\ nil, %Image{} = image, duplicate_of_image, user) do
|
2020-09-06 18:37:31 +02:00
|
|
|
multi = multi || Multi.new()
|
2019-12-28 05:27:37 +01:00
|
|
|
|
2020-09-06 18:37:31 +02:00
|
|
|
image
|
|
|
|
|> Image.merge_changeset(duplicate_of_image)
|
2020-09-07 20:50:34 +02:00
|
|
|
|> hide_image_multi(image, user, multi)
|
2020-09-06 18:37:31 +02:00
|
|
|
|> Multi.run(:first_seen_at, fn _, %{} ->
|
|
|
|
update_first_seen_at(
|
|
|
|
duplicate_of_image,
|
|
|
|
image.first_seen_at,
|
|
|
|
duplicate_of_image.first_seen_at
|
|
|
|
)
|
|
|
|
end)
|
|
|
|
|> Multi.run(:copy_tags, fn _, %{} ->
|
|
|
|
{:ok, Tags.copy_tags(image, duplicate_of_image)}
|
|
|
|
end)
|
2023-05-28 18:12:53 +02:00
|
|
|
|> Multi.run(:migrate_sources, fn repo, %{} ->
|
|
|
|
{:ok,
|
|
|
|
migrate_sources(
|
|
|
|
repo.preload(image, [:sources]),
|
|
|
|
repo.preload(duplicate_of_image, [:sources])
|
|
|
|
)}
|
|
|
|
end)
|
2020-09-06 18:37:31 +02:00
|
|
|
|> Multi.run(:migrate_comments, fn _, %{} ->
|
|
|
|
{:ok, Comments.migrate_comments(image, duplicate_of_image)}
|
|
|
|
end)
|
2020-09-06 19:30:28 +02:00
|
|
|
|> Multi.run(:migrate_subscriptions, fn _, %{} ->
|
|
|
|
{:ok, migrate_subscriptions(image, duplicate_of_image)}
|
|
|
|
end)
|
2020-09-06 18:37:31 +02:00
|
|
|
|> Multi.run(:migrate_interactions, fn _, %{} ->
|
|
|
|
{:ok, Interactions.migrate_interactions(image, duplicate_of_image)}
|
|
|
|
end)
|
|
|
|
|> Repo.transaction()
|
|
|
|
|> process_after_hide()
|
|
|
|
|> case do
|
|
|
|
{:ok, result} ->
|
|
|
|
reindex_image(duplicate_of_image)
|
2020-09-06 19:38:30 +02:00
|
|
|
Comments.reindex_comments(duplicate_of_image)
|
2020-09-10 04:47:35 +02:00
|
|
|
notify_merge(image, duplicate_of_image)
|
2020-09-06 18:37:31 +02:00
|
|
|
|
|
|
|
{:ok, result}
|
|
|
|
|
|
|
|
error ->
|
|
|
|
error
|
|
|
|
end
|
2019-12-25 14:48:44 +01:00
|
|
|
end
|
|
|
|
|
2020-09-07 20:50:34 +02:00
|
|
|
defp hide_image_multi(changeset, image, user, multi) do
|
2019-12-17 17:45:22 +01:00
|
|
|
reports =
|
|
|
|
Report
|
|
|
|
|> where(reportable_type: "Image", reportable_id: ^image.id)
|
|
|
|
|> select([r], r.id)
|
2020-09-07 20:50:34 +02:00
|
|
|
|> update(set: [open: false, state: "closed", admin_id: ^user.id])
|
2019-12-17 17:45:22 +01:00
|
|
|
|
2020-10-27 02:58:58 +01:00
|
|
|
galleries =
|
|
|
|
Gallery
|
|
|
|
|> join(:inner, [g], gi in assoc(g, :interactions), on: gi.image_id == ^image.id)
|
|
|
|
|> update(inc: [image_count: -1])
|
|
|
|
|
|
|
|
gallery_interactions = where(Interaction, image_id: ^image.id)
|
|
|
|
|
2020-09-06 18:37:31 +02:00
|
|
|
multi
|
2019-12-08 05:53:18 +01:00
|
|
|
|> Multi.update(:image, changeset)
|
2019-12-17 17:45:22 +01:00
|
|
|
|> Multi.update_all(:reports, reports, [])
|
2020-10-27 02:58:58 +01:00
|
|
|
|> Multi.update_all(:galleries, galleries, [])
|
|
|
|
|> Multi.delete_all(:gallery_interactions, gallery_interactions, [])
|
2019-12-08 05:53:18 +01:00
|
|
|
|> Multi.run(:tags, fn repo, %{image: image} ->
|
|
|
|
image = Repo.preload(image, :tags, force: true)
|
|
|
|
|
|
|
|
# I'm not convinced this is a good idea. It leads
|
|
|
|
# to way too much drift, and the index has to be
|
|
|
|
# maintained.
|
|
|
|
tag_ids = Enum.map(image.tags, & &1.id)
|
|
|
|
query = where(Tag, [t], t.id in ^tag_ids)
|
|
|
|
|
|
|
|
repo.update_all(query, inc: [images_count: -1])
|
|
|
|
|
|
|
|
{:ok, image.tags}
|
|
|
|
end)
|
2020-09-06 18:37:31 +02:00
|
|
|
end
|
2019-12-08 05:53:18 +01:00
|
|
|
|
2020-09-06 18:37:31 +02:00
|
|
|
defp process_after_hide(result) do
|
|
|
|
case result do
|
|
|
|
{:ok, %{image: image, tags: tags, reports: {_count, reports}} = result} ->
|
2022-07-01 18:24:51 +02:00
|
|
|
spawn(fn ->
|
|
|
|
Thumbnailer.hide_thumbnails(image, image.hidden_image_key)
|
|
|
|
purge_files(image, image.hidden_image_key)
|
|
|
|
end)
|
2020-09-06 18:37:31 +02:00
|
|
|
|
2020-09-06 19:38:30 +02:00
|
|
|
Comments.reindex_comments(image)
|
2020-09-06 18:37:31 +02:00
|
|
|
Reports.reindex_reports(reports)
|
|
|
|
Tags.reindex_tags(tags)
|
|
|
|
reindex_image(image)
|
|
|
|
reindex_copied_tags(result)
|
|
|
|
|
|
|
|
{:ok, result}
|
|
|
|
|
|
|
|
error ->
|
|
|
|
error
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
defp reindex_copied_tags(%{copy_tags: tags}), do: Tags.reindex_tags(tags)
|
|
|
|
defp reindex_copied_tags(_result), do: nil
|
|
|
|
|
|
|
|
defp update_first_seen_at(image, time_1, time_2) do
|
|
|
|
min_time =
|
2021-01-18 19:01:03 +01:00
|
|
|
case DateTime.compare(time_1, time_2) do
|
2020-09-06 18:37:31 +02:00
|
|
|
:gt -> time_2
|
|
|
|
_ -> time_1
|
|
|
|
end
|
|
|
|
|
|
|
|
Image
|
|
|
|
|> where(id: ^image.id)
|
|
|
|
|> Repo.update_all(set: [first_seen_at: min_time])
|
|
|
|
|
|
|
|
{:ok, image}
|
2019-12-08 05:53:18 +01:00
|
|
|
end
|
|
|
|
|
2019-12-09 05:41:35 +01:00
|
|
|
def unhide_image(%Image{hidden_from_users: true} = image) do
|
2019-12-08 05:53:18 +01:00
|
|
|
key = image.hidden_image_key
|
|
|
|
|
2020-01-11 05:20:19 +01:00
|
|
|
Multi.new()
|
2019-12-08 05:53:18 +01:00
|
|
|
|> Multi.update(:image, Image.unhide_changeset(image))
|
|
|
|
|> Multi.run(:tags, fn repo, %{image: image} ->
|
|
|
|
image = Repo.preload(image, :tags, force: true)
|
|
|
|
|
|
|
|
tag_ids = Enum.map(image.tags, & &1.id)
|
|
|
|
query = where(Tag, [t], t.id in ^tag_ids)
|
|
|
|
|
|
|
|
repo.update_all(query, inc: [images_count: 1])
|
|
|
|
|
|
|
|
{:ok, image.tags}
|
|
|
|
end)
|
2020-09-06 18:37:31 +02:00
|
|
|
|> Repo.transaction()
|
|
|
|
|> case do
|
|
|
|
{:ok, %{image: image, tags: tags}} ->
|
2022-07-01 18:24:51 +02:00
|
|
|
spawn(fn ->
|
|
|
|
Thumbnailer.unhide_thumbnails(image, key)
|
|
|
|
end)
|
2019-12-08 05:53:18 +01:00
|
|
|
|
2020-09-06 18:37:31 +02:00
|
|
|
reindex_image(image)
|
2020-12-16 16:53:26 +01:00
|
|
|
purge_files(image, image.hidden_image_key)
|
2020-09-06 19:38:30 +02:00
|
|
|
Comments.reindex_comments(image)
|
2020-09-06 18:37:31 +02:00
|
|
|
Tags.reindex_tags(tags)
|
|
|
|
|
|
|
|
{:ok, image}
|
|
|
|
|
|
|
|
error ->
|
|
|
|
error
|
|
|
|
end
|
2019-12-08 05:53:18 +01:00
|
|
|
end
|
2020-01-11 05:20:19 +01:00
|
|
|
|
2019-12-09 05:41:35 +01:00
|
|
|
def unhide_image(image), do: {:ok, image}
|
2019-12-08 05:53:18 +01:00
|
|
|
|
2019-12-16 23:11:16 +01:00
|
|
|
def batch_update(image_ids, added_tags, removed_tags, tag_change_attributes) do
|
2019-12-23 15:46:48 +01:00
|
|
|
image_ids =
|
|
|
|
Image
|
|
|
|
|> where([i], i.id in ^image_ids and i.hidden_from_users == false)
|
|
|
|
|> select([i], i.id)
|
|
|
|
|> Repo.all()
|
|
|
|
|
2019-12-16 23:11:16 +01:00
|
|
|
added_tags = Enum.map(added_tags, & &1.id)
|
|
|
|
removed_tags = Enum.map(removed_tags, & &1.id)
|
|
|
|
|
|
|
|
# Change everything in one go, ignoring any validation errors
|
|
|
|
|
|
|
|
# Note: computing the Cartesian product
|
|
|
|
insertions =
|
|
|
|
for tag_id <- added_tags, image_id <- image_ids do
|
|
|
|
%{tag_id: tag_id, image_id: image_id}
|
|
|
|
end
|
|
|
|
|
|
|
|
deletions =
|
|
|
|
Tagging
|
|
|
|
|> where([t], t.image_id in ^image_ids and t.tag_id in ^removed_tags)
|
|
|
|
|> select([t], [t.image_id, t.tag_id])
|
|
|
|
|
2021-01-18 19:01:03 +01:00
|
|
|
now = DateTime.utc_now() |> DateTime.truncate(:second)
|
2019-12-16 23:11:16 +01:00
|
|
|
tag_change_attributes = Map.merge(tag_change_attributes, %{created_at: now, updated_at: now})
|
2020-08-15 03:04:12 +02:00
|
|
|
tag_attributes = %{name: "", slug: "", created_at: now, updated_at: now}
|
2019-12-16 23:11:16 +01:00
|
|
|
|
2020-01-11 05:20:19 +01:00
|
|
|
Repo.transaction(fn ->
|
2020-08-15 03:04:12 +02:00
|
|
|
{_count, inserted} =
|
2020-01-11 05:20:19 +01:00
|
|
|
Repo.insert_all(Tagging, insertions,
|
|
|
|
on_conflict: :nothing,
|
|
|
|
returning: [:image_id, :tag_id]
|
|
|
|
)
|
|
|
|
|
2020-08-15 03:04:12 +02:00
|
|
|
{_count, deleted} = Repo.delete_all(deletions)
|
2019-12-16 23:11:16 +01:00
|
|
|
|
|
|
|
inserted = Enum.map(inserted, &[&1.image_id, &1.tag_id])
|
|
|
|
|
2020-01-11 05:20:19 +01:00
|
|
|
added_changes =
|
|
|
|
Enum.map(inserted, fn [image_id, tag_id] ->
|
|
|
|
Map.merge(tag_change_attributes, %{image_id: image_id, tag_id: tag_id, added: true})
|
|
|
|
end)
|
2019-12-16 23:11:16 +01:00
|
|
|
|
2020-01-11 05:20:19 +01:00
|
|
|
removed_changes =
|
|
|
|
Enum.map(deleted, fn [image_id, tag_id] ->
|
|
|
|
Map.merge(tag_change_attributes, %{image_id: image_id, tag_id: tag_id, added: false})
|
|
|
|
end)
|
2019-12-16 23:11:16 +01:00
|
|
|
|
|
|
|
changes = added_changes ++ removed_changes
|
|
|
|
|
|
|
|
Repo.insert_all(TagChange, changes)
|
2020-08-15 03:04:12 +02:00
|
|
|
|
|
|
|
# In order to merge into the existing tables here in one go, insert_all
|
|
|
|
# is used with a query that is guaranteed to conflict on every row by
|
|
|
|
# using the primary key.
|
|
|
|
|
|
|
|
added_upserts =
|
|
|
|
inserted
|
|
|
|
|> Enum.group_by(fn [_image_id, tag_id] -> tag_id end)
|
|
|
|
|> Enum.map(fn {tag_id, instances} ->
|
|
|
|
Map.merge(tag_attributes, %{id: tag_id, images_count: length(instances)})
|
|
|
|
end)
|
|
|
|
|
|
|
|
removed_upserts =
|
|
|
|
deleted
|
|
|
|
|> Enum.group_by(fn [_image_id, tag_id] -> tag_id end)
|
|
|
|
|> Enum.map(fn {tag_id, instances} ->
|
|
|
|
Map.merge(tag_attributes, %{id: tag_id, images_count: -length(instances)})
|
|
|
|
end)
|
|
|
|
|
|
|
|
update_query = update(Tag, inc: [images_count: fragment("EXCLUDED.images_count")])
|
|
|
|
|
|
|
|
upserts = added_upserts ++ removed_upserts
|
|
|
|
|
|
|
|
Repo.insert_all(Tag, upserts, on_conflict: update_query, conflict_target: [:id])
|
2020-01-11 05:20:19 +01:00
|
|
|
end)
|
2020-05-08 05:02:48 +02:00
|
|
|
|> case do
|
|
|
|
{:ok, _} = result ->
|
|
|
|
reindex_images(image_ids)
|
2020-05-29 00:59:43 +02:00
|
|
|
Tags.reindex_tags(Enum.map(added_tags ++ removed_tags, &%{id: &1}))
|
2020-05-08 05:02:48 +02:00
|
|
|
|
|
|
|
result
|
|
|
|
|
|
|
|
result ->
|
|
|
|
result
|
|
|
|
end
|
2019-12-16 23:11:16 +01:00
|
|
|
end
|
|
|
|
|
2019-08-18 18:17:05 +02:00
|
|
|
@doc """
|
|
|
|
Deletes a Image.
|
|
|
|
|
|
|
|
## Examples
|
|
|
|
|
|
|
|
iex> delete_image(image)
|
|
|
|
{:ok, %Image{}}
|
|
|
|
|
|
|
|
iex> delete_image(image)
|
|
|
|
{:error, %Ecto.Changeset{}}
|
|
|
|
|
|
|
|
"""
|
|
|
|
def delete_image(%Image{} = image) do
|
|
|
|
Repo.delete(image)
|
|
|
|
end
|
|
|
|
|
|
|
|
@doc """
|
|
|
|
Returns an `%Ecto.Changeset{}` for tracking image changes.
|
|
|
|
|
|
|
|
## Examples
|
|
|
|
|
|
|
|
iex> change_image(image)
|
|
|
|
%Ecto.Changeset{source: %Image{}}
|
|
|
|
|
|
|
|
"""
|
|
|
|
def change_image(%Image{} = image) do
|
|
|
|
Image.changeset(image, %{})
|
|
|
|
end
|
2019-08-28 19:19:27 +02:00
|
|
|
|
2020-05-03 00:17:55 +02:00
|
|
|
def user_name_reindex(old_name, new_name) do
|
|
|
|
data = ImageIndex.user_name_update_by_query(old_name, new_name)
|
|
|
|
|
2024-05-25 20:03:45 +02:00
|
|
|
Search.update_by_query(Image, data.query, data.set_replacements, data.replacements)
|
2020-05-03 00:17:55 +02:00
|
|
|
end
|
|
|
|
|
2019-11-17 03:20:33 +01:00
|
|
|
def reindex_image(%Image{} = image) do
|
2020-12-06 17:42:14 +01:00
|
|
|
Exq.enqueue(Exq, "indexing", IndexWorker, ["Images", "id", [image.id]])
|
2019-12-05 05:12:49 +01:00
|
|
|
|
|
|
|
image
|
|
|
|
end
|
|
|
|
|
|
|
|
def reindex_images(image_ids) do
|
2020-12-06 17:42:14 +01:00
|
|
|
Exq.enqueue(Exq, "indexing", IndexWorker, ["Images", "id", image_ids])
|
2019-11-17 03:20:33 +01:00
|
|
|
|
2019-12-05 05:12:49 +01:00
|
|
|
image_ids
|
2019-11-17 03:20:33 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
def indexing_preloads do
|
2020-01-11 05:20:19 +01:00
|
|
|
[
|
|
|
|
:user,
|
|
|
|
:favers,
|
|
|
|
:downvoters,
|
|
|
|
:upvoters,
|
|
|
|
:hiders,
|
|
|
|
:deleter,
|
|
|
|
:gallery_interactions,
|
2021-10-09 03:33:16 +02:00
|
|
|
:sources,
|
2020-01-11 05:20:19 +01:00
|
|
|
tags: [:aliases, :aliased_tag]
|
|
|
|
]
|
2019-11-17 03:20:33 +01:00
|
|
|
end
|
|
|
|
|
2020-12-06 17:42:14 +01:00
|
|
|
def perform_reindex(column, condition) do
|
|
|
|
Image
|
|
|
|
|> preload(^indexing_preloads())
|
|
|
|
|> where([i], field(i, ^column) in ^condition)
|
2024-05-25 20:03:45 +02:00
|
|
|
|> Search.reindex(Image)
|
2020-12-06 17:42:14 +01:00
|
|
|
end
|
|
|
|
|
2020-12-16 16:53:26 +01:00
|
|
|
def purge_files(image, hidden_key) do
|
|
|
|
files =
|
|
|
|
if is_nil(hidden_key) do
|
|
|
|
Thumbnailer.thumbnail_urls(image, nil)
|
|
|
|
else
|
|
|
|
Thumbnailer.thumbnail_urls(image, hidden_key) ++
|
|
|
|
Thumbnailer.thumbnail_urls(image, nil)
|
|
|
|
end
|
|
|
|
|
|
|
|
Exq.enqueue(Exq, "indexing", ImagePurgeWorker, [files])
|
|
|
|
end
|
|
|
|
|
|
|
|
def perform_purge(files) do
|
2022-02-09 01:08:53 +01:00
|
|
|
{_out, 0} = System.cmd("purge-cache", [Jason.encode!(%{files: files})])
|
|
|
|
|
|
|
|
:ok
|
2020-12-16 16:53:26 +01:00
|
|
|
end
|
|
|
|
|
2019-08-28 19:19:27 +02:00
|
|
|
alias Philomena.Images.Subscription
|
|
|
|
|
2019-11-17 19:18:21 +01:00
|
|
|
def subscribed?(_image, nil), do: false
|
2020-01-11 05:20:19 +01:00
|
|
|
|
2019-11-17 18:50:42 +01:00
|
|
|
def subscribed?(image, user) do
|
|
|
|
Subscription
|
|
|
|
|> where(image_id: ^image.id, user_id: ^user.id)
|
|
|
|
|> Repo.exists?()
|
2019-08-28 19:19:27 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
@doc """
|
|
|
|
Creates a subscription.
|
|
|
|
|
|
|
|
## Examples
|
|
|
|
|
|
|
|
iex> create_subscription(%{field: value})
|
|
|
|
{:ok, %Subscription{}}
|
|
|
|
|
|
|
|
iex> create_subscription(%{field: bad_value})
|
|
|
|
{:error, %Ecto.Changeset{}}
|
|
|
|
|
|
|
|
"""
|
2019-11-18 05:47:09 +01:00
|
|
|
def create_subscription(_image, nil), do: {:ok, nil}
|
2020-01-11 05:20:19 +01:00
|
|
|
|
2019-11-17 18:50:42 +01:00
|
|
|
def create_subscription(image, user) do
|
|
|
|
%Subscription{image_id: image.id, user_id: user.id}
|
|
|
|
|> Subscription.changeset(%{})
|
|
|
|
|> Repo.insert(on_conflict: :nothing)
|
2019-08-28 19:19:27 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
@doc """
|
2020-09-06 19:30:28 +02:00
|
|
|
Deletes a subscription.
|
2019-08-28 19:19:27 +02:00
|
|
|
|
|
|
|
## Examples
|
|
|
|
|
|
|
|
iex> delete_subscription(subscription)
|
|
|
|
{:ok, %Subscription{}}
|
|
|
|
|
|
|
|
iex> delete_subscription(subscription)
|
|
|
|
{:error, %Ecto.Changeset{}}
|
|
|
|
|
|
|
|
"""
|
2019-11-17 18:50:42 +01:00
|
|
|
def delete_subscription(image, user) do
|
2019-11-29 20:29:01 +01:00
|
|
|
clear_notification(image, user)
|
|
|
|
|
2019-11-17 18:50:42 +01:00
|
|
|
%Subscription{image_id: image.id, user_id: user.id}
|
|
|
|
|> Repo.delete()
|
2019-08-28 19:19:27 +02:00
|
|
|
end
|
2019-11-29 20:29:01 +01:00
|
|
|
|
2020-09-06 19:30:28 +02:00
|
|
|
def migrate_subscriptions(source, target) do
|
2020-09-08 01:35:19 +02:00
|
|
|
subscriptions =
|
|
|
|
Subscription
|
|
|
|
|> where(image_id: ^source.id)
|
|
|
|
|> select([s], %{image_id: type(^target.id, :integer), user_id: s.user_id})
|
|
|
|
|> Repo.all()
|
|
|
|
|
2020-12-01 05:46:33 +01:00
|
|
|
Repo.insert_all(Subscription, subscriptions, on_conflict: :nothing)
|
|
|
|
|
|
|
|
{count, nil} =
|
|
|
|
Notification
|
|
|
|
|> where(actor_type: "Image", actor_id: ^source.id)
|
2020-12-02 19:43:32 +01:00
|
|
|
|> Repo.delete_all()
|
2020-09-08 01:35:19 +02:00
|
|
|
|
|
|
|
{:ok, count}
|
2020-09-06 19:30:28 +02:00
|
|
|
end
|
|
|
|
|
2023-05-28 18:12:53 +02:00
|
|
|
def migrate_sources(source, target) do
|
|
|
|
sources =
|
|
|
|
(source.sources ++ target.sources)
|
2023-05-29 14:20:57 +02:00
|
|
|
|> Enum.map(fn s -> %Source{image_id: target.id, source: s.source} end)
|
2023-05-28 18:12:53 +02:00
|
|
|
|> Enum.uniq()
|
|
|
|
|> Enum.take(10)
|
|
|
|
|
|
|
|
target
|
|
|
|
|> Image.sources_changeset(sources)
|
|
|
|
|> Repo.update()
|
|
|
|
end
|
|
|
|
|
2020-09-10 04:47:35 +02:00
|
|
|
def notify_merge(source, target) do
|
2020-12-06 17:42:14 +01:00
|
|
|
Exq.enqueue(Exq, "notifications", NotificationWorker, ["Images", [source.id, target.id]])
|
|
|
|
end
|
|
|
|
|
|
|
|
def perform_notify([source_id, target_id]) do
|
|
|
|
target = get_image!(target_id)
|
|
|
|
|
|
|
|
subscriptions =
|
|
|
|
target
|
|
|
|
|> Repo.preload(:subscriptions)
|
|
|
|
|> Map.fetch!(:subscriptions)
|
|
|
|
|
|
|
|
Notifications.notify(
|
|
|
|
nil,
|
|
|
|
subscriptions,
|
|
|
|
%{
|
|
|
|
actor_id: target.id,
|
|
|
|
actor_type: "Image",
|
|
|
|
actor_child_id: nil,
|
|
|
|
actor_child_type: nil,
|
|
|
|
action: "merged ##{source_id} into"
|
|
|
|
}
|
|
|
|
)
|
2020-09-10 04:47:35 +02:00
|
|
|
end
|
|
|
|
|
2019-12-01 06:03:45 +01:00
|
|
|
def clear_notification(_image, nil), do: nil
|
2020-01-11 05:20:19 +01:00
|
|
|
|
2019-11-29 20:29:01 +01:00
|
|
|
def clear_notification(image, user) do
|
|
|
|
Notifications.delete_unread_notification("Image", image.id, user)
|
|
|
|
end
|
2019-08-18 18:17:05 +02:00
|
|
|
end
|