2020-12-22 11:04:33 -08:00
|
|
|
# Pleroma: A lightweight social networking server
|
|
|
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
|
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
|
|
|
|
defmodule Pleroma.Hashtag do
|
|
|
|
use Ecto.Schema
|
|
|
|
|
|
|
|
import Ecto.Changeset
|
2021-02-11 08:30:21 -08:00
|
|
|
import Ecto.Query
|
2020-12-22 11:04:33 -08:00
|
|
|
|
2021-02-11 08:30:21 -08:00
|
|
|
alias Ecto.Multi
|
2020-12-22 11:04:33 -08:00
|
|
|
alias Pleroma.Hashtag
|
2021-02-11 08:30:21 -08:00
|
|
|
alias Pleroma.Object
|
2020-12-22 11:04:33 -08:00
|
|
|
alias Pleroma.Repo
|
|
|
|
|
|
|
|
schema "hashtags" do
|
|
|
|
field(:name, :string)
|
|
|
|
|
2021-02-11 08:30:21 -08:00
|
|
|
many_to_many(:objects, Object, join_through: "hashtags_objects", on_replace: :delete)
|
2020-12-22 11:04:33 -08:00
|
|
|
|
|
|
|
timestamps()
|
|
|
|
end
|
|
|
|
|
2021-02-22 12:26:07 -08:00
|
|
|
def normalize_name(name) do
|
|
|
|
name
|
|
|
|
|> String.downcase()
|
|
|
|
|> String.trim()
|
|
|
|
end
|
|
|
|
|
2021-02-23 02:52:28 -08:00
|
|
|
def get_or_create_by_name(name) do
|
|
|
|
changeset = changeset(%Hashtag{}, %{name: name})
|
2020-12-22 11:04:33 -08:00
|
|
|
|
2021-02-23 02:52:28 -08:00
|
|
|
Repo.insert(
|
|
|
|
changeset,
|
|
|
|
on_conflict: [set: [name: get_field(changeset, :name)]],
|
|
|
|
conflict_target: :name,
|
|
|
|
returning: true
|
|
|
|
)
|
2020-12-22 11:04:33 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def get_or_create_by_names(names) when is_list(names) do
|
2021-02-22 12:26:07 -08:00
|
|
|
names = Enum.map(names, &normalize_name/1)
|
2021-02-11 08:30:21 -08:00
|
|
|
timestamp = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
|
|
|
|
|
|
|
|
structs =
|
|
|
|
Enum.map(names, fn name ->
|
|
|
|
%Hashtag{}
|
|
|
|
|> changeset(%{name: name})
|
|
|
|
|> Map.get(:changes)
|
|
|
|
|> Map.merge(%{inserted_at: timestamp, updated_at: timestamp})
|
|
|
|
end)
|
|
|
|
|
2021-02-13 11:01:11 -08:00
|
|
|
try do
|
|
|
|
with {:ok, %{query_op: hashtags}} <-
|
|
|
|
Multi.new()
|
2021-02-22 12:26:07 -08:00
|
|
|
|> Multi.insert_all(:insert_all_op, Hashtag, structs,
|
|
|
|
on_conflict: :nothing,
|
|
|
|
conflict_target: :name
|
|
|
|
)
|
2021-02-13 11:01:11 -08:00
|
|
|
|> Multi.run(:query_op, fn _repo, _changes ->
|
2021-02-22 12:26:07 -08:00
|
|
|
{:ok, Repo.all(from(ht in Hashtag, where: ht.name in ^names))}
|
2021-02-13 11:01:11 -08:00
|
|
|
end)
|
|
|
|
|> Repo.transaction() do
|
|
|
|
{:ok, hashtags}
|
|
|
|
else
|
|
|
|
{:error, _name, value, _changes_so_far} -> {:error, value}
|
|
|
|
end
|
|
|
|
rescue
|
|
|
|
e -> {:error, e}
|
2021-02-11 08:30:21 -08:00
|
|
|
end
|
2020-12-22 11:04:33 -08:00
|
|
|
end
|
|
|
|
|
|
|
|
def changeset(%Hashtag{} = struct, params) do
|
|
|
|
struct
|
2021-02-07 11:24:12 -08:00
|
|
|
|> cast(params, [:name])
|
2021-02-22 12:26:07 -08:00
|
|
|
|> update_change(:name, &normalize_name/1)
|
2020-12-22 11:04:33 -08:00
|
|
|
|> validate_required([:name])
|
|
|
|
|> unique_constraint(:name)
|
|
|
|
end
|
2021-02-11 08:30:21 -08:00
|
|
|
|
|
|
|
def unlink(%Object{id: object_id}) do
|
|
|
|
with {_, hashtag_ids} <-
|
|
|
|
from(hto in "hashtags_objects",
|
|
|
|
where: hto.object_id == ^object_id,
|
|
|
|
select: hto.hashtag_id
|
|
|
|
)
|
2021-02-13 11:01:11 -08:00
|
|
|
|> Repo.delete_all(),
|
|
|
|
{:ok, unreferenced_count} <- delete_unreferenced(hashtag_ids) do
|
|
|
|
{:ok, length(hashtag_ids), unreferenced_count}
|
2021-02-11 08:30:21 -08:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
@delete_unreferenced_query """
|
|
|
|
DELETE FROM hashtags WHERE id IN
|
|
|
|
(SELECT hashtags.id FROM hashtags
|
|
|
|
LEFT OUTER JOIN hashtags_objects
|
|
|
|
ON hashtags_objects.hashtag_id = hashtags.id
|
|
|
|
WHERE hashtags_objects.hashtag_id IS NULL AND hashtags.id = ANY($1));
|
|
|
|
"""
|
|
|
|
|
|
|
|
def delete_unreferenced(ids) do
|
|
|
|
with {:ok, %{num_rows: deleted_count}} <- Repo.query(@delete_unreferenced_query, [ids]) do
|
|
|
|
{:ok, deleted_count}
|
|
|
|
end
|
|
|
|
end
|
2020-12-22 11:04:33 -08:00
|
|
|
end
|