Add ability to follow hashtags (#336)
[akkoma] / lib / pleroma / hashtag.ex
index 0d6a4d09e8caa5f8bd16cd5116ce7bd165b1854e..9030ee4e92c0f1537476459f0eda21eb2b49c756 100644 (file)
@@ -10,6 +10,7 @@ defmodule Pleroma.Hashtag do
 
   alias Ecto.Multi
   alias Pleroma.Hashtag
+  alias Pleroma.User.HashtagFollow
   alias Pleroma.Object
   alias Pleroma.Repo
 
@@ -21,22 +22,33 @@ defmodule Pleroma.Hashtag do
     timestamps()
   end
 
+  def normalize_name(name) do
+    name
+    |> String.downcase()
+    |> String.trim()
+  end
+
+  def get_by_id(id) do
+    Repo.get(Hashtag, id)
+  end
+
   def get_by_name(name) do
-    Repo.get_by(Hashtag, name: name)
+    Repo.get_by(Hashtag, name: normalize_name(name))
   end
 
-  def get_or_create_by_name(name) when is_bitstring(name) do
-    with %Hashtag{} = hashtag <- get_by_name(name) do
-      {:ok, hashtag}
-    else
-      _ ->
-        %Hashtag{}
-        |> changeset(%{name: name})
-        |> Repo.insert()
-    end
+  def get_or_create_by_name(name) do
+    changeset = changeset(%Hashtag{}, %{name: name})
+
+    Repo.insert(
+      changeset,
+      on_conflict: [set: [name: get_field(changeset, :name)]],
+      conflict_target: :name,
+      returning: true
+    )
   end
 
   def get_or_create_by_names(names) when is_list(names) do
+    names = Enum.map(names, &normalize_name/1)
     timestamp = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
 
     structs =
@@ -50,7 +62,10 @@ defmodule Pleroma.Hashtag do
     try do
       with {:ok, %{query_op: hashtags}} <-
              Multi.new()
-             |> Multi.insert_all(:insert_all_op, Hashtag, structs, on_conflict: :nothing)
+             |> Multi.insert_all(:insert_all_op, Hashtag, structs,
+               on_conflict: :nothing,
+               conflict_target: :name
+             )
              |> Multi.run(:query_op, fn _repo, _changes ->
                {:ok, Repo.all(from(ht in Hashtag, where: ht.name in ^names))}
              end)
@@ -67,7 +82,7 @@ defmodule Pleroma.Hashtag do
   def changeset(%Hashtag{} = struct, params) do
     struct
     |> cast(params, [:name])
-    |> update_change(:name, &String.downcase/1)
+    |> update_change(:name, &normalize_name/1)
     |> validate_required([:name])
     |> unique_constraint(:name)
   end
@@ -97,4 +112,22 @@ defmodule Pleroma.Hashtag do
       {:ok, deleted_count}
     end
   end
+
+  def get_followers(%Hashtag{id: hashtag_id}) do
+    from(hf in HashtagFollow)
+    |> where([hf], hf.hashtag_id == ^hashtag_id)
+    |> join(:inner, [hf], u in assoc(hf, :user))
+    |> select([hf, u], u.id)
+    |> Repo.all()
+  end
+
+  def get_recipients_for_activity(%Pleroma.Activity{object: %{hashtags: tags}})
+      when is_list(tags) do
+    tags
+    |> Enum.map(&get_followers/1)
+    |> List.flatten()
+    |> Enum.uniq()
+  end
+
+  def get_recipients_for_activity(_activity), do: []
 end