mirror of
https://github.com/philomena-dev/philomena.git
synced 2025-02-08 15:16:43 +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)
|
Repo.delete(comment)
|
||||||
end
|
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
|
def hide_comment(%Comment{} = comment, attrs, user) do
|
||||||
report_query = Reports.close_report_query({"Comment", comment.id}, user)
|
report_query = Reports.close_report_query({"Comment", comment.id}, user)
|
||||||
comment = Comment.hide_changeset(comment, attrs, user)
|
comment = Comment.hide_changeset(comment, attrs, user)
|
||||||
|
@ -139,6 +153,15 @@ defmodule Philomena.Comments do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Unhides a previously hidden comment.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> unhide_comment(comment)
|
||||||
|
{:ok, %Comment{}}
|
||||||
|
|
||||||
|
"""
|
||||||
def unhide_comment(%Comment{} = comment) do
|
def unhide_comment(%Comment{} = comment) do
|
||||||
comment
|
comment
|
||||||
|> Comment.unhide_changeset()
|
|> Comment.unhide_changeset()
|
||||||
|
@ -154,12 +177,35 @@ defmodule Philomena.Comments do
|
||||||
end
|
end
|
||||||
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
|
def destroy_comment(%Comment{} = comment) do
|
||||||
comment
|
comment
|
||||||
|> Comment.destroy_changeset()
|
|> Comment.destroy_changeset()
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def approve_comment(%Comment{} = comment, user) do
|
||||||
report_query = Reports.close_report_query({"Comment", comment.id}, user)
|
report_query = Reports.close_report_query({"Comment", comment.id}, user)
|
||||||
comment = Comment.approve_changeset(comment)
|
comment = Comment.approve_changeset(comment)
|
||||||
|
@ -181,6 +227,23 @@ defmodule Philomena.Comments do
|
||||||
end
|
end
|
||||||
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{approved: true}), do: false
|
||||||
|
|
||||||
def report_non_approved(comment) do
|
def report_non_approved(comment) do
|
||||||
|
@ -191,6 +254,20 @@ defmodule Philomena.Comments do
|
||||||
)
|
)
|
||||||
end
|
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
|
def migrate_comments(image, duplicate_of_image) do
|
||||||
{count, nil} =
|
{count, nil} =
|
||||||
Comment
|
Comment
|
||||||
|
@ -217,24 +294,62 @@ defmodule Philomena.Comments do
|
||||||
Comment.changeset(comment, %{})
|
Comment.changeset(comment, %{})
|
||||||
end
|
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
|
def user_name_reindex(old_name, new_name) do
|
||||||
data = CommentIndex.user_name_update_by_query(old_name, new_name)
|
data = CommentIndex.user_name_update_by_query(old_name, new_name)
|
||||||
|
|
||||||
Search.update_by_query(Comment, data.query, data.set_replacements, data.replacements)
|
Search.update_by_query(Comment, data.query, data.set_replacements, data.replacements)
|
||||||
end
|
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
|
def reindex_comment(%Comment{} = comment) do
|
||||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Comments", "id", [comment.id]])
|
Exq.enqueue(Exq, "indexing", IndexWorker, ["Comments", "id", [comment.id]])
|
||||||
|
|
||||||
comment
|
comment
|
||||||
end
|
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
|
def reindex_comments(image) do
|
||||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Comments", "image_id", [image.id]])
|
Exq.enqueue(Exq, "indexing", IndexWorker, ["Comments", "image_id", [image.id]])
|
||||||
|
|
||||||
image
|
image
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Provides preload queries for comment indexing operations.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> indexing_preloads()
|
||||||
|
[user: user_query, image: image_query]
|
||||||
|
|
||||||
|
"""
|
||||||
def indexing_preloads do
|
def indexing_preloads do
|
||||||
user_query = select(User, [u], map(u, [:id, :name]))
|
user_query = select(User, [u], map(u, [:id, :name]))
|
||||||
tag_query = select(Tag, [t], map(t, [:id]))
|
tag_query = select(Tag, [t], map(t, [:id]))
|
||||||
|
@ -250,6 +365,22 @@ defmodule Philomena.Comments do
|
||||||
]
|
]
|
||||||
end
|
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
|
def perform_reindex(column, condition) do
|
||||||
Comment
|
Comment
|
||||||
|> preload(^indexing_preloads())
|
|> preload(^indexing_preloads())
|
||||||
|
|
|
@ -77,6 +77,18 @@ defmodule Philomena.DnpEntries do
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def transition_dnp_entry(%DnpEntry{} = dnp_entry, user, new_state) do
|
||||||
dnp_entry
|
dnp_entry
|
||||||
|> DnpEntry.transition_changeset(user, new_state)
|
|> DnpEntry.transition_changeset(user, new_state)
|
||||||
|
@ -112,6 +124,19 @@ defmodule Philomena.DnpEntries do
|
||||||
DnpEntry.changeset(dnp_entry, %{})
|
DnpEntry.changeset(dnp_entry, %{})
|
||||||
end
|
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
|
def count_dnp_entries(user) do
|
||||||
if Canada.Can.can?(user, :index, DnpEntry) do
|
if Canada.Can.can?(user, :index, DnpEntry) do
|
||||||
DnpEntry
|
DnpEntry
|
||||||
|
|
|
@ -16,6 +16,18 @@ defmodule Philomena.DuplicateReports do
|
||||||
alias Philomena.Images.Image
|
alias Philomena.Images.Image
|
||||||
alias Philomena.Images
|
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
|
def generate_reports(source) do
|
||||||
source = Repo.preload(source, :intensity)
|
source = Repo.preload(source, :intensity)
|
||||||
|
|
||||||
|
@ -30,6 +42,23 @@ defmodule Philomena.DuplicateReports do
|
||||||
end)
|
end)
|
||||||
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
|
def find_duplicates({intensities, aspect_ratio}, opts \\ []) do
|
||||||
aspect_dist = Keyword.get(opts, :aspect_dist, 0.05)
|
aspect_dist = Keyword.get(opts, :aspect_dist, 0.05)
|
||||||
limit = Keyword.get(opts, :limit, 10)
|
limit = Keyword.get(opts, :limit, 10)
|
||||||
|
@ -150,6 +179,21 @@ defmodule Philomena.DuplicateReports do
|
||||||
|> Repo.insert()
|
|> Repo.insert()
|
||||||
end
|
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
|
def accept_duplicate_report(multi \\ nil, %DuplicateReport{} = duplicate_report, user) do
|
||||||
duplicate_report = Repo.preload(duplicate_report, [:image, :duplicate_of_image])
|
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)
|
|> Images.merge_image(duplicate_report.image, duplicate_report.duplicate_of_image, user)
|
||||||
end
|
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
|
def accept_reverse_duplicate_report(%DuplicateReport{} = duplicate_report, user) do
|
||||||
new_report =
|
new_report =
|
||||||
DuplicateReport
|
DuplicateReport
|
||||||
|
@ -204,18 +260,47 @@ defmodule Philomena.DuplicateReports do
|
||||||
|> accept_duplicate_report(new_report, user)
|
|> accept_duplicate_report(new_report, user)
|
||||||
end
|
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
|
def claim_duplicate_report(%DuplicateReport{} = duplicate_report, user) do
|
||||||
duplicate_report
|
duplicate_report
|
||||||
|> DuplicateReport.claim_changeset(user)
|
|> DuplicateReport.claim_changeset(user)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def unclaim_duplicate_report(%DuplicateReport{} = duplicate_report) do
|
||||||
duplicate_report
|
duplicate_report
|
||||||
|> DuplicateReport.unclaim_changeset()
|
|> DuplicateReport.unclaim_changeset()
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def reject_duplicate_report(%DuplicateReport{} = duplicate_report, user) do
|
||||||
duplicate_report
|
duplicate_report
|
||||||
|> DuplicateReport.reject_changeset(user)
|
|> DuplicateReport.reject_changeset(user)
|
||||||
|
@ -251,6 +336,19 @@ defmodule Philomena.DuplicateReports do
|
||||||
DuplicateReport.changeset(duplicate_report, %{})
|
DuplicateReport.changeset(duplicate_report, %{})
|
||||||
end
|
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
|
def count_duplicate_reports(user) do
|
||||||
if Canada.Can.can?(user, :index, DuplicateReport) do
|
if Canada.Can.can?(user, :index, DuplicateReport) do
|
||||||
DuplicateReport
|
DuplicateReport
|
||||||
|
|
|
@ -93,6 +93,17 @@ defmodule Philomena.Filters do
|
||||||
|> reindex_after_update()
|
|> reindex_after_update()
|
||||||
end
|
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
|
def make_filter_public(%Filter{} = filter) do
|
||||||
filter
|
filter
|
||||||
|> Filter.public_changeset()
|
|> Filter.public_changeset()
|
||||||
|
@ -140,6 +151,21 @@ defmodule Philomena.Filters do
|
||||||
Filter.changeset(filter, %{})
|
Filter.changeset(filter, %{})
|
||||||
end
|
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
|
def recent_and_user_filters(user) do
|
||||||
recent_filter_ids =
|
recent_filter_ids =
|
||||||
[user.current_filter_id | user.recent_filter_ids]
|
[user.current_filter_id | user.recent_filter_ids]
|
||||||
|
@ -174,6 +200,17 @@ defmodule Philomena.Filters do
|
||||||
|> Enum.reverse()
|
|> Enum.reverse()
|
||||||
end
|
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
|
def hide_tag(filter, tag) do
|
||||||
hidden_tag_ids = Enum.uniq([tag.id | filter.hidden_tag_ids])
|
hidden_tag_ids = Enum.uniq([tag.id | filter.hidden_tag_ids])
|
||||||
|
|
||||||
|
@ -183,6 +220,15 @@ defmodule Philomena.Filters do
|
||||||
|> reindex_after_update()
|
|> reindex_after_update()
|
||||||
end
|
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
|
def unhide_tag(filter, tag) do
|
||||||
hidden_tag_ids = filter.hidden_tag_ids -- [tag.id]
|
hidden_tag_ids = filter.hidden_tag_ids -- [tag.id]
|
||||||
|
|
||||||
|
@ -192,6 +238,15 @@ defmodule Philomena.Filters do
|
||||||
|> reindex_after_update()
|
|> reindex_after_update()
|
||||||
end
|
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
|
def spoiler_tag(filter, tag) do
|
||||||
spoilered_tag_ids = Enum.uniq([tag.id | filter.spoilered_tag_ids])
|
spoilered_tag_ids = Enum.uniq([tag.id | filter.spoilered_tag_ids])
|
||||||
|
|
||||||
|
@ -201,6 +256,15 @@ defmodule Philomena.Filters do
|
||||||
|> reindex_after_update()
|
|> reindex_after_update()
|
||||||
end
|
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
|
def unspoiler_tag(filter, tag) do
|
||||||
spoilered_tag_ids = filter.spoilered_tag_ids -- [tag.id]
|
spoilered_tag_ids = filter.spoilered_tag_ids -- [tag.id]
|
||||||
|
|
||||||
|
@ -210,38 +274,92 @@ defmodule Philomena.Filters do
|
||||||
|> reindex_after_update()
|
|> reindex_after_update()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp reindex_after_update({:ok, filter}) do
|
defp reindex_after_update(result) do
|
||||||
|
case result do
|
||||||
|
{:ok, filter} ->
|
||||||
reindex_filter(filter)
|
reindex_filter(filter)
|
||||||
|
|
||||||
{:ok, filter}
|
{:ok, filter}
|
||||||
end
|
|
||||||
|
|
||||||
defp reindex_after_update(error) do
|
error ->
|
||||||
error
|
error
|
||||||
end
|
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
|
def user_name_reindex(old_name, new_name) do
|
||||||
data = FilterIndex.user_name_update_by_query(old_name, new_name)
|
data = FilterIndex.user_name_update_by_query(old_name, new_name)
|
||||||
|
|
||||||
Search.update_by_query(Filter, data.query, data.set_replacements, data.replacements)
|
Search.update_by_query(Filter, data.query, data.set_replacements, data.replacements)
|
||||||
end
|
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
|
def reindex_filter(%Filter{} = filter) do
|
||||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Filters", "id", [filter.id]])
|
Exq.enqueue(Exq, "indexing", IndexWorker, ["Filters", "id", [filter.id]])
|
||||||
|
|
||||||
filter
|
filter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Removes a filter from the search index.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> unindex_filter(filter)
|
||||||
|
%Filter{}
|
||||||
|
|
||||||
|
"""
|
||||||
def unindex_filter(%Filter{} = filter) do
|
def unindex_filter(%Filter{} = filter) do
|
||||||
Search.delete_document(filter.id, Filter)
|
Search.delete_document(filter.id, Filter)
|
||||||
|
|
||||||
filter
|
filter
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns a list of associations to preload when indexing filters.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> indexing_preloads()
|
||||||
|
[:user]
|
||||||
|
|
||||||
|
"""
|
||||||
def indexing_preloads do
|
def indexing_preloads do
|
||||||
[:user]
|
[:user]
|
||||||
end
|
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
|
def perform_reindex(column, condition) do
|
||||||
Filter
|
Filter
|
||||||
|> preload(^indexing_preloads())
|
|> preload(^indexing_preloads())
|
||||||
|
|
|
@ -121,6 +121,15 @@ defmodule Philomena.Galleries do
|
||||||
Gallery.changeset(gallery, %{})
|
Gallery.changeset(gallery, %{})
|
||||||
end
|
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
|
def user_name_reindex(old_name, new_name) do
|
||||||
data = GalleryIndex.user_name_update_by_query(old_name, new_name)
|
data = GalleryIndex.user_name_update_by_query(old_name, new_name)
|
||||||
|
|
||||||
|
@ -137,22 +146,65 @@ defmodule Philomena.Galleries do
|
||||||
error
|
error
|
||||||
end
|
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
|
def reindex_gallery(%Gallery{} = gallery) do
|
||||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Galleries", "id", [gallery.id]])
|
Exq.enqueue(Exq, "indexing", IndexWorker, ["Galleries", "id", [gallery.id]])
|
||||||
|
|
||||||
gallery
|
gallery
|
||||||
end
|
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
|
def unindex_gallery(%Gallery{} = gallery) do
|
||||||
Search.delete_document(gallery.id, Gallery)
|
Search.delete_document(gallery.id, Gallery)
|
||||||
|
|
||||||
gallery
|
gallery
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns a list of associations to preload when indexing galleries.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> indexing_preloads()
|
||||||
|
[:subscribers, :creator, :interactions]
|
||||||
|
|
||||||
|
"""
|
||||||
def indexing_preloads do
|
def indexing_preloads do
|
||||||
[:subscribers, :creator, :interactions]
|
[:subscribers, :creator, :interactions]
|
||||||
end
|
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
|
def perform_reindex(column, condition) do
|
||||||
Gallery
|
Gallery
|
||||||
|> preload(^indexing_preloads())
|
|> preload(^indexing_preloads())
|
||||||
|
@ -160,6 +212,24 @@ defmodule Philomena.Galleries do
|
||||||
|> Search.reindex(Gallery)
|
|> Search.reindex(Gallery)
|
||||||
end
|
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
|
def add_image_to_gallery(gallery, image) do
|
||||||
Multi.new()
|
Multi.new()
|
||||||
|> Multi.run(:gallery, fn repo, %{} ->
|
|> Multi.run(:gallery, fn repo, %{} ->
|
||||||
|
@ -202,6 +272,21 @@ defmodule Philomena.Galleries do
|
||||||
end
|
end
|
||||||
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
|
def remove_image_from_gallery(gallery, image) do
|
||||||
Multi.new()
|
Multi.new()
|
||||||
|> Multi.run(:gallery, fn repo, %{} ->
|
|> Multi.run(:gallery, fn repo, %{} ->
|
||||||
|
@ -254,10 +339,35 @@ defmodule Philomena.Galleries do
|
||||||
|> Repo.aggregate(:max, :position)
|
|> Repo.aggregate(:max, :position)
|
||||||
end
|
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
|
def reorder_gallery(gallery, image_ids) do
|
||||||
Exq.enqueue(Exq, "indexing", GalleryReorderWorker, [gallery.id, image_ids])
|
Exq.enqueue(Exq, "indexing", GalleryReorderWorker, [gallery.id, image_ids])
|
||||||
|
|
||||||
|
gallery
|
||||||
end
|
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
|
def perform_reorder(gallery_id, image_ids) do
|
||||||
gallery = get_gallery!(gallery_id)
|
gallery = get_gallery!(gallery_id)
|
||||||
|
|
||||||
|
@ -320,6 +430,8 @@ defmodule Philomena.Galleries do
|
||||||
|
|
||||||
# Now update all the associated images
|
# Now update all the associated images
|
||||||
Images.reindex_images(Map.keys(requested))
|
Images.reindex_images(Map.keys(requested))
|
||||||
|
|
||||||
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
defp position_order(%{order_position_asc: true}), do: [asc: :position]
|
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")
|
Logger.error("Aborting upload of #{image.id} after #{retry_count} retries")
|
||||||
end
|
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
|
def approve_image(image) do
|
||||||
image
|
image
|
||||||
|> Repo.preload(:user)
|
|> Repo.preload(:user)
|
||||||
|
@ -197,6 +207,18 @@ defmodule Philomena.Images do
|
||||||
|
|
||||||
defp maybe_suggest_user_verification(_user), do: false
|
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
|
def count_pending_approvals(user) do
|
||||||
if Canada.Can.can?(user, :approve, %Image{}) do
|
if Canada.Can.can?(user, :approve, %Image{}) do
|
||||||
Image
|
Image
|
||||||
|
@ -208,12 +230,36 @@ defmodule Philomena.Images do
|
||||||
end
|
end
|
||||||
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
|
def feature_image(featurer, %Image{} = image) do
|
||||||
%ImageFeature{user_id: featurer.id, image_id: image.id}
|
%ImageFeature{user_id: featurer.id, image_id: image.id}
|
||||||
|> ImageFeature.changeset(%{})
|
|> ImageFeature.changeset(%{})
|
||||||
|> Repo.insert()
|
|> Repo.insert()
|
||||||
end
|
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
|
def destroy_image(%Image{} = image) do
|
||||||
image
|
image
|
||||||
|> Image.remove_image_changeset()
|
|> Image.remove_image_changeset()
|
||||||
|
@ -230,36 +276,91 @@ defmodule Philomena.Images do
|
||||||
end
|
end
|
||||||
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
|
def lock_comments(%Image{} = image, locked) do
|
||||||
image
|
image
|
||||||
|> Image.lock_comments_changeset(locked)
|
|> Image.lock_comments_changeset(locked)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def lock_description(%Image{} = image, locked) do
|
||||||
image
|
image
|
||||||
|> Image.lock_description_changeset(locked)
|
|> Image.lock_description_changeset(locked)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def lock_tags(%Image{} = image, locked) do
|
||||||
image
|
image
|
||||||
|> Image.lock_tags_changeset(locked)
|
|> Image.lock_tags_changeset(locked)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def remove_hash(%Image{} = image) do
|
||||||
image
|
image
|
||||||
|> Image.remove_hash_changeset()
|
|> Image.remove_hash_changeset()
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def update_scratchpad(%Image{} = image, attrs) do
|
||||||
image
|
image
|
||||||
|> Image.scratchpad_changeset(attrs)
|
|> Image.scratchpad_changeset(attrs)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def remove_source_history(%Image{} = image) do
|
||||||
image
|
image
|
||||||
|> Repo.preload(:source_changes)
|
|> Repo.preload(:source_changes)
|
||||||
|
@ -267,17 +368,49 @@ defmodule Philomena.Images do
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def repair_image(%Image{} = image) do
|
||||||
Image
|
Image
|
||||||
|> where(id: ^image.id)
|
|> where(id: ^image.id)
|
||||||
|> Repo.update_all(set: [thumbnails_generated: false, processed: false])
|
|> Repo.update_all(set: [thumbnails_generated: false, processed: false])
|
||||||
|
|
||||||
Exq.enqueue(Exq, queue(image.image_mime_type), ThumbnailWorker, [image.id])
|
Exq.enqueue(Exq, queue(image.image_mime_type), ThumbnailWorker, [image.id])
|
||||||
|
|
||||||
|
image
|
||||||
end
|
end
|
||||||
|
|
||||||
defp queue("video/webm"), do: "videos"
|
defp queue("video/webm"), do: "videos"
|
||||||
defp queue(_mime_type), do: "images"
|
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
|
def update_file(%Image{} = image, attrs) do
|
||||||
image
|
image
|
||||||
|> Image.changeset(attrs)
|
|> Image.changeset(attrs)
|
||||||
|
@ -316,12 +449,48 @@ defmodule Philomena.Images do
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Updates an image's description.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> update_description(image, %{"description" => "New description"})
|
||||||
|
{:ok, %Image{}}
|
||||||
|
|
||||||
|
"""
|
||||||
def update_description(%Image{} = image, attrs) do
|
def update_description(%Image{} = image, attrs) do
|
||||||
image
|
image
|
||||||
|> Image.description_changeset(attrs)
|
|> Image.description_changeset(attrs)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def update_sources(%Image{} = image, attribution, attrs) do
|
||||||
old_sources = attrs["old_sources"]
|
old_sources = attrs["old_sources"]
|
||||||
new_sources = attrs["sources"]
|
new_sources = attrs["sources"]
|
||||||
|
@ -383,6 +552,17 @@ defmodule Philomena.Images do
|
||||||
}
|
}
|
||||||
end
|
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
|
def update_locked_tags(%Image{} = image, attrs) do
|
||||||
new_tags = Tags.get_or_create_tags(attrs["tag_input"])
|
new_tags = Tags.get_or_create_tags(attrs["tag_input"])
|
||||||
|
|
||||||
|
@ -392,6 +572,33 @@ defmodule Philomena.Images do
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def update_tags(%Image{} = image, attribution, attrs) do
|
||||||
old_tags = Tags.get_or_create_tags(attrs["old_tag_input"])
|
old_tags = Tags.get_or_create_tags(attrs["old_tag_input"])
|
||||||
new_tags = Tags.get_or_create_tags(attrs["tag_input"])
|
new_tags = Tags.get_or_create_tags(attrs["tag_input"])
|
||||||
|
@ -486,14 +693,27 @@ defmodule Philomena.Images do
|
||||||
end
|
end
|
||||||
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
|
def update_tag_change_limits_after_commit(image, attribution) do
|
||||||
rating_changed_count = if(image.ratings_changed, do: 1, else: 0)
|
rating_changed_count = if(image.ratings_changed, do: 1, else: 0)
|
||||||
tag_changed_count = length(image.added_tags) + length(image.removed_tags)
|
tag_changed_count = length(image.added_tags) + length(image.removed_tags)
|
||||||
user = attribution[:user]
|
user = attribution[:user]
|
||||||
ip = attribution[:ip]
|
ip = attribution[:ip]
|
||||||
|
|
||||||
Limits.update_tag_count_after_update(user, ip, tag_changed_count)
|
:ok = 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_rating_count_after_update(user, ip, rating_changed_count)
|
||||||
|
:ok
|
||||||
end
|
end
|
||||||
|
|
||||||
defp tag_change_attributes(attribution, image, tag, added, user) do
|
defp tag_change_attributes(attribution, image, tag, added, user) do
|
||||||
|
@ -518,18 +738,48 @@ defmodule Philomena.Images do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Changes the uploader of an image.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> update_uploader(image, %{"username" => "Admin"})
|
||||||
|
{:ok, %Image{}}
|
||||||
|
|
||||||
|
"""
|
||||||
def update_uploader(%Image{} = image, attrs) do
|
def update_uploader(%Image{} = image, attrs) do
|
||||||
image
|
image
|
||||||
|> Image.uploader_changeset(attrs)
|
|> Image.uploader_changeset(attrs)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def update_anonymous(%Image{} = image, attrs) do
|
||||||
image
|
image
|
||||||
|> Image.anonymous_changeset(attrs)
|
|> Image.anonymous_changeset(attrs)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def update_hide_reason(%Image{} = image, attrs) do
|
||||||
image
|
image
|
||||||
|> Image.hide_reason_changeset(attrs)
|
|> Image.hide_reason_changeset(attrs)
|
||||||
|
@ -545,6 +795,28 @@ defmodule Philomena.Images do
|
||||||
end
|
end
|
||||||
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
|
def hide_image(%Image{} = image, user, attrs) do
|
||||||
duplicate_reports =
|
duplicate_reports =
|
||||||
DuplicateReport
|
DuplicateReport
|
||||||
|
@ -560,6 +832,33 @@ defmodule Philomena.Images do
|
||||||
|> process_after_hide()
|
|> process_after_hide()
|
||||||
end
|
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
|
def merge_image(multi \\ nil, %Image{} = image, duplicate_of_image, user) do
|
||||||
multi = multi || Multi.new()
|
multi = multi || Multi.new()
|
||||||
|
|
||||||
|
@ -675,6 +974,26 @@ defmodule Philomena.Images do
|
||||||
{:ok, image}
|
{:ok, image}
|
||||||
end
|
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
|
def unhide_image(%Image{hidden_from_users: true} = image) do
|
||||||
key = image.hidden_image_key
|
key = image.hidden_image_key
|
||||||
|
|
||||||
|
@ -711,6 +1030,24 @@ defmodule Philomena.Images do
|
||||||
|
|
||||||
def unhide_image(image), do: {:ok, image}
|
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
|
def batch_update(image_ids, added_tags, removed_tags, tag_change_attributes) do
|
||||||
image_ids =
|
image_ids =
|
||||||
Image
|
Image
|
||||||
|
@ -828,24 +1165,65 @@ defmodule Philomena.Images do
|
||||||
Image.changeset(image, %{})
|
Image.changeset(image, %{})
|
||||||
end
|
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
|
def user_name_reindex(old_name, new_name) do
|
||||||
data = ImageIndex.user_name_update_by_query(old_name, new_name)
|
data = ImageIndex.user_name_update_by_query(old_name, new_name)
|
||||||
|
|
||||||
Search.update_by_query(Image, data.query, data.set_replacements, data.replacements)
|
Search.update_by_query(Image, data.query, data.set_replacements, data.replacements)
|
||||||
end
|
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
|
def reindex_image(%Image{} = image) do
|
||||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Images", "id", [image.id]])
|
Exq.enqueue(Exq, "indexing", IndexWorker, ["Images", "id", [image.id]])
|
||||||
|
|
||||||
image
|
image
|
||||||
end
|
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
|
def reindex_images(image_ids) do
|
||||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Images", "id", image_ids])
|
Exq.enqueue(Exq, "indexing", IndexWorker, ["Images", "id", image_ids])
|
||||||
|
|
||||||
image_ids
|
image_ids
|
||||||
end
|
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
|
def indexing_preloads do
|
||||||
user_query = select(User, [u], map(u, [:id, :name]))
|
user_query = select(User, [u], map(u, [:id, :name]))
|
||||||
sources_query = select(Source, [s], map(s, [:image_id, :source]))
|
sources_query = select(Source, [s], map(s, [:image_id, :source]))
|
||||||
|
@ -869,6 +1247,19 @@ defmodule Philomena.Images do
|
||||||
]
|
]
|
||||||
end
|
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
|
def perform_reindex(column, condition) do
|
||||||
Image
|
Image
|
||||||
|> preload(^indexing_preloads())
|
|> preload(^indexing_preloads())
|
||||||
|
@ -876,6 +1267,17 @@ defmodule Philomena.Images do
|
||||||
|> Search.reindex(Image)
|
|> Search.reindex(Image)
|
||||||
end
|
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
|
def purge_files(image, hidden_key) do
|
||||||
files =
|
files =
|
||||||
if is_nil(hidden_key) do
|
if is_nil(hidden_key) do
|
||||||
|
@ -888,6 +1290,17 @@ defmodule Philomena.Images do
|
||||||
Exq.enqueue(Exq, "indexing", ImagePurgeWorker, [files])
|
Exq.enqueue(Exq, "indexing", ImagePurgeWorker, [files])
|
||||||
end
|
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
|
def perform_purge(files) do
|
||||||
{_out, 0} = System.cmd("purge-cache", [Jason.encode!(%{files: files})])
|
{_out, 0} = System.cmd("purge-cache", [Jason.encode!(%{files: files})])
|
||||||
|
|
||||||
|
@ -896,6 +1309,29 @@ defmodule Philomena.Images do
|
||||||
|
|
||||||
alias Philomena.Images.Subscription
|
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
|
def migrate_subscriptions(source, target) do
|
||||||
subscriptions =
|
subscriptions =
|
||||||
Subscription
|
Subscription
|
||||||
|
@ -941,6 +1377,29 @@ defmodule Philomena.Images do
|
||||||
{:ok, {comment_notification_count, merge_notification_count}}
|
{:ok, {comment_notification_count, merge_notification_count}}
|
||||||
end
|
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
|
def migrate_sources(source, target) do
|
||||||
sources =
|
sources =
|
||||||
(source.sources ++ target.sources)
|
(source.sources ++ target.sources)
|
||||||
|
|
|
@ -8,6 +8,19 @@ defmodule Philomena.Interactions do
|
||||||
alias Philomena.Repo
|
alias Philomena.Repo
|
||||||
alias Ecto.Multi
|
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),
|
def user_interactions(_images, nil),
|
||||||
do: []
|
do: []
|
||||||
|
|
||||||
|
@ -71,6 +84,18 @@ defmodule Philomena.Interactions do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
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
|
def migrate_interactions(source, target) do
|
||||||
now = DateTime.utc_now(:second)
|
now = DateTime.utc_now(:second)
|
||||||
source = Repo.preload(source, [:hiders, :favers, :upvoters, :downvoters])
|
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)
|
Notifications.create_forum_post_notification(post.user, topic, post)
|
||||||
end
|
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{approved: true}), do: false
|
||||||
|
|
||||||
def report_non_approved(post) do
|
def report_non_approved(post) do
|
||||||
|
@ -176,6 +193,20 @@ defmodule Philomena.Posts do
|
||||||
Repo.delete(post)
|
Repo.delete(post)
|
||||||
end
|
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
|
def hide_post(%Post{} = post, attrs, user) do
|
||||||
report_query = Reports.close_report_query({"Post", post.id}, user)
|
report_query = Reports.close_report_query({"Post", post.id}, user)
|
||||||
|
|
||||||
|
@ -209,6 +240,15 @@ defmodule Philomena.Posts do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Unhides a previously hidden post.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> unhide_post(post)
|
||||||
|
{:ok, %Post{}}
|
||||||
|
|
||||||
|
"""
|
||||||
def unhide_post(%Post{} = post) do
|
def unhide_post(%Post{} = post) do
|
||||||
post
|
post
|
||||||
|> Post.unhide_changeset()
|
|> Post.unhide_changeset()
|
||||||
|
@ -216,6 +256,15 @@ defmodule Philomena.Posts do
|
||||||
|> reindex_after_update()
|
|> reindex_after_update()
|
||||||
end
|
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
|
def destroy_post(%Post{} = post) do
|
||||||
post
|
post
|
||||||
|> Post.destroy_changeset()
|
|> Post.destroy_changeset()
|
||||||
|
@ -223,6 +272,20 @@ defmodule Philomena.Posts do
|
||||||
|> reindex_after_update()
|
|> reindex_after_update()
|
||||||
end
|
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
|
def approve_post(%Post{} = post, user) do
|
||||||
report_query = Reports.close_report_query({"Post", post.id}, user)
|
report_query = Reports.close_report_query({"Post", post.id}, user)
|
||||||
post = Post.approve_changeset(post)
|
post = Post.approve_changeset(post)
|
||||||
|
@ -257,6 +320,15 @@ defmodule Philomena.Posts do
|
||||||
Post.changeset(post, %{})
|
Post.changeset(post, %{})
|
||||||
end
|
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
|
def user_name_reindex(old_name, new_name) do
|
||||||
data = PostIndex.user_name_update_by_query(old_name, new_name)
|
data = PostIndex.user_name_update_by_query(old_name, new_name)
|
||||||
|
|
||||||
|
@ -273,12 +345,31 @@ defmodule Philomena.Posts do
|
||||||
result
|
result
|
||||||
end
|
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
|
def reindex_post(%Post{} = post) do
|
||||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Posts", "id", [post.id]])
|
Exq.enqueue(Exq, "indexing", IndexWorker, ["Posts", "id", [post.id]])
|
||||||
|
|
||||||
post
|
post
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Provides preload queries for post indexing operations.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> indexing_preloads()
|
||||||
|
[user: user_query, topic: topic_query]
|
||||||
|
|
||||||
|
"""
|
||||||
def indexing_preloads do
|
def indexing_preloads do
|
||||||
user_query = select(User, [u], map(u, [:id, :name]))
|
user_query = select(User, [u], map(u, [:id, :name]))
|
||||||
|
|
||||||
|
@ -293,6 +384,22 @@ defmodule Philomena.Posts do
|
||||||
]
|
]
|
||||||
end
|
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
|
def perform_reindex(column, condition) do
|
||||||
Post
|
Post
|
||||||
|> preload(^indexing_preloads())
|
|> preload(^indexing_preloads())
|
||||||
|
|
|
@ -1,29 +1,32 @@
|
||||||
defmodule Philomena.Slug do
|
defmodule Philomena.Slug do
|
||||||
# Generates a URL-safe slug from a string by removing nonessential
|
@moduledoc """
|
||||||
# information from it.
|
URL-safe string shortening.
|
||||||
#
|
"""
|
||||||
# The process for this is as follows:
|
|
||||||
#
|
@doc """
|
||||||
# 1. Remove non-ASCII or non-printable characters.
|
Generates a URL-safe slug from a string by removing nonessential
|
||||||
#
|
information from it.
|
||||||
# 2. Replace any runs of non-alphanumeric characters that were allowed
|
|
||||||
# through previously with hyphens.
|
The process for this is as follows:
|
||||||
#
|
|
||||||
# 3. Remove any starting or ending hyphens.
|
1. Remove non-ASCII or non-printable characters.
|
||||||
#
|
2. Replace any runs of non-alphanumeric characters that were allowed
|
||||||
# 4. Convert all characters to their lowercase equivalents.
|
through previously with hyphens.
|
||||||
#
|
3. Remove any starting or ending hyphens.
|
||||||
# This method makes no guarantee of creating unique slugs for unique inputs.
|
4. Convert all characters to their lowercase equivalents.
|
||||||
# In addition, for certain inputs, it will return empty strings.
|
|
||||||
#
|
This method makes no guarantee of creating unique slugs for unique inputs.
|
||||||
# Example
|
In addition, for certain inputs, it will return empty strings.
|
||||||
#
|
|
||||||
# destructive_slug("Time-Wasting Thread 3.0 (SFW - No Explicit/Grimdark)")
|
## Example
|
||||||
# #=> "time-wasting-thread-3-0-sfw-no-explicit-grimdark"
|
|
||||||
#
|
iex> destructive_slug("Time-Wasting Thread 3.0 (SFW - No Explicit/Grimdark)")
|
||||||
# destructive_slug("~`!@#$%^&*()-_=+[]{};:'\" <>,./?")
|
"time-wasting-thread-3-0-sfw-no-explicit-grimdark"
|
||||||
# #=> ""
|
|
||||||
#
|
iex> destructive_slug("~`!@#$%^&*()-_=+[]{};:'\" <>,./?")
|
||||||
|
""
|
||||||
|
|
||||||
|
"""
|
||||||
@spec destructive_slug(String.t()) :: String.t()
|
@spec destructive_slug(String.t()) :: String.t()
|
||||||
def destructive_slug(input) when is_binary(input) do
|
def destructive_slug(input) when is_binary(input) do
|
||||||
input
|
input
|
||||||
|
@ -39,6 +42,11 @@ defmodule Philomena.Slug do
|
||||||
|
|
||||||
def destructive_slug(_input), 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
|
def slug(string) when is_binary(string) do
|
||||||
string
|
string
|
||||||
|> String.replace("-", "-dash-")
|
|> String.replace("-", "-dash-")
|
||||||
|
|
|
@ -24,6 +24,19 @@ defmodule Philomena.Tags do
|
||||||
alias Philomena.DnpEntries.DnpEntry
|
alias Philomena.DnpEntries.DnpEntry
|
||||||
alias Philomena.Channels.Channel
|
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()
|
@spec get_or_create_tags(String.t()) :: list()
|
||||||
def get_or_create_tags(tag_list) do
|
def get_or_create_tags(tag_list) do
|
||||||
tag_names = Tag.parse_tag_list(tag_list)
|
tag_names = Tag.parse_tag_list(tag_list)
|
||||||
|
@ -174,6 +187,18 @@ defmodule Philomena.Tags do
|
||||||
end
|
end
|
||||||
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
|
def update_tag_image(%Tag{} = tag, attrs) do
|
||||||
tag
|
tag
|
||||||
|> Uploader.analyze_upload(attrs)
|
|> Uploader.analyze_upload(attrs)
|
||||||
|
@ -190,6 +215,17 @@ defmodule Philomena.Tags do
|
||||||
end
|
end
|
||||||
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
|
def remove_tag_image(%Tag{} = tag) do
|
||||||
tag
|
tag
|
||||||
|> Tag.remove_image_changeset()
|
|> Tag.remove_image_changeset()
|
||||||
|
@ -223,6 +259,18 @@ defmodule Philomena.Tags do
|
||||||
{:ok, tag}
|
{:ok, tag}
|
||||||
end
|
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
|
def perform_delete(tag_id) do
|
||||||
tag = get_tag!(tag_id)
|
tag = get_tag!(tag_id)
|
||||||
|
|
||||||
|
@ -243,6 +291,19 @@ defmodule Philomena.Tags do
|
||||||
|> Search.reindex(Image)
|
|> Search.reindex(Image)
|
||||||
end
|
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
|
def alias_tag(%Tag{} = tag, attrs) do
|
||||||
target_tag = Repo.get_by(Tag, name: String.downcase(attrs["target_tag"]))
|
target_tag = Repo.get_by(Tag, name: String.downcase(attrs["target_tag"]))
|
||||||
|
|
||||||
|
@ -261,6 +322,19 @@ defmodule Philomena.Tags do
|
||||||
end
|
end
|
||||||
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
|
def perform_alias(tag_id, target_tag_id) do
|
||||||
tag = get_tag!(tag_id)
|
tag = get_tag!(tag_id)
|
||||||
target_tag = get_tag!(target_tag_id)
|
target_tag = get_tag!(target_tag_id)
|
||||||
|
@ -315,14 +389,36 @@ defmodule Philomena.Tags do
|
||||||
# Finally, reindex
|
# Finally, reindex
|
||||||
reindex_tag_images(target_tag)
|
reindex_tag_images(target_tag)
|
||||||
reindex_tags([tag, target_tag])
|
reindex_tags([tag, target_tag])
|
||||||
|
|
||||||
|
:ok
|
||||||
end
|
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
|
def reindex_tag_images(%Tag{} = tag) do
|
||||||
Exq.enqueue(Exq, "indexing", TagReindexWorker, [tag.id])
|
Exq.enqueue(Exq, "indexing", TagReindexWorker, [tag.id])
|
||||||
|
|
||||||
{:ok, tag}
|
{:ok, tag}
|
||||||
end
|
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
|
def perform_reindex_images(tag_id) do
|
||||||
tag = get_tag!(tag_id)
|
tag = get_tag!(tag_id)
|
||||||
|
|
||||||
|
@ -351,12 +447,32 @@ defmodule Philomena.Tags do
|
||||||
|> Search.reindex(Filter)
|
|> Search.reindex(Filter)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Enqueues removal of a tag alias.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> unalias_tag(tag)
|
||||||
|
{:ok, %Tag{}}
|
||||||
|
|
||||||
|
"""
|
||||||
def unalias_tag(%Tag{} = tag) do
|
def unalias_tag(%Tag{} = tag) do
|
||||||
Exq.enqueue(Exq, "indexing", TagUnaliasWorker, [tag.id])
|
Exq.enqueue(Exq, "indexing", TagUnaliasWorker, [tag.id])
|
||||||
|
|
||||||
{:ok, tag}
|
{:ok, tag}
|
||||||
end
|
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
|
def perform_unalias(tag_id) do
|
||||||
tag = get_tag!(tag_id)
|
tag = get_tag!(tag_id)
|
||||||
former_alias = Repo.preload(tag, :aliased_tag).aliased_tag
|
former_alias = Repo.preload(tag, :aliased_tag).aliased_tag
|
||||||
|
@ -389,6 +505,18 @@ defmodule Philomena.Tags do
|
||||||
|> Repo.update_all([])
|
|> Repo.update_all([])
|
||||||
end
|
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
|
def copy_tags(source, target) do
|
||||||
# Ecto bug:
|
# Ecto bug:
|
||||||
# ** (DBConnection.EncodeError) Postgrex expected a binary, got 5.
|
# ** (DBConnection.EncodeError) Postgrex expected a binary, got 5.
|
||||||
|
@ -437,22 +565,66 @@ defmodule Philomena.Tags do
|
||||||
Tag.changeset(tag, %{})
|
Tag.changeset(tag, %{})
|
||||||
end
|
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
|
def reindex_tag(%Tag{} = tag) do
|
||||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Tags", "id", [tag.id]])
|
Exq.enqueue(Exq, "indexing", IndexWorker, ["Tags", "id", [tag.id]])
|
||||||
|
|
||||||
tag
|
tag
|
||||||
end
|
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
|
def reindex_tags(tags) do
|
||||||
Exq.enqueue(Exq, "indexing", IndexWorker, ["Tags", "id", Enum.map(tags, & &1.id)])
|
Exq.enqueue(Exq, "indexing", IndexWorker, ["Tags", "id", Enum.map(tags, & &1.id)])
|
||||||
|
|
||||||
tags
|
tags
|
||||||
end
|
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
|
def indexing_preloads do
|
||||||
[:aliased_tag, :aliases, :implied_tags, :implied_by_tags]
|
[:aliased_tag, :aliases, :implied_tags, :implied_by_tags]
|
||||||
end
|
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
|
def perform_reindex(column, condition) do
|
||||||
Tag
|
Tag
|
||||||
|> preload(^indexing_preloads())
|
|> preload(^indexing_preloads())
|
||||||
|
|
|
@ -138,26 +138,71 @@ defmodule Philomena.Topics do
|
||||||
Topic.changeset(topic, %{})
|
Topic.changeset(topic, %{})
|
||||||
end
|
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
|
def stick_topic(topic) do
|
||||||
Topic.stick_changeset(topic)
|
Topic.stick_changeset(topic)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Removes sticky status from a topic.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> unstick_topic(topic)
|
||||||
|
{:ok, %Topic{}}
|
||||||
|
|
||||||
|
"""
|
||||||
def unstick_topic(topic) do
|
def unstick_topic(topic) do
|
||||||
Topic.unstick_changeset(topic)
|
Topic.unstick_changeset(topic)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def lock_topic(%Topic{} = topic, attrs, user) do
|
||||||
Topic.lock_changeset(topic, attrs, user)
|
Topic.lock_changeset(topic, attrs, user)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Unlocks a topic to allow posting again.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> unlock_topic(topic)
|
||||||
|
{:ok, %Topic{}}
|
||||||
|
|
||||||
|
"""
|
||||||
def unlock_topic(%Topic{} = topic) do
|
def unlock_topic(%Topic{} = topic) do
|
||||||
Topic.unlock_changeset(topic)
|
Topic.unlock_changeset(topic)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def move_topic(topic, new_forum_id) do
|
||||||
old_forum_id = topic.forum_id
|
old_forum_id = topic.forum_id
|
||||||
topic_changes = Topic.move_changeset(topic, new_forum_id)
|
topic_changes = Topic.move_changeset(topic, new_forum_id)
|
||||||
|
@ -183,6 +228,15 @@ defmodule Philomena.Topics do
|
||||||
|> Repo.transaction()
|
|> Repo.transaction()
|
||||||
end
|
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
|
def hide_topic(topic, deletion_reason, user) do
|
||||||
topic_changes = Topic.hide_changeset(topic, deletion_reason, user)
|
topic_changes = Topic.hide_changeset(topic, deletion_reason, user)
|
||||||
|
|
||||||
|
@ -205,11 +259,29 @@ defmodule Philomena.Topics do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Unhides a previously hidden topic.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> unhide_topic(topic)
|
||||||
|
{:ok, %Topic{}}
|
||||||
|
|
||||||
|
"""
|
||||||
def unhide_topic(topic) do
|
def unhide_topic(topic) do
|
||||||
Topic.unhide_changeset(topic)
|
Topic.unhide_changeset(topic)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def update_topic_title(topic, attrs) do
|
||||||
topic
|
topic
|
||||||
|> Topic.title_changeset(attrs)
|
|> Topic.title_changeset(attrs)
|
||||||
|
|
|
@ -273,6 +273,12 @@ defmodule Philomena.Users do
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Unconditionally unlocks the given user.
|
Unconditionally unlocks the given user.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> unlock_user(user)
|
||||||
|
{:ok, %User{}}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
def unlock_user(user) do
|
def unlock_user(user) do
|
||||||
user
|
user
|
||||||
|
@ -369,6 +375,20 @@ defmodule Philomena.Users do
|
||||||
load_with_roles(query)
|
load_with_roles(query)
|
||||||
end
|
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
|
def user_totp_token_valid?(nil, _token) do
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
@ -503,6 +523,15 @@ defmodule Philomena.Users do
|
||||||
end
|
end
|
||||||
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
|
def change_user(%User{} = user) do
|
||||||
User.changeset(user, %{})
|
User.changeset(user, %{})
|
||||||
end
|
end
|
||||||
|
@ -544,30 +573,84 @@ defmodule Philomena.Users do
|
||||||
defp clean_roles(nil), do: []
|
defp clean_roles(nil), do: []
|
||||||
defp clean_roles(roles), do: Enum.filter(roles, &("" != &1))
|
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
|
def update_spoiler_type(%User{} = user, attrs) do
|
||||||
user
|
user
|
||||||
|> User.spoiler_type_changeset(attrs)
|
|> User.spoiler_type_changeset(attrs)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def update_settings(%User{} = user, attrs) do
|
||||||
user
|
user
|
||||||
|> User.settings_changeset(attrs)
|
|> User.settings_changeset(attrs)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def update_description(%User{} = user, attrs) do
|
||||||
user
|
user
|
||||||
|> User.description_changeset(attrs)
|
|> User.description_changeset(attrs)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def update_scratchpad(%User{} = user, attrs) do
|
||||||
user
|
user
|
||||||
|> User.scratchpad_changeset(attrs)
|
|> User.scratchpad_changeset(attrs)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def watch_tag(%User{} = user, tag) do
|
||||||
watched_tag_ids = Enum.uniq([tag.id | user.watched_tag_ids])
|
watched_tag_ids = Enum.uniq([tag.id | user.watched_tag_ids])
|
||||||
|
|
||||||
|
@ -576,6 +659,15 @@ defmodule Philomena.Users do
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def unwatch_tag(%User{} = user, tag) do
|
||||||
watched_tag_ids = user.watched_tag_ids -- [tag.id]
|
watched_tag_ids = user.watched_tag_ids -- [tag.id]
|
||||||
|
|
||||||
|
@ -584,6 +676,17 @@ defmodule Philomena.Users do
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def update_avatar(%User{} = user, attrs) do
|
||||||
user
|
user
|
||||||
|> Uploader.analyze_upload(attrs)
|
|> Uploader.analyze_upload(attrs)
|
||||||
|
@ -600,6 +703,15 @@ defmodule Philomena.Users do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Removes a user's avatar.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> remove_avatar(user)
|
||||||
|
{:ok, %User{}}
|
||||||
|
|
||||||
|
"""
|
||||||
def remove_avatar(%User{} = user) do
|
def remove_avatar(%User{} = user) do
|
||||||
user
|
user
|
||||||
|> User.remove_avatar_changeset()
|
|> User.remove_avatar_changeset()
|
||||||
|
@ -615,6 +727,17 @@ defmodule Philomena.Users do
|
||||||
end
|
end
|
||||||
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
|
def update_name(user, user_params) do
|
||||||
old_name = user.name
|
old_name = user.name
|
||||||
|
|
||||||
|
@ -636,6 +759,17 @@ defmodule Philomena.Users do
|
||||||
end
|
end
|
||||||
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
|
def perform_rename(old_name, new_name) do
|
||||||
Images.user_name_reindex(old_name, new_name)
|
Images.user_name_reindex(old_name, new_name)
|
||||||
Comments.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)
|
Filters.user_name_reindex(old_name, new_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Reactivates a previously deactivated user account.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> reactivate_user(user)
|
||||||
|
{:ok, %User{}}
|
||||||
|
|
||||||
|
"""
|
||||||
def reactivate_user(%User{} = user) do
|
def reactivate_user(%User{} = user) do
|
||||||
user
|
user
|
||||||
|> User.reactivate_changeset()
|
|> User.reactivate_changeset()
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def deactivate_user(moderator, %User{} = user) do
|
||||||
user
|
user
|
||||||
|> User.deactivate_changeset(moderator)
|
|> User.deactivate_changeset(moderator)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def reset_api_key(%User{} = user) do
|
||||||
user
|
user
|
||||||
|> User.api_key_changeset()
|
|> User.api_key_changeset()
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def force_filter(%User{} = user, user_params) do
|
||||||
user
|
user
|
||||||
|> User.force_filter_changeset(user_params)
|
|> User.force_filter_changeset(user_params)
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Removes a forced filter from a user's account.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> unforce_filter(user)
|
||||||
|
{:ok, %User{}}
|
||||||
|
|
||||||
|
"""
|
||||||
def unforce_filter(%User{} = user) do
|
def unforce_filter(%User{} = user) do
|
||||||
user
|
user
|
||||||
|> User.unforce_filter_changeset()
|
|> User.unforce_filter_changeset()
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Clears a user's recent filter history.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> clear_recent_filters(user)
|
||||||
|
{:ok, %User{}}
|
||||||
|
|
||||||
|
"""
|
||||||
def clear_recent_filters(%User{} = user) do
|
def clear_recent_filters(%User{} = user) do
|
||||||
user
|
user
|
||||||
|> User.clear_recent_filters_changeset()
|
|> User.clear_recent_filters_changeset()
|
||||||
|
@ -688,18 +882,50 @@ defmodule Philomena.Users do
|
||||||
|> setup_roles()
|
|> setup_roles()
|
||||||
end
|
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
|
def verify_user(%User{} = user) do
|
||||||
user
|
user
|
||||||
|> User.verify_changeset()
|
|> User.verify_changeset()
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Unverifies a user, removing the automatic approval status.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> unverify_user(user)
|
||||||
|
{:ok, %User{}}
|
||||||
|
|
||||||
|
"""
|
||||||
def unverify_user(%User{} = user) do
|
def unverify_user(%User{} = user) do
|
||||||
user
|
user
|
||||||
|> User.unverify_changeset()
|
|> User.unverify_changeset()
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end
|
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
|
def erase_user(%User{} = user, %User{} = moderator) do
|
||||||
# Deactivate to prevent the user from racing these changes
|
# Deactivate to prevent the user from racing these changes
|
||||||
{:ok, user} = deactivate_user(moderator, user)
|
{:ok, user} = deactivate_user(moderator, user)
|
||||||
|
|
Loading…
Reference in a new issue