mirror of
https://github.com/philomena-dev/philomena.git
synced 2025-02-08 07:06:44 +01:00
Merge pull request #406 from philomena-dev/docs
Add doc comments to context modules
This commit is contained in:
commit
d6e360a3a5
12 changed files with 1586 additions and 33 deletions
|
@ -119,6 +119,20 @@ defmodule Philomena.Comments do
|
|||
Repo.delete(comment)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Hides a comment and handles associated reports.
|
||||
|
||||
## Parameters
|
||||
- comment: The comment to hide
|
||||
- attrs: Attributes for the hide operation
|
||||
- user: The user performing the hide action
|
||||
|
||||
## Examples
|
||||
|
||||
iex> hide_comment(comment, %{staff_note: "Rule violation"}, user)
|
||||
{:ok, %Comment{}}
|
||||
|
||||
"""
|
||||
def hide_comment(%Comment{} = comment, attrs, user) do
|
||||
report_query = Reports.close_report_query({"Comment", comment.id}, user)
|
||||
comment = Comment.hide_changeset(comment, attrs, user)
|
||||
|
@ -139,6 +153,15 @@ defmodule Philomena.Comments do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unhides a previously hidden comment.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unhide_comment(comment)
|
||||
{:ok, %Comment{}}
|
||||
|
||||
"""
|
||||
def unhide_comment(%Comment{} = comment) do
|
||||
comment
|
||||
|> Comment.unhide_changeset()
|
||||
|
@ -154,12 +177,35 @@ defmodule Philomena.Comments do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Marks a comment as destroyed and removes its text (hard deletion).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> destroy_comment(comment)
|
||||
{:ok, %Comment{}}
|
||||
|
||||
"""
|
||||
def destroy_comment(%Comment{} = comment) do
|
||||
comment
|
||||
|> Comment.destroy_changeset()
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Approves a comment, closes associated reports, and increments the user comments
|
||||
posted count.
|
||||
|
||||
## Parameters
|
||||
- comment: The comment to approve
|
||||
- user: The user performing the approval
|
||||
|
||||
## Examples
|
||||
|
||||
iex> approve_comment(comment, user)
|
||||
{:ok, %Comment{}}
|
||||
|
||||
"""
|
||||
def approve_comment(%Comment{} = comment, user) do
|
||||
report_query = Reports.close_report_query({"Comment", comment.id}, user)
|
||||
comment = Comment.approve_changeset(comment)
|
||||
|
@ -181,6 +227,23 @@ defmodule Philomena.Comments do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a system report for non-approved comments containing external images.
|
||||
Returns false for already approved comments.
|
||||
|
||||
## Returns
|
||||
- `false`: If the comment is already approved
|
||||
- `{:ok, %Report{}}`: If a system report was created
|
||||
|
||||
## Examples
|
||||
|
||||
iex> report_non_approved(approved_comment)
|
||||
false
|
||||
|
||||
iex> report_non_approved(unapproved_comment)
|
||||
{:ok, %Report{}}
|
||||
|
||||
"""
|
||||
def report_non_approved(%Comment{approved: true}), do: false
|
||||
|
||||
def report_non_approved(comment) do
|
||||
|
@ -191,6 +254,20 @@ defmodule Philomena.Comments do
|
|||
)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Migrates comments from one image to another when handling duplicate images.
|
||||
Returns the duplicate image parameter unchanged, for use in a pipeline.
|
||||
|
||||
## Parameters
|
||||
- image: The source image whose comments will be moved
|
||||
- duplicate_of_image: The target image that will receive the comments
|
||||
|
||||
## Examples
|
||||
|
||||
iex> migrate_comments(source_image, target_image)
|
||||
%Image{}
|
||||
|
||||
"""
|
||||
def migrate_comments(image, duplicate_of_image) do
|
||||
{count, nil} =
|
||||
Comment
|
||||
|
@ -217,24 +294,62 @@ defmodule Philomena.Comments do
|
|||
Comment.changeset(comment, %{})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates comment search indices when a user's name changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> user_name_reindex("old_username", "new_username")
|
||||
:ok
|
||||
|
||||
"""
|
||||
def user_name_reindex(old_name, new_name) do
|
||||
data = CommentIndex.user_name_update_by_query(old_name, new_name)
|
||||
|
||||
Search.update_by_query(Comment, data.query, data.set_replacements, data.replacements)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Queues a single comment for search index updates.
|
||||
Returns the comment struct unchanged, for use in a pipeline.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> reindex_comment(comment)
|
||||
%Comment{}
|
||||
|
||||
"""
|
||||
def reindex_comment(%Comment{} = comment) do
|
||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Comments", "id", [comment.id]])
|
||||
|
||||
comment
|
||||
end
|
||||
|
||||
@doc """
|
||||
Queues all comments associated with an image for search index updates.
|
||||
Returns the image struct unchanged, for use in a pipeline.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> reindex_comments(image)
|
||||
%Image{}
|
||||
|
||||
"""
|
||||
def reindex_comments(image) do
|
||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Comments", "image_id", [image.id]])
|
||||
|
||||
image
|
||||
end
|
||||
|
||||
@doc """
|
||||
Provides preload queries for comment indexing operations.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> indexing_preloads()
|
||||
[user: user_query, image: image_query]
|
||||
|
||||
"""
|
||||
def indexing_preloads do
|
||||
user_query = select(User, [u], map(u, [:id, :name]))
|
||||
tag_query = select(Tag, [t], map(t, [:id]))
|
||||
|
@ -250,6 +365,22 @@ defmodule Philomena.Comments do
|
|||
]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a search reindex operation on comments matching the given criteria.
|
||||
|
||||
## Parameters
|
||||
- column: The database column to filter on (e.g., :id, :image_id)
|
||||
- condition: A list of values to match against the column
|
||||
|
||||
## Examples
|
||||
|
||||
iex> perform_reindex(:id, [1, 2, 3])
|
||||
:ok
|
||||
|
||||
iex> perform_reindex(:image_id, [123])
|
||||
:ok
|
||||
|
||||
"""
|
||||
def perform_reindex(column, condition) do
|
||||
Comment
|
||||
|> preload(^indexing_preloads())
|
||||
|
|
|
@ -77,6 +77,18 @@ defmodule Philomena.DnpEntries do
|
|||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Transitions a DNP entry to a new state.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> transition_dnp_entry(dnp_entry, user, "acknowledged")
|
||||
{:ok, %DnpEntry{}}
|
||||
|
||||
iex> transition_dnp_entry(dnp_entry, user, "invalid_state")
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def transition_dnp_entry(%DnpEntry{} = dnp_entry, user, new_state) do
|
||||
dnp_entry
|
||||
|> DnpEntry.transition_changeset(user, new_state)
|
||||
|
@ -112,6 +124,19 @@ defmodule Philomena.DnpEntries do
|
|||
DnpEntry.changeset(dnp_entry, %{})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the count of active DNP entries in requested, claimed,
|
||||
or acknowledged state, if the user has permission to view them.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> count_dnp_entries(admin)
|
||||
42
|
||||
|
||||
iex> count_dnp_entries(user)
|
||||
nil
|
||||
|
||||
"""
|
||||
def count_dnp_entries(user) do
|
||||
if Canada.Can.can?(user, :index, DnpEntry) do
|
||||
DnpEntry
|
||||
|
|
|
@ -16,6 +16,18 @@ defmodule Philomena.DuplicateReports do
|
|||
alias Philomena.Images.Image
|
||||
alias Philomena.Images
|
||||
|
||||
@doc """
|
||||
Generates automated duplicate reports for an image based on perceptual matching.
|
||||
|
||||
Takes a source image and generates duplicate reports for similar images based on
|
||||
intensity and aspect ratio comparison.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> generate_reports(source_image)
|
||||
[{:ok, %DuplicateReport{}}, ...]
|
||||
|
||||
"""
|
||||
def generate_reports(source) do
|
||||
source = Repo.preload(source, :intensity)
|
||||
|
||||
|
@ -30,6 +42,23 @@ defmodule Philomena.DuplicateReports do
|
|||
end)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Query for potential duplicate images based on intensity values and aspect ratio.
|
||||
|
||||
Takes a tuple of {intensities, aspect_ratio} and optional options to control the search:
|
||||
- `:aspect_dist` - Maximum aspect ratio difference (default: 0.05)
|
||||
- `:limit` - Maximum number of results (default: 10)
|
||||
- `:dist` - Maximum intensity difference per channel (default: 0.25)
|
||||
|
||||
## Examples
|
||||
|
||||
iex> find_duplicates({%{nw: 0.5, ne: 0.5, sw: 0.5, se: 0.5}, 1.0})
|
||||
#Ecto.Query<...>
|
||||
|
||||
iex> find_duplicates({intensities, ratio}, dist: 0.3, limit: 20)
|
||||
#Ecto.Query<...>
|
||||
|
||||
"""
|
||||
def find_duplicates({intensities, aspect_ratio}, opts \\ []) do
|
||||
aspect_dist = Keyword.get(opts, :aspect_dist, 0.05)
|
||||
limit = Keyword.get(opts, :limit, 10)
|
||||
|
@ -150,6 +179,21 @@ defmodule Philomena.DuplicateReports do
|
|||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Accepts a duplicate report and merges the duplicate image into the target image.
|
||||
|
||||
Takes an optional Ecto.Multi, the duplicate report to accept, and the user accepting the report.
|
||||
Handles rejecting any other duplicate reports between the same images and merges the images.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> accept_duplicate_report(nil, duplicate_report, user)
|
||||
{:ok, %{duplicate_report: %DuplicateReport{}, ...}}
|
||||
|
||||
iex> accept_duplicate_report(existing_multi, duplicate_report, user)
|
||||
%Ecto.Multi{}
|
||||
|
||||
"""
|
||||
def accept_duplicate_report(multi \\ nil, %DuplicateReport{} = duplicate_report, user) do
|
||||
duplicate_report = Repo.preload(duplicate_report, [:image, :duplicate_of_image])
|
||||
|
||||
|
@ -175,6 +219,18 @@ defmodule Philomena.DuplicateReports do
|
|||
|> Images.merge_image(duplicate_report.image, duplicate_report.duplicate_of_image, user)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Accepts a duplicate report in reverse, making the target image the duplicate instead.
|
||||
|
||||
Creates a new duplicate report with reversed image relationship if one doesn't exist,
|
||||
rejects the original report, and accepts the reversed report.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> accept_reverse_duplicate_report(duplicate_report, user)
|
||||
{:ok, %{duplicate_report: %DuplicateReport{}, ...}}
|
||||
|
||||
"""
|
||||
def accept_reverse_duplicate_report(%DuplicateReport{} = duplicate_report, user) do
|
||||
new_report =
|
||||
DuplicateReport
|
||||
|
@ -204,18 +260,47 @@ defmodule Philomena.DuplicateReports do
|
|||
|> accept_duplicate_report(new_report, user)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Claims a duplicate report for review by a user.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> claim_duplicate_report(duplicate_report, user)
|
||||
{:ok, %DuplicateReport{}}
|
||||
|
||||
"""
|
||||
def claim_duplicate_report(%DuplicateReport{} = duplicate_report, user) do
|
||||
duplicate_report
|
||||
|> DuplicateReport.claim_changeset(user)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes a user's claim on a duplicate report.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unclaim_duplicate_report(duplicate_report)
|
||||
{:ok, %DuplicateReport{}}
|
||||
|
||||
"""
|
||||
def unclaim_duplicate_report(%DuplicateReport{} = duplicate_report) do
|
||||
duplicate_report
|
||||
|> DuplicateReport.unclaim_changeset()
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Rejects a duplicate report.
|
||||
|
||||
Updates the duplicate report's state to rejected and records the user who rejected it.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> reject_duplicate_report(duplicate_report, user)
|
||||
{:ok, %DuplicateReport{}}
|
||||
|
||||
"""
|
||||
def reject_duplicate_report(%DuplicateReport{} = duplicate_report, user) do
|
||||
duplicate_report
|
||||
|> DuplicateReport.reject_changeset(user)
|
||||
|
@ -251,6 +336,19 @@ defmodule Philomena.DuplicateReports do
|
|||
DuplicateReport.changeset(duplicate_report, %{})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Counts the number of duplicate reports in "open" state,
|
||||
if the user has permission to view them.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> count_duplicate_reports(admin)
|
||||
42
|
||||
|
||||
iex> count_duplicate_reports(user)
|
||||
nil
|
||||
|
||||
"""
|
||||
def count_duplicate_reports(user) do
|
||||
if Canada.Can.can?(user, :index, DuplicateReport) do
|
||||
DuplicateReport
|
||||
|
|
|
@ -93,6 +93,17 @@ defmodule Philomena.Filters do
|
|||
|> reindex_after_update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes a filter public.
|
||||
|
||||
Updates the filter to be publicly accessible by other users.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> make_filter_public(filter)
|
||||
{:ok, %Filter{}}
|
||||
|
||||
"""
|
||||
def make_filter_public(%Filter{} = filter) do
|
||||
filter
|
||||
|> Filter.public_changeset()
|
||||
|
@ -140,6 +151,21 @@ defmodule Philomena.Filters do
|
|||
Filter.changeset(filter, %{})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a grouped list of recent and user filters.
|
||||
|
||||
Takes a user and returns a list of their recently used filters and personal filters,
|
||||
grouped into "Recent Filters" and "Your Filters" categories.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> recent_and_user_filters(user)
|
||||
[
|
||||
{"Recent Filters", [[key: "Filter 1", value: 1], ...]},
|
||||
{"Your Filters", [[key: "Filter 2", value: 2], ...]}
|
||||
]
|
||||
|
||||
"""
|
||||
def recent_and_user_filters(user) do
|
||||
recent_filter_ids =
|
||||
[user.current_filter_id | user.recent_filter_ids]
|
||||
|
@ -174,6 +200,17 @@ defmodule Philomena.Filters do
|
|||
|> Enum.reverse()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds a tag to a filter's hidden tags list.
|
||||
|
||||
Updates the filter to hide content with the specified tag.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> hide_tag(filter, tag)
|
||||
{:ok, %Filter{}}
|
||||
|
||||
"""
|
||||
def hide_tag(filter, tag) do
|
||||
hidden_tag_ids = Enum.uniq([tag.id | filter.hidden_tag_ids])
|
||||
|
||||
|
@ -183,6 +220,15 @@ defmodule Philomena.Filters do
|
|||
|> reindex_after_update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes a tag from a filter's hidden tags list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unhide_tag(filter, tag)
|
||||
{:ok, %Filter{}}
|
||||
|
||||
"""
|
||||
def unhide_tag(filter, tag) do
|
||||
hidden_tag_ids = filter.hidden_tag_ids -- [tag.id]
|
||||
|
||||
|
@ -192,6 +238,15 @@ defmodule Philomena.Filters do
|
|||
|> reindex_after_update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds a tag to a filter's spoilered tags list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> spoiler_tag(filter, tag)
|
||||
{:ok, %Filter{}}
|
||||
|
||||
"""
|
||||
def spoiler_tag(filter, tag) do
|
||||
spoilered_tag_ids = Enum.uniq([tag.id | filter.spoilered_tag_ids])
|
||||
|
||||
|
@ -201,6 +256,15 @@ defmodule Philomena.Filters do
|
|||
|> reindex_after_update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes a tag from a filter's spoilered tags list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unspoiler_tag(filter, tag)
|
||||
{:ok, %Filter{}}
|
||||
|
||||
"""
|
||||
def unspoiler_tag(filter, tag) do
|
||||
spoilered_tag_ids = filter.spoilered_tag_ids -- [tag.id]
|
||||
|
||||
|
@ -210,38 +274,92 @@ defmodule Philomena.Filters do
|
|||
|> reindex_after_update()
|
||||
end
|
||||
|
||||
defp reindex_after_update({:ok, filter}) do
|
||||
defp reindex_after_update(result) do
|
||||
case result do
|
||||
{:ok, filter} ->
|
||||
reindex_filter(filter)
|
||||
|
||||
{:ok, filter}
|
||||
end
|
||||
|
||||
defp reindex_after_update(error) do
|
||||
error ->
|
||||
error
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates filter indexes when a user's name changes.
|
||||
|
||||
Updates search indexes to reflect a user's new name.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> user_name_reindex("old_name", "new_name")
|
||||
:ok
|
||||
|
||||
"""
|
||||
def user_name_reindex(old_name, new_name) do
|
||||
data = FilterIndex.user_name_update_by_query(old_name, new_name)
|
||||
|
||||
Search.update_by_query(Filter, data.query, data.set_replacements, data.replacements)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Queues a single filter for search index updates.
|
||||
Returns the filter struct unchanged, for use in a pipeline.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> reindex_filter(filter)
|
||||
%Filter{}
|
||||
|
||||
"""
|
||||
def reindex_filter(%Filter{} = filter) do
|
||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Filters", "id", [filter.id]])
|
||||
|
||||
filter
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes a filter from the search index.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unindex_filter(filter)
|
||||
%Filter{}
|
||||
|
||||
"""
|
||||
def unindex_filter(%Filter{} = filter) do
|
||||
Search.delete_document(filter.id, Filter)
|
||||
|
||||
filter
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of associations to preload when indexing filters.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> indexing_preloads()
|
||||
[:user]
|
||||
|
||||
"""
|
||||
def indexing_preloads do
|
||||
[:user]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a search reindex operation on filters matching the given criteria.
|
||||
|
||||
## Parameters
|
||||
- column: The database column to filter on (e.g., :id)
|
||||
- condition: A list of values to match against the column
|
||||
|
||||
## Examples
|
||||
|
||||
iex> perform_reindex(:id, [1, 2, 3])
|
||||
{:ok, [%Filter{}, ...]}
|
||||
|
||||
"""
|
||||
def perform_reindex(column, condition) do
|
||||
Filter
|
||||
|> preload(^indexing_preloads())
|
||||
|
|
|
@ -121,6 +121,15 @@ defmodule Philomena.Galleries do
|
|||
Gallery.changeset(gallery, %{})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates gallery search indices when a user's name changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> user_name_reindex("old_username", "new_username")
|
||||
:ok
|
||||
|
||||
"""
|
||||
def user_name_reindex(old_name, new_name) do
|
||||
data = GalleryIndex.user_name_update_by_query(old_name, new_name)
|
||||
|
||||
|
@ -137,22 +146,65 @@ defmodule Philomena.Galleries do
|
|||
error
|
||||
end
|
||||
|
||||
@doc """
|
||||
Queues a gallery for reindexing.
|
||||
|
||||
Adds the gallery to the indexing queue to update its search index.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> reindex_gallery(gallery)
|
||||
%Gallery{}
|
||||
|
||||
"""
|
||||
def reindex_gallery(%Gallery{} = gallery) do
|
||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Galleries", "id", [gallery.id]])
|
||||
|
||||
gallery
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes a gallery from the search index.
|
||||
|
||||
Deletes the gallery's document from the search index.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unindex_gallery(gallery)
|
||||
%Gallery{}
|
||||
|
||||
"""
|
||||
def unindex_gallery(%Gallery{} = gallery) do
|
||||
Search.delete_document(gallery.id, Gallery)
|
||||
|
||||
gallery
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns a list of associations to preload when indexing galleries.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> indexing_preloads()
|
||||
[:subscribers, :creator, :interactions]
|
||||
|
||||
"""
|
||||
def indexing_preloads do
|
||||
[:subscribers, :creator, :interactions]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reindexes galleries based on a column condition.
|
||||
|
||||
Updates the search index for all galleries matching the given column condition.
|
||||
Used for batch reindexing of galleries.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> perform_reindex(:id, [1, 2, 3])
|
||||
{:ok, [%Gallery{}, ...]}
|
||||
|
||||
"""
|
||||
def perform_reindex(column, condition) do
|
||||
Gallery
|
||||
|> preload(^indexing_preloads())
|
||||
|
@ -160,6 +212,24 @@ defmodule Philomena.Galleries do
|
|||
|> Search.reindex(Gallery)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds the specified image to the gallery, updates image count, triggers
|
||||
notifications, and performs necessary reindexing.
|
||||
|
||||
The image is added at the last position.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> add_image_to_gallery(gallery, image)
|
||||
{:ok,
|
||||
%{
|
||||
gallery: %Gallery{},
|
||||
interaction: %Interaction{},
|
||||
image_count: 1,
|
||||
notification: %Notification{}
|
||||
}}
|
||||
|
||||
"""
|
||||
def add_image_to_gallery(gallery, image) do
|
||||
Multi.new()
|
||||
|> Multi.run(:gallery, fn repo, %{} ->
|
||||
|
@ -202,6 +272,21 @@ defmodule Philomena.Galleries do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes the specified image from the gallery, updates image count,
|
||||
and performs necessary reindexing.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> remove_image_from_gallery(gallery, image)
|
||||
{:ok,
|
||||
%{
|
||||
gallery: %Gallery{},
|
||||
interaction: 1,
|
||||
image_count: 0
|
||||
}}
|
||||
|
||||
"""
|
||||
def remove_image_from_gallery(gallery, image) do
|
||||
Multi.new()
|
||||
|> Multi.run(:gallery, fn repo, %{} ->
|
||||
|
@ -254,10 +339,35 @@ defmodule Philomena.Galleries do
|
|||
|> Repo.aggregate(:max, :position)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Queues a gallery reorder operation.
|
||||
Returns the gallery struct unchanged, for use in a pipeline.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> reorder_gallery(gallery, [1, 2, 3])
|
||||
%Gallery{}
|
||||
|
||||
"""
|
||||
def reorder_gallery(gallery, image_ids) do
|
||||
Exq.enqueue(Exq, "indexing", GalleryReorderWorker, [gallery.id, image_ids])
|
||||
|
||||
gallery
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs the actual reordering of images in a gallery.
|
||||
|
||||
Reorders the gallery's images according to the provided image IDs list, updating
|
||||
positions while maintaining relative order for unspecified images. Handles position
|
||||
updates efficiently and reindexes only the affected images.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> perform_reorder(gallery_id, [3, 1, 2])
|
||||
:ok
|
||||
|
||||
"""
|
||||
def perform_reorder(gallery_id, image_ids) do
|
||||
gallery = get_gallery!(gallery_id)
|
||||
|
||||
|
@ -320,6 +430,8 @@ defmodule Philomena.Galleries do
|
|||
|
||||
# Now update all the associated images
|
||||
Images.reindex_images(Map.keys(requested))
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
defp position_order(%{order_position_asc: true}), do: [asc: :position]
|
||||
|
|
|
@ -156,6 +156,16 @@ defmodule Philomena.Images do
|
|||
Logger.error("Aborting upload of #{image.id} after #{retry_count} retries")
|
||||
end
|
||||
|
||||
@doc """
|
||||
Approves an image for public viewing.
|
||||
|
||||
This will make the image visible to users and update necessary statistics.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> approve_image(image)
|
||||
{:ok, %Image{}}
|
||||
"""
|
||||
def approve_image(image) do
|
||||
image
|
||||
|> Repo.preload(:user)
|
||||
|
@ -197,6 +207,18 @@ defmodule Philomena.Images do
|
|||
|
||||
defp maybe_suggest_user_verification(_user), do: false
|
||||
|
||||
@doc """
|
||||
Counts the number of images pending approval that a user can moderate.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> count_pending_approvals(admin)
|
||||
42
|
||||
|
||||
iex> count_pending_approvals(user)
|
||||
nil
|
||||
|
||||
"""
|
||||
def count_pending_approvals(user) do
|
||||
if Canada.Can.can?(user, :approve, %Image{}) do
|
||||
Image
|
||||
|
@ -208,12 +230,36 @@ defmodule Philomena.Images do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Marks the given image as the current featured image.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> feature_image(user, image)
|
||||
{:ok, %ImageFeature{}}
|
||||
|
||||
"""
|
||||
def feature_image(featurer, %Image{} = image) do
|
||||
%ImageFeature{user_id: featurer.id, image_id: image.id}
|
||||
|> ImageFeature.changeset(%{})
|
||||
|> Repo.insert()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Destroys the contents of an image (hard deletion) by marking it as hidden
|
||||
and deleting up associated files.
|
||||
|
||||
This will:
|
||||
1. Mark the image as removed in the database
|
||||
2. Purge associated files
|
||||
3. Remove thumbnails
|
||||
|
||||
## Examples
|
||||
|
||||
iex> destroy_image(image)
|
||||
{:ok, %Image{}}
|
||||
|
||||
"""
|
||||
def destroy_image(%Image{} = image) do
|
||||
image
|
||||
|> Image.remove_image_changeset()
|
||||
|
@ -230,36 +276,91 @@ defmodule Philomena.Images do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Locks or unlocks comments on an image.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> lock_comments(image, true)
|
||||
{:ok, %Image{}}
|
||||
|
||||
"""
|
||||
def lock_comments(%Image{} = image, locked) do
|
||||
image
|
||||
|> Image.lock_comments_changeset(locked)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Locks or unlocks the description of an image.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> lock_description(image, true)
|
||||
{:ok, %Image{}}
|
||||
|
||||
"""
|
||||
def lock_description(%Image{} = image, locked) do
|
||||
image
|
||||
|> Image.lock_description_changeset(locked)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Locks or unlocks the tags on an image.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> lock_tags(image, true)
|
||||
{:ok, %Image{}}
|
||||
|
||||
"""
|
||||
def lock_tags(%Image{} = image, locked) do
|
||||
image
|
||||
|> Image.lock_tags_changeset(locked)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes the original SHA-512 hash from an image, allowing users to upload
|
||||
the same file again.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> remove_hash(image)
|
||||
{:ok, %Image{}}
|
||||
|
||||
"""
|
||||
def remove_hash(%Image{} = image) do
|
||||
image
|
||||
|> Image.remove_hash_changeset()
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the scratchpad notes on an image.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_scratchpad(image, %{"scratchpad" => "New notes"})
|
||||
{:ok, %Image{}}
|
||||
|
||||
"""
|
||||
def update_scratchpad(%Image{} = image, attrs) do
|
||||
image
|
||||
|> Image.scratchpad_changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes all source change history for an image.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> remove_source_history(image)
|
||||
{:ok, %Image{}}
|
||||
|
||||
"""
|
||||
def remove_source_history(%Image{} = image) do
|
||||
image
|
||||
|> Repo.preload(:source_changes)
|
||||
|
@ -267,17 +368,49 @@ defmodule Philomena.Images do
|
|||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Repairs an image by regenerating its thumbnails.
|
||||
Returns the image struct unchanged, for use in a pipeline.
|
||||
|
||||
This will:
|
||||
1. Mark the image as needing thumbnail regeneration
|
||||
2. Queue the thumbnail generation job
|
||||
|
||||
## Examples
|
||||
|
||||
iex> repair_image(image)
|
||||
%Image{}
|
||||
|
||||
"""
|
||||
def repair_image(%Image{} = image) do
|
||||
Image
|
||||
|> where(id: ^image.id)
|
||||
|> Repo.update_all(set: [thumbnails_generated: false, processed: false])
|
||||
|
||||
Exq.enqueue(Exq, queue(image.image_mime_type), ThumbnailWorker, [image.id])
|
||||
|
||||
image
|
||||
end
|
||||
|
||||
defp queue("video/webm"), do: "videos"
|
||||
defp queue(_mime_type), do: "images"
|
||||
|
||||
@doc """
|
||||
Updates the file content of an image.
|
||||
|
||||
This will:
|
||||
1. Update the image metadata
|
||||
2. Save the new file
|
||||
3. Generate new thumbnails
|
||||
4. Purge old files
|
||||
5. Reindex the image
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_file(image, %{"image" => upload})
|
||||
{:ok, %Image{}}
|
||||
|
||||
"""
|
||||
def update_file(%Image{} = image, attrs) do
|
||||
image
|
||||
|> Image.changeset(attrs)
|
||||
|
@ -316,12 +449,48 @@ defmodule Philomena.Images do
|
|||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates an image's description.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_description(image, %{"description" => "New description"})
|
||||
{:ok, %Image{}}
|
||||
|
||||
"""
|
||||
def update_description(%Image{} = image, attrs) do
|
||||
image
|
||||
|> Image.description_changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates an image's sources with attribution tracking.
|
||||
|
||||
Handles both added and removed sources. Automatically determines the user's
|
||||
intended source changes based on the provided previous image state.
|
||||
|
||||
This will update the image's sources, create source change records
|
||||
for tracking, and reindex the image.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_sources(
|
||||
...> image,
|
||||
...> %{attribution: attrs},
|
||||
...> %{
|
||||
...> "old_sources" => %{},
|
||||
...> "sources" => %{"0" => "http://example.com"}
|
||||
...> }
|
||||
...> )
|
||||
{:ok,
|
||||
%{
|
||||
image: image,
|
||||
added_source_changes: 1,
|
||||
removed_source_changes: 0
|
||||
}}
|
||||
|
||||
"""
|
||||
def update_sources(%Image{} = image, attribution, attrs) do
|
||||
old_sources = attrs["old_sources"]
|
||||
new_sources = attrs["sources"]
|
||||
|
@ -383,6 +552,17 @@ defmodule Philomena.Images do
|
|||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the locked tags on an image.
|
||||
|
||||
Locked tags can only be added or removed by privileged users.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_locked_tags(image, %{tag_input: "safe, validated"})
|
||||
{:ok, %Image{}}
|
||||
|
||||
"""
|
||||
def update_locked_tags(%Image{} = image, attrs) do
|
||||
new_tags = Tags.get_or_create_tags(attrs["tag_input"])
|
||||
|
||||
|
@ -392,6 +572,33 @@ defmodule Philomena.Images do
|
|||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates an image's tags with attribution tracking.
|
||||
|
||||
Handles both added and removed tags. Automatically determines the user's
|
||||
intended tag changes based on the provided previous image state.
|
||||
|
||||
This will update the image's tags, create tag change records
|
||||
for tracking, and reindex the image.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_tags(
|
||||
...> image,
|
||||
...> %{attribution: attrs},
|
||||
...> %{
|
||||
...> old_tag_input: "safe",
|
||||
...> tag_input: "safe, cute"
|
||||
...> }
|
||||
...> )
|
||||
{:ok,
|
||||
%{
|
||||
image: image,
|
||||
added_tag_changes: 1,
|
||||
removed_tag_changes: 0
|
||||
}}
|
||||
|
||||
"""
|
||||
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"])
|
||||
|
@ -486,14 +693,27 @@ defmodule Philomena.Images do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the tag change tracking after committing updates to an image.
|
||||
|
||||
This updates the rate limit counters for total tag change count and rating change count
|
||||
based on the changes made to the image.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_tag_change_limits_after_commit(image, %{user: user, ip: "127.0.0.1"})
|
||||
:ok
|
||||
|
||||
"""
|
||||
def update_tag_change_limits_after_commit(image, attribution) do
|
||||
rating_changed_count = if(image.ratings_changed, do: 1, else: 0)
|
||||
tag_changed_count = length(image.added_tags) + length(image.removed_tags)
|
||||
user = attribution[:user]
|
||||
ip = attribution[:ip]
|
||||
|
||||
Limits.update_tag_count_after_update(user, ip, tag_changed_count)
|
||||
Limits.update_rating_count_after_update(user, ip, rating_changed_count)
|
||||
:ok = Limits.update_tag_count_after_update(user, ip, tag_changed_count)
|
||||
:ok = Limits.update_rating_count_after_update(user, ip, rating_changed_count)
|
||||
:ok
|
||||
end
|
||||
|
||||
defp tag_change_attributes(attribution, image, tag, added, user) do
|
||||
|
@ -518,18 +738,48 @@ defmodule Philomena.Images do
|
|||
}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Changes the uploader of an image.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_uploader(image, %{"username" => "Admin"})
|
||||
{:ok, %Image{}}
|
||||
|
||||
"""
|
||||
def update_uploader(%Image{} = image, attrs) do
|
||||
image
|
||||
|> Image.uploader_changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the anonymous status of an image.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_anonymous(image, %{"anonymous" => "true"})
|
||||
{:ok, %Image{}}
|
||||
|
||||
"""
|
||||
def update_anonymous(%Image{} = image, attrs) do
|
||||
image
|
||||
|> Image.anonymous_changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates the hide reason for an image.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_hide_reason(image, %{hide_reason: "Duplicate of #1234"})
|
||||
{:ok, %Image{}}
|
||||
|
||||
iex> update_hide_reason(image, %{hide_reason: ""})
|
||||
{:ok, %Image{}}
|
||||
|
||||
"""
|
||||
def update_hide_reason(%Image{} = image, attrs) do
|
||||
image
|
||||
|> Image.hide_reason_changeset(attrs)
|
||||
|
@ -545,6 +795,28 @@ defmodule Philomena.Images do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Hides an image from public view.
|
||||
|
||||
This will:
|
||||
1. Mark the image as hidden
|
||||
2. Close all reports and duplicate reports
|
||||
3. Delete all gallery interactions containing the image
|
||||
4. Decrement all tag counts with the image
|
||||
5. Hide the image's thumbnails and purge them from the CDN
|
||||
6. Reindex the image and all of its comments
|
||||
|
||||
## Examples
|
||||
|
||||
iex> hide_image(image, moderator, %{reason: "Rule violation"})
|
||||
{:ok,
|
||||
%{
|
||||
image: image,
|
||||
tags: tags,
|
||||
reports: {count, reports}
|
||||
}}
|
||||
|
||||
"""
|
||||
def hide_image(%Image{} = image, user, attrs) do
|
||||
duplicate_reports =
|
||||
DuplicateReport
|
||||
|
@ -560,6 +832,33 @@ defmodule Philomena.Images do
|
|||
|> process_after_hide()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Merges one image into another, combining their metadata and content.
|
||||
|
||||
This will:
|
||||
1. Hide the source image
|
||||
2. Update first_seen_at timestamp
|
||||
3. Copy tags to the target image
|
||||
4. Migrate sources, comments, subscriptions and interactions
|
||||
5. Send merge notifications
|
||||
6. Reindex both images and all of the comments
|
||||
|
||||
## Parameters
|
||||
- multi: Optional `m:Ecto.Multi` for transaction handling
|
||||
- image: The source image to merge from
|
||||
- duplicate_of_image: The target image to merge into
|
||||
- user: The user performing the merge
|
||||
|
||||
## Examples
|
||||
|
||||
iex> merge_image(nil, source_image, target_image, moderator)
|
||||
{:ok,
|
||||
%{
|
||||
image: image,
|
||||
tags: tags
|
||||
}}
|
||||
|
||||
"""
|
||||
def merge_image(multi \\ nil, %Image{} = image, duplicate_of_image, user) do
|
||||
multi = multi || Multi.new()
|
||||
|
||||
|
@ -675,6 +974,26 @@ defmodule Philomena.Images do
|
|||
{:ok, image}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unhides an image, making it visible to users again.
|
||||
|
||||
This will:
|
||||
1. Remove the hidden status from the image
|
||||
2. Increment tag counts
|
||||
3. Unhide thumbnails
|
||||
4. Reindex the image and related content
|
||||
|
||||
Returns {:ok, image} if successful, or returns the image unchanged if it's not hidden.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unhide_image(hidden_image)
|
||||
{:ok, %Image{hidden_from_users: false}}
|
||||
|
||||
iex> unhide_image(visible_image)
|
||||
{:ok, %Image{}}
|
||||
|
||||
"""
|
||||
def unhide_image(%Image{hidden_from_users: true} = image) do
|
||||
key = image.hidden_image_key
|
||||
|
||||
|
@ -711,6 +1030,24 @@ defmodule Philomena.Images do
|
|||
|
||||
def unhide_image(image), do: {:ok, image}
|
||||
|
||||
@doc """
|
||||
Performs a batch update on multiple images, adding and removing tags.
|
||||
|
||||
This function efficiently updates tags for multiple images at once,
|
||||
handling tag changes, tag counts, and reindexing in a single transaction.
|
||||
|
||||
## Parameters
|
||||
- image_ids: List of image IDs to update
|
||||
- added_tags: List of tags to add to all images
|
||||
- removed_tags: List of tags to remove from all images
|
||||
- tag_change_attributes: Attributes tag changes are created with
|
||||
|
||||
## Examples
|
||||
|
||||
iex> batch_update([1, 2], [tag1], [tag2], %{user_id: user.id})
|
||||
{:ok, ...}
|
||||
|
||||
"""
|
||||
def batch_update(image_ids, added_tags, removed_tags, tag_change_attributes) do
|
||||
image_ids =
|
||||
Image
|
||||
|
@ -828,24 +1165,65 @@ defmodule Philomena.Images do
|
|||
Image.changeset(image, %{})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates image search indices when a user's name changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> user_name_reindex("old_username", "new_username")
|
||||
:ok
|
||||
|
||||
"""
|
||||
def user_name_reindex(old_name, new_name) do
|
||||
data = ImageIndex.user_name_update_by_query(old_name, new_name)
|
||||
|
||||
Search.update_by_query(Image, data.query, data.set_replacements, data.replacements)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Queues a single image for search index updates.
|
||||
Returns the image struct unchanged, for use in a pipeline.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> reindex_image(image)
|
||||
%Image{}
|
||||
|
||||
"""
|
||||
def reindex_image(%Image{} = image) do
|
||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Images", "id", [image.id]])
|
||||
|
||||
image
|
||||
end
|
||||
|
||||
@doc """
|
||||
Queues all listed image IDs for search index updates.
|
||||
Returns the list unchanged, for use in a pipeline.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> reindex_images([1, 2, 3])
|
||||
[1, 2, 3]
|
||||
|
||||
"""
|
||||
def reindex_images(image_ids) do
|
||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Images", "id", image_ids])
|
||||
|
||||
image_ids
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the preload configuration for image indexing.
|
||||
|
||||
Specifies which associations should be preloaded when indexing images,
|
||||
optimizing the queries for better performance.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> indexing_preloads()
|
||||
[sources: query, user: query, ...]
|
||||
|
||||
"""
|
||||
def indexing_preloads do
|
||||
user_query = select(User, [u], map(u, [:id, :name]))
|
||||
sources_query = select(Source, [s], map(s, [:image_id, :source]))
|
||||
|
@ -869,6 +1247,19 @@ defmodule Philomena.Images do
|
|||
]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a search reindex operation on images matching the given criteria.
|
||||
|
||||
## Parameters
|
||||
- column: The database column to filter on (e.g., :id)
|
||||
- condition: A list of values to match against the column
|
||||
|
||||
## Examples
|
||||
|
||||
iex> perform_reindex(:id, [1, 2, 3])
|
||||
:ok
|
||||
|
||||
"""
|
||||
def perform_reindex(column, condition) do
|
||||
Image
|
||||
|> preload(^indexing_preloads())
|
||||
|
@ -876,6 +1267,17 @@ defmodule Philomena.Images do
|
|||
|> Search.reindex(Image)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Purges image files from the CDN.
|
||||
|
||||
Enqueues a job to purge both visible and hidden thumbnail paths for the given image.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> purge_files(image, "hidden_key")
|
||||
:ok
|
||||
|
||||
"""
|
||||
def purge_files(image, hidden_key) do
|
||||
files =
|
||||
if is_nil(hidden_key) do
|
||||
|
@ -888,6 +1290,17 @@ defmodule Philomena.Images do
|
|||
Exq.enqueue(Exq, "indexing", ImagePurgeWorker, [files])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Executes the actual purge operation for image files.
|
||||
|
||||
Calls the system purge-cache command to remove the specified files from the CDN cache.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> perform_purge(["file1.jpg", "file2.jpg"])
|
||||
:ok
|
||||
|
||||
"""
|
||||
def perform_purge(files) do
|
||||
{_out, 0} = System.cmd("purge-cache", [Jason.encode!(%{files: files})])
|
||||
|
||||
|
@ -896,6 +1309,29 @@ defmodule Philomena.Images do
|
|||
|
||||
alias Philomena.Images.Subscription
|
||||
|
||||
@doc """
|
||||
Migrates subscriptions and notifications from one image to another.
|
||||
|
||||
This function is used during image merging to transfer all subscriptions
|
||||
and notifications from the source image to the target image. It handles:
|
||||
|
||||
1. User subscriptions
|
||||
2. Comment notifications
|
||||
3. Merge notifications
|
||||
|
||||
Returns `{:ok, {comment_notification_count, merge_notification_count}}`.
|
||||
|
||||
## Parameters
|
||||
|
||||
- source: The source image to migrate from
|
||||
- target: The target image to migrate to
|
||||
|
||||
## Examples
|
||||
|
||||
iex> migrate_subscriptions(source_image, target_image)
|
||||
{:ok, {5, 2}}
|
||||
|
||||
"""
|
||||
def migrate_subscriptions(source, target) do
|
||||
subscriptions =
|
||||
Subscription
|
||||
|
@ -941,6 +1377,29 @@ defmodule Philomena.Images do
|
|||
{:ok, {comment_notification_count, merge_notification_count}}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Migrates source URLs from one image to another.
|
||||
|
||||
This function is used during image merging to combine source URLs from both images.
|
||||
It will:
|
||||
|
||||
1. Combine sources from both images
|
||||
2. Remove duplicates
|
||||
3. Take up to 15 sources (the system limit)
|
||||
4. Update the target image with the combined sources
|
||||
|
||||
Returns the result of updating the target image with the combined sources.
|
||||
|
||||
## Parameters
|
||||
- source: The source image containing sources to migrate
|
||||
- target: The target image to receive the combined sources
|
||||
|
||||
## Examples
|
||||
|
||||
iex> migrate_sources(source_image, target_image)
|
||||
{:ok, %Image{}}
|
||||
|
||||
"""
|
||||
def migrate_sources(source, target) do
|
||||
sources =
|
||||
(source.sources ++ target.sources)
|
||||
|
|
|
@ -8,6 +8,19 @@ defmodule Philomena.Interactions do
|
|||
alias Philomena.Repo
|
||||
alias Ecto.Multi
|
||||
|
||||
@doc """
|
||||
Gets all interactions for a list of images for a given user.
|
||||
|
||||
Returns an empty list if no user is provided. Otherwise returns a list of maps containing:
|
||||
- image_id: The ID of the image
|
||||
- user_id: The ID of the user
|
||||
- interaction_type: One of "hidden", "faved", or "voted"
|
||||
- value: For votes, either "up" or "down". Empty string for other interaction types.
|
||||
|
||||
## Parameters
|
||||
* images - List of images or image IDs to get interactions for
|
||||
* user - The user to get interactions for, or nil
|
||||
"""
|
||||
def user_interactions(_images, nil),
|
||||
do: []
|
||||
|
||||
|
@ -71,6 +84,18 @@ defmodule Philomena.Interactions do
|
|||
|> Repo.all()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Migrates all interactions from one image to another.
|
||||
|
||||
Copies all hides, faves, and votes from the source image to the target image.
|
||||
Updates the target image's counters to reflect the new interactions.
|
||||
All operations are performed in a single transaction.
|
||||
|
||||
## Parameters
|
||||
* source - The source Image struct to copy interactions from
|
||||
* target - The target Image struct to copy interactions to
|
||||
|
||||
"""
|
||||
def migrate_interactions(source, target) do
|
||||
now = DateTime.utc_now(:second)
|
||||
source = Repo.preload(source, [:hiders, :favers, :upvoters, :downvoters])
|
||||
|
|
|
@ -111,6 +111,23 @@ defmodule Philomena.Posts do
|
|||
Notifications.create_forum_post_notification(post.user, topic, post)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates a system report for non-approved posts containing external images.
|
||||
Returns false for already approved posts.
|
||||
|
||||
## Returns
|
||||
- `false`: If the post is already approved
|
||||
- `{:ok, %Report{}}`: If a system report was created
|
||||
|
||||
## Examples
|
||||
|
||||
iex> report_non_approved(approved_post)
|
||||
false
|
||||
|
||||
iex> report_non_approved(unapproved_post)
|
||||
{:ok, %Report{}}
|
||||
|
||||
"""
|
||||
def report_non_approved(%Post{approved: true}), do: false
|
||||
|
||||
def report_non_approved(post) do
|
||||
|
@ -176,6 +193,20 @@ defmodule Philomena.Posts do
|
|||
Repo.delete(post)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Hides a post and handles associated reports.
|
||||
|
||||
## Parameters
|
||||
- post: The post to hide
|
||||
- attrs: Attributes for the hide operation
|
||||
- user: The user performing the hide action
|
||||
|
||||
## Examples
|
||||
|
||||
iex> hide_post(post, %{staff_note: "Rule violation"}, user)
|
||||
{:ok, %Post{}}
|
||||
|
||||
"""
|
||||
def hide_post(%Post{} = post, attrs, user) do
|
||||
report_query = Reports.close_report_query({"Post", post.id}, user)
|
||||
|
||||
|
@ -209,6 +240,15 @@ defmodule Philomena.Posts do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unhides a previously hidden post.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unhide_post(post)
|
||||
{:ok, %Post{}}
|
||||
|
||||
"""
|
||||
def unhide_post(%Post{} = post) do
|
||||
post
|
||||
|> Post.unhide_changeset()
|
||||
|
@ -216,6 +256,15 @@ defmodule Philomena.Posts do
|
|||
|> reindex_after_update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Marks a post as destroyed and removes its text (hard deletion).
|
||||
|
||||
## Examples
|
||||
|
||||
iex> destroy_post(post)
|
||||
{:ok, %Post{}}
|
||||
|
||||
"""
|
||||
def destroy_post(%Post{} = post) do
|
||||
post
|
||||
|> Post.destroy_changeset()
|
||||
|
@ -223,6 +272,20 @@ defmodule Philomena.Posts do
|
|||
|> reindex_after_update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Approves a post, closes associated reports, and increments the user forum
|
||||
posts count.
|
||||
|
||||
## Parameters
|
||||
- post: The post to approve
|
||||
- user: The user performing the approval
|
||||
|
||||
## Examples
|
||||
|
||||
iex> approve_comment(post, user)
|
||||
{:ok, %Post{}}
|
||||
|
||||
"""
|
||||
def approve_post(%Post{} = post, user) do
|
||||
report_query = Reports.close_report_query({"Post", post.id}, user)
|
||||
post = Post.approve_changeset(post)
|
||||
|
@ -257,6 +320,15 @@ defmodule Philomena.Posts do
|
|||
Post.changeset(post, %{})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates post search indices when a user's name changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> user_name_reindex("old_username", "new_username")
|
||||
:ok
|
||||
|
||||
"""
|
||||
def user_name_reindex(old_name, new_name) do
|
||||
data = PostIndex.user_name_update_by_query(old_name, new_name)
|
||||
|
||||
|
@ -273,12 +345,31 @@ defmodule Philomena.Posts do
|
|||
result
|
||||
end
|
||||
|
||||
@doc """
|
||||
Queues a single post for search index updates.
|
||||
Returns the post struct unchanged, for use in a pipeline.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> reindex_comment(post)
|
||||
%Post{}
|
||||
|
||||
"""
|
||||
def reindex_post(%Post{} = post) do
|
||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Posts", "id", [post.id]])
|
||||
|
||||
post
|
||||
end
|
||||
|
||||
@doc """
|
||||
Provides preload queries for post indexing operations.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> indexing_preloads()
|
||||
[user: user_query, topic: topic_query]
|
||||
|
||||
"""
|
||||
def indexing_preloads do
|
||||
user_query = select(User, [u], map(u, [:id, :name]))
|
||||
|
||||
|
@ -293,6 +384,22 @@ defmodule Philomena.Posts do
|
|||
]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs a search reindex operation on posts matching the given criteria.
|
||||
|
||||
## Parameters
|
||||
- column: The database column to filter on (e.g., :id, :topic_id)
|
||||
- condition: A list of values to match against the column
|
||||
|
||||
## Examples
|
||||
|
||||
iex> perform_reindex(:id, [1, 2, 3])
|
||||
:ok
|
||||
|
||||
iex> perform_reindex(:topic_id, [123])
|
||||
:ok
|
||||
|
||||
"""
|
||||
def perform_reindex(column, condition) do
|
||||
Post
|
||||
|> preload(^indexing_preloads())
|
||||
|
|
|
@ -1,29 +1,32 @@
|
|||
defmodule Philomena.Slug do
|
||||
# Generates a URL-safe slug from a string by removing nonessential
|
||||
# information from it.
|
||||
#
|
||||
# The process for this is as follows:
|
||||
#
|
||||
# 1. Remove non-ASCII or non-printable characters.
|
||||
#
|
||||
# 2. Replace any runs of non-alphanumeric characters that were allowed
|
||||
# through previously with hyphens.
|
||||
#
|
||||
# 3. Remove any starting or ending hyphens.
|
||||
#
|
||||
# 4. Convert all characters to their lowercase equivalents.
|
||||
#
|
||||
# This method makes no guarantee of creating unique slugs for unique inputs.
|
||||
# In addition, for certain inputs, it will return empty strings.
|
||||
#
|
||||
# Example
|
||||
#
|
||||
# destructive_slug("Time-Wasting Thread 3.0 (SFW - No Explicit/Grimdark)")
|
||||
# #=> "time-wasting-thread-3-0-sfw-no-explicit-grimdark"
|
||||
#
|
||||
# destructive_slug("~`!@#$%^&*()-_=+[]{};:'\" <>,./?")
|
||||
# #=> ""
|
||||
#
|
||||
@moduledoc """
|
||||
URL-safe string shortening.
|
||||
"""
|
||||
|
||||
@doc """
|
||||
Generates a URL-safe slug from a string by removing nonessential
|
||||
information from it.
|
||||
|
||||
The process for this is as follows:
|
||||
|
||||
1. Remove non-ASCII or non-printable characters.
|
||||
2. Replace any runs of non-alphanumeric characters that were allowed
|
||||
through previously with hyphens.
|
||||
3. Remove any starting or ending hyphens.
|
||||
4. Convert all characters to their lowercase equivalents.
|
||||
|
||||
This method makes no guarantee of creating unique slugs for unique inputs.
|
||||
In addition, for certain inputs, it will return empty strings.
|
||||
|
||||
## Example
|
||||
|
||||
iex> destructive_slug("Time-Wasting Thread 3.0 (SFW - No Explicit/Grimdark)")
|
||||
"time-wasting-thread-3-0-sfw-no-explicit-grimdark"
|
||||
|
||||
iex> destructive_slug("~`!@#$%^&*()-_=+[]{};:'\" <>,./?")
|
||||
""
|
||||
|
||||
"""
|
||||
@spec destructive_slug(String.t()) :: String.t()
|
||||
def destructive_slug(input) when is_binary(input) do
|
||||
input
|
||||
|
@ -39,6 +42,11 @@ defmodule Philomena.Slug do
|
|||
|
||||
def destructive_slug(_input), do: ""
|
||||
|
||||
@doc """
|
||||
Generates a reversible slug from a string by replacing certain characters
|
||||
with escaped (not URL-encoded) equivalents.
|
||||
"""
|
||||
@spec slug(String.t()) :: String.t()
|
||||
def slug(string) when is_binary(string) do
|
||||
string
|
||||
|> String.replace("-", "-dash-")
|
||||
|
|
|
@ -24,6 +24,19 @@ defmodule Philomena.Tags do
|
|||
alias Philomena.DnpEntries.DnpEntry
|
||||
alias Philomena.Channels.Channel
|
||||
|
||||
@doc """
|
||||
Gets existing tags or creates new ones from a tag list string.
|
||||
|
||||
Takes a string of comma-separated tag names, parses it into individual tags,
|
||||
and either retrieves existing tags or creates new ones for tags that don't exist.
|
||||
Also handles tag aliases by returning the aliased tag instead of the alias.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> get_or_create_tags("safe, cute, pony")
|
||||
[%Tag{name: "safe"}, %Tag{name: "cute"}, %Tag{name: "pony"}]
|
||||
|
||||
"""
|
||||
@spec get_or_create_tags(String.t()) :: list()
|
||||
def get_or_create_tags(tag_list) do
|
||||
tag_names = Tag.parse_tag_list(tag_list)
|
||||
|
@ -174,6 +187,18 @@ defmodule Philomena.Tags do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a tag's associated image.
|
||||
|
||||
Takes a tag and image upload attributes, analyzes the upload,
|
||||
persists it, and removes the old tag image if successful.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_tag_image(tag, %{"image" => upload})
|
||||
{:ok, %Tag{}}
|
||||
|
||||
"""
|
||||
def update_tag_image(%Tag{} = tag, attrs) do
|
||||
tag
|
||||
|> Uploader.analyze_upload(attrs)
|
||||
|
@ -190,6 +215,17 @@ defmodule Philomena.Tags do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes a tag's associated image.
|
||||
|
||||
Removes the image from the tag and deletes the persisted file.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> remove_tag_image(tag)
|
||||
{:ok, %Tag{}}
|
||||
|
||||
"""
|
||||
def remove_tag_image(%Tag{} = tag) do
|
||||
tag
|
||||
|> Tag.remove_image_changeset()
|
||||
|
@ -223,6 +259,18 @@ defmodule Philomena.Tags do
|
|||
{:ok, tag}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs the actual deletion of a tag.
|
||||
|
||||
Removes the tag from the database, deletes its search index,
|
||||
and reindexes all images that were tagged with it.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> perform_delete(123)
|
||||
:ok
|
||||
|
||||
"""
|
||||
def perform_delete(tag_id) do
|
||||
tag = get_tag!(tag_id)
|
||||
|
||||
|
@ -243,6 +291,19 @@ defmodule Philomena.Tags do
|
|||
|> Search.reindex(Image)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Creates an alias from one tag to another.
|
||||
|
||||
Takes a source tag and target tag name, creating an alias relationship
|
||||
where the source tag becomes an alias of the target tag. Once the alias
|
||||
is created, a job is queued to finish processing the alias.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> alias_tag(source_tag, %{"target_tag" => "destination"})
|
||||
{:ok, %Tag{}}
|
||||
|
||||
"""
|
||||
def alias_tag(%Tag{} = tag, attrs) do
|
||||
target_tag = Repo.get_by(Tag, name: String.downcase(attrs["target_tag"]))
|
||||
|
||||
|
@ -261,6 +322,19 @@ defmodule Philomena.Tags do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs the actual tag aliasing operation.
|
||||
|
||||
Transfers all associations from the source tag to the target tag,
|
||||
including image taggings, filters, user watches, and other relationships.
|
||||
Updates counters and reindexes affected records.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> perform_alias(123, 456)
|
||||
:ok
|
||||
|
||||
"""
|
||||
def perform_alias(tag_id, target_tag_id) do
|
||||
tag = get_tag!(tag_id)
|
||||
target_tag = get_tag!(target_tag_id)
|
||||
|
@ -315,14 +389,36 @@ defmodule Philomena.Tags do
|
|||
# Finally, reindex
|
||||
reindex_tag_images(target_tag)
|
||||
reindex_tags([tag, target_tag])
|
||||
|
||||
:ok
|
||||
end
|
||||
|
||||
@doc """
|
||||
Enqueues reindexing of all images associated with a tag.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> reindex_tag_images(tag)
|
||||
{:ok, %Tag{}}
|
||||
|
||||
"""
|
||||
def reindex_tag_images(%Tag{} = tag) do
|
||||
Exq.enqueue(Exq, "indexing", TagReindexWorker, [tag.id])
|
||||
|
||||
{:ok, tag}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs reindexing of all images associated with a tag.
|
||||
|
||||
Updates the tag's image count to reflect the current number of non-hidden images,
|
||||
then reindexes all associated images and filters that reference this tag.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> perform_reindex_images(123)
|
||||
|
||||
"""
|
||||
def perform_reindex_images(tag_id) do
|
||||
tag = get_tag!(tag_id)
|
||||
|
||||
|
@ -351,12 +447,32 @@ defmodule Philomena.Tags do
|
|||
|> Search.reindex(Filter)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Enqueues removal of a tag alias.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unalias_tag(tag)
|
||||
{:ok, %Tag{}}
|
||||
|
||||
"""
|
||||
def unalias_tag(%Tag{} = tag) do
|
||||
Exq.enqueue(Exq, "indexing", TagUnaliasWorker, [tag.id])
|
||||
|
||||
{:ok, tag}
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs removal of a tag alias.
|
||||
|
||||
Removes the alias relationship between two tags and reindexes
|
||||
the images of the formerly aliased tag.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> perform_unalias(123)
|
||||
{:ok, %Tag{}}
|
||||
"""
|
||||
def perform_unalias(tag_id) do
|
||||
tag = get_tag!(tag_id)
|
||||
former_alias = Repo.preload(tag, :aliased_tag).aliased_tag
|
||||
|
@ -389,6 +505,18 @@ defmodule Philomena.Tags do
|
|||
|> Repo.update_all([])
|
||||
end
|
||||
|
||||
@doc """
|
||||
Copies tags from one image to another.
|
||||
|
||||
Creates new taggings on the target image for all tags present on the source image,
|
||||
updates tag counters, and returns the list of copied tags.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> copy_tags(source_image, target_image)
|
||||
[%Tag{}, ...]
|
||||
|
||||
"""
|
||||
def copy_tags(source, target) do
|
||||
# Ecto bug:
|
||||
# ** (DBConnection.EncodeError) Postgrex expected a binary, got 5.
|
||||
|
@ -437,22 +565,66 @@ defmodule Philomena.Tags do
|
|||
Tag.changeset(tag, %{})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Queues a single tag for search index updates.
|
||||
Returns the tag struct unchanged, for use in a pipeline.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> reindex_tag(tag)
|
||||
%Tag{}
|
||||
|
||||
"""
|
||||
def reindex_tag(%Tag{} = tag) do
|
||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Tags", "id", [tag.id]])
|
||||
|
||||
tag
|
||||
end
|
||||
|
||||
@doc """
|
||||
Queues a list of tags for search index updates.
|
||||
Returns the list of tags unchanged, for use in a pipeline.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> reindex_tags([%Tag{}, %Tag{}, ...])
|
||||
[%Tag{}, %Tag{}, ...]
|
||||
|
||||
"""
|
||||
def reindex_tags(tags) do
|
||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Tags", "id", Enum.map(tags, & &1.id)])
|
||||
|
||||
tags
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns the list of associations to preload for tag indexing.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> indexing_preloads()
|
||||
[:aliased_tag, :aliases, :implied_tags, :implied_by_tags]
|
||||
|
||||
"""
|
||||
def indexing_preloads do
|
||||
[:aliased_tag, :aliases, :implied_tags, :implied_by_tags]
|
||||
end
|
||||
|
||||
@doc """
|
||||
Performs reindexing of tags based on a column condition.
|
||||
|
||||
Takes a column name and a list of values to match against that column,
|
||||
then reindexes all matching tags.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> perform_reindex(:id, [1, 2, 3])
|
||||
{:ok, []}
|
||||
|
||||
iex> perform_reindex(:name, ["safe", "suggestive"])
|
||||
{:ok, []}
|
||||
|
||||
"""
|
||||
def perform_reindex(column, condition) do
|
||||
Tag
|
||||
|> preload(^indexing_preloads())
|
||||
|
|
|
@ -138,26 +138,71 @@ defmodule Philomena.Topics do
|
|||
Topic.changeset(topic, %{})
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes a topic sticky, appearing at the top of its forum.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> stick_topic(topic)
|
||||
{:ok, %Topic{}}
|
||||
|
||||
"""
|
||||
def stick_topic(topic) do
|
||||
Topic.stick_changeset(topic)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes sticky status from a topic.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unstick_topic(topic)
|
||||
{:ok, %Topic{}}
|
||||
|
||||
"""
|
||||
def unstick_topic(topic) do
|
||||
Topic.unstick_changeset(topic)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Locks a topic to prevent further posting.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> lock_topic(topic, %{"lock_reason" => "Off topic"}, user)
|
||||
{:ok, %Topic{}}
|
||||
|
||||
"""
|
||||
def lock_topic(%Topic{} = topic, attrs, user) do
|
||||
Topic.lock_changeset(topic, attrs, user)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unlocks a topic to allow posting again.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unlock_topic(topic)
|
||||
{:ok, %Topic{}}
|
||||
|
||||
"""
|
||||
def unlock_topic(%Topic{} = topic) do
|
||||
Topic.unlock_changeset(topic)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Moves a topic to a different forum, updating post counts for both forums.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> move_topic(topic, 123)
|
||||
{:ok, %{topic: %Topic{}}}
|
||||
|
||||
"""
|
||||
def move_topic(topic, new_forum_id) do
|
||||
old_forum_id = topic.forum_id
|
||||
topic_changes = Topic.move_changeset(topic, new_forum_id)
|
||||
|
@ -183,6 +228,15 @@ defmodule Philomena.Topics do
|
|||
|> Repo.transaction()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Hides a topic and updates related forum data.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> hide_topic(topic, "Violates rules", moderator)
|
||||
{:ok, %Topic{}}
|
||||
|
||||
"""
|
||||
def hide_topic(topic, deletion_reason, user) do
|
||||
topic_changes = Topic.hide_changeset(topic, deletion_reason, user)
|
||||
|
||||
|
@ -205,11 +259,29 @@ defmodule Philomena.Topics do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unhides a previously hidden topic.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unhide_topic(topic)
|
||||
{:ok, %Topic{}}
|
||||
|
||||
"""
|
||||
def unhide_topic(topic) do
|
||||
Topic.unhide_changeset(topic)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a topic's title.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_topic_title(topic, %{"title" => "New Title"})
|
||||
{:ok, %Topic{}}
|
||||
|
||||
"""
|
||||
def update_topic_title(topic, attrs) do
|
||||
topic
|
||||
|> Topic.title_changeset(attrs)
|
||||
|
|
|
@ -273,6 +273,12 @@ defmodule Philomena.Users do
|
|||
|
||||
@doc """
|
||||
Unconditionally unlocks the given user.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unlock_user(user)
|
||||
{:ok, %User{}}
|
||||
|
||||
"""
|
||||
def unlock_user(user) do
|
||||
user
|
||||
|
@ -369,6 +375,20 @@ defmodule Philomena.Users do
|
|||
load_with_roles(query)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Checks if a TOTP token is valid for a given user.
|
||||
|
||||
Returns false if no user is provided.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> user_totp_token_valid?(user, "123456")
|
||||
true
|
||||
|
||||
iex> user_totp_token_valid?(nil, "123456")
|
||||
false
|
||||
|
||||
"""
|
||||
def user_totp_token_valid?(nil, _token) do
|
||||
false
|
||||
end
|
||||
|
@ -503,6 +523,15 @@ defmodule Philomena.Users do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Returns an `%Ecto.Changeset{}` for tracking user changes.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> change_user(user)
|
||||
%Ecto.Changeset{data: %User{}}
|
||||
|
||||
"""
|
||||
def change_user(%User{} = user) do
|
||||
User.changeset(user, %{})
|
||||
end
|
||||
|
@ -544,30 +573,84 @@ defmodule Philomena.Users do
|
|||
defp clean_roles(nil), do: []
|
||||
defp clean_roles(roles), do: Enum.filter(roles, &("" != &1))
|
||||
|
||||
@doc """
|
||||
Updates a user's spoiler type settings.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_spoiler_type(user, %{spoiler_type: "click"})
|
||||
{:ok, %User{}}
|
||||
|
||||
iex> update_spoiler_type(user, %{spoiler_type: bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_spoiler_type(%User{} = user, attrs) do
|
||||
user
|
||||
|> User.spoiler_type_changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a user's general settings.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_settings(user, %{"theme" => "dark"})
|
||||
{:ok, %User{}}
|
||||
|
||||
iex> update_settings(user, %{"theme" => bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_settings(%User{} = user, attrs) do
|
||||
user
|
||||
|> User.settings_changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a user's profile description and personal title.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_description(user, %{"description" => "Hello world"})
|
||||
{:ok, %User{}}
|
||||
|
||||
iex> update_description(user, %{"personal_title" => "Site Admin"})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def update_description(%User{} = user, attrs) do
|
||||
user
|
||||
|> User.description_changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a user's moderation scratchpad content.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_scratchpad(user, %{"scratchpad" => "My notes"})
|
||||
{:ok, %User{}}
|
||||
|
||||
"""
|
||||
def update_scratchpad(%User{} = user, attrs) do
|
||||
user
|
||||
|> User.scratchpad_changeset(attrs)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Adds a tag to a user's watched tags list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> watch_tag(user, tag)
|
||||
{:ok, %User{}}
|
||||
|
||||
"""
|
||||
def watch_tag(%User{} = user, tag) do
|
||||
watched_tag_ids = Enum.uniq([tag.id | user.watched_tag_ids])
|
||||
|
||||
|
@ -576,6 +659,15 @@ defmodule Philomena.Users do
|
|||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes a tag from a user's watched tags list.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unwatch_tag(user, tag)
|
||||
{:ok, %User{}}
|
||||
|
||||
"""
|
||||
def unwatch_tag(%User{} = user, tag) do
|
||||
watched_tag_ids = user.watched_tag_ids -- [tag.id]
|
||||
|
||||
|
@ -584,6 +676,17 @@ defmodule Philomena.Users do
|
|||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a user's avatar with the provided file.
|
||||
|
||||
Handles file analysis and persistence.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_avatar(user, %{"avatar" => upload})
|
||||
{:ok, %User{}}
|
||||
|
||||
"""
|
||||
def update_avatar(%User{} = user, attrs) do
|
||||
user
|
||||
|> Uploader.analyze_upload(attrs)
|
||||
|
@ -600,6 +703,15 @@ defmodule Philomena.Users do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes a user's avatar.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> remove_avatar(user)
|
||||
{:ok, %User{}}
|
||||
|
||||
"""
|
||||
def remove_avatar(%User{} = user) do
|
||||
user
|
||||
|> User.remove_avatar_changeset()
|
||||
|
@ -615,6 +727,17 @@ defmodule Philomena.Users do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates a user's name and records the change in history.
|
||||
|
||||
Triggers a background job to update references to the old username.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> update_name(user, %{"name" => "new_name"})
|
||||
{:ok, %User{}}
|
||||
|
||||
"""
|
||||
def update_name(user, user_params) do
|
||||
old_name = user.name
|
||||
|
||||
|
@ -636,6 +759,17 @@ defmodule Philomena.Users do
|
|||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Updates all search engine references to a user's old name with their new name.
|
||||
|
||||
This is called as a background job after a user requests a name change.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> perform_rename("old_name", "new_name")
|
||||
:ok
|
||||
|
||||
"""
|
||||
def perform_rename(old_name, new_name) do
|
||||
Images.user_name_reindex(old_name, new_name)
|
||||
Comments.user_name_reindex(old_name, new_name)
|
||||
|
@ -645,36 +779,96 @@ defmodule Philomena.Users do
|
|||
Filters.user_name_reindex(old_name, new_name)
|
||||
end
|
||||
|
||||
@doc """
|
||||
Reactivates a previously deactivated user account.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> reactivate_user(user)
|
||||
{:ok, %User{}}
|
||||
|
||||
"""
|
||||
def reactivate_user(%User{} = user) do
|
||||
user
|
||||
|> User.reactivate_changeset()
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Deactivates a user account.
|
||||
|
||||
Takes a moderator who is recorded as performing the deactivation.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> deactivate_user(moderator, user)
|
||||
{:ok, %User{}}
|
||||
|
||||
"""
|
||||
def deactivate_user(moderator, %User{} = user) do
|
||||
user
|
||||
|> User.deactivate_changeset(moderator)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Generates a new API key for the user.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> reset_api_key(user)
|
||||
{:ok, %User{}}
|
||||
|
||||
"""
|
||||
def reset_api_key(%User{} = user) do
|
||||
user
|
||||
|> User.api_key_changeset()
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Forces a specific filter on a user's account, which will be applied in
|
||||
conjunction to the user's current filter.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> force_filter(user, %{"forced_filter_id" => 123})
|
||||
{:ok, %User{}}
|
||||
|
||||
iex> force_filter(user, %{"forced_filter_id" => bad_value})
|
||||
{:error, %Ecto.Changeset{}}
|
||||
|
||||
"""
|
||||
def force_filter(%User{} = user, user_params) do
|
||||
user
|
||||
|> User.force_filter_changeset(user_params)
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Removes a forced filter from a user's account.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unforce_filter(user)
|
||||
{:ok, %User{}}
|
||||
|
||||
"""
|
||||
def unforce_filter(%User{} = user) do
|
||||
user
|
||||
|> User.unforce_filter_changeset()
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Clears a user's recent filter history.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> clear_recent_filters(user)
|
||||
{:ok, %User{}}
|
||||
|
||||
"""
|
||||
def clear_recent_filters(%User{} = user) do
|
||||
user
|
||||
|> User.clear_recent_filters_changeset()
|
||||
|
@ -688,18 +882,50 @@ defmodule Philomena.Users do
|
|||
|> setup_roles()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Marks a user as verified for the purposes of automatically approving uploads,
|
||||
and posting images in comments/posts/messages without moderator review.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> verify_user(user)
|
||||
{:ok, %User{}}
|
||||
|
||||
"""
|
||||
def verify_user(%User{} = user) do
|
||||
user
|
||||
|> User.verify_changeset()
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Unverifies a user, removing the automatic approval status.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> unverify_user(user)
|
||||
{:ok, %User{}}
|
||||
|
||||
"""
|
||||
def unverify_user(%User{} = user) do
|
||||
user
|
||||
|> User.unverify_changeset()
|
||||
|> Repo.update()
|
||||
end
|
||||
|
||||
@doc """
|
||||
Erases all changes associated with a user account, removing all personal
|
||||
data and anonymizing the account.
|
||||
|
||||
This is primarily intended for use with spam accounts or other situations
|
||||
where all of a user's data should be removed from the system.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> erase_user(user, moderator)
|
||||
{:ok, %User{}}
|
||||
|
||||
"""
|
||||
def erase_user(%User{} = user, %User{} = moderator) do
|
||||
# Deactivate to prevent the user from racing these changes
|
||||
{:ok, user} = deactivate_user(moderator, user)
|
||||
|
|
Loading…
Reference in a new issue