Merge remote-tracking branch 'origin/patch/readd-mastofe' into develop
[akkoma] / lib / pleroma / hashtag.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Hashtag do
6 use Ecto.Schema
7
8 import Ecto.Changeset
9 import Ecto.Query
10
11 alias Ecto.Multi
12 alias Pleroma.Hashtag
13 alias Pleroma.Object
14 alias Pleroma.Repo
15
16 schema "hashtags" do
17 field(:name, :string)
18
19 many_to_many(:objects, Object, join_through: "hashtags_objects", on_replace: :delete)
20
21 timestamps()
22 end
23
24 def normalize_name(name) do
25 name
26 |> String.downcase()
27 |> String.trim()
28 end
29
30 def get_or_create_by_name(name) do
31 changeset = changeset(%Hashtag{}, %{name: name})
32
33 Repo.insert(
34 changeset,
35 on_conflict: [set: [name: get_field(changeset, :name)]],
36 conflict_target: :name,
37 returning: true
38 )
39 end
40
41 def get_or_create_by_names(names) when is_list(names) do
42 names = Enum.map(names, &normalize_name/1)
43 timestamp = NaiveDateTime.truncate(NaiveDateTime.utc_now(), :second)
44
45 structs =
46 Enum.map(names, fn name ->
47 %Hashtag{}
48 |> changeset(%{name: name})
49 |> Map.get(:changes)
50 |> Map.merge(%{inserted_at: timestamp, updated_at: timestamp})
51 end)
52
53 try do
54 with {:ok, %{query_op: hashtags}} <-
55 Multi.new()
56 |> Multi.insert_all(:insert_all_op, Hashtag, structs,
57 on_conflict: :nothing,
58 conflict_target: :name
59 )
60 |> Multi.run(:query_op, fn _repo, _changes ->
61 {:ok, Repo.all(from(ht in Hashtag, where: ht.name in ^names))}
62 end)
63 |> Repo.transaction() do
64 Pleroma.Elasticsearch.maybe_bulk_post(hashtags, :hashtags)
65 {:ok, hashtags}
66 else
67 {:error, _name, value, _changes_so_far} -> {:error, value}
68 end
69 rescue
70 e -> {:error, e}
71 end
72 end
73
74 def changeset(%Hashtag{} = struct, params) do
75 struct
76 |> cast(params, [:name])
77 |> update_change(:name, &normalize_name/1)
78 |> validate_required([:name])
79 |> unique_constraint(:name)
80 end
81
82 def unlink(%Object{id: object_id}) do
83 with {_, hashtag_ids} <-
84 from(hto in "hashtags_objects",
85 where: hto.object_id == ^object_id,
86 select: hto.hashtag_id
87 )
88 |> Repo.delete_all(),
89 {:ok, unreferenced_count} <- delete_unreferenced(hashtag_ids) do
90 {:ok, length(hashtag_ids), unreferenced_count}
91 end
92 end
93
94 @delete_unreferenced_query """
95 DELETE FROM hashtags WHERE id IN
96 (SELECT hashtags.id FROM hashtags
97 LEFT OUTER JOIN hashtags_objects
98 ON hashtags_objects.hashtag_id = hashtags.id
99 WHERE hashtags_objects.hashtag_id IS NULL AND hashtags.id = ANY($1));
100 """
101
102 def delete_unreferenced(ids) do
103 with {:ok, %{num_rows: deleted_count}} <- Repo.query(@delete_unreferenced_query, [ids]) do
104 {:ok, deleted_count}
105 end
106 end
107 end