cc381af53f1e47986d70bdfbe0b272044829edb4
[akkoma] / lib / pleroma / following_relationship.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.FollowingRelationship do
6 use Ecto.Schema
7
8 import Ecto.Changeset
9 import Ecto.Query
10
11 alias FlakeId.Ecto.CompatType
12 alias Pleroma.Repo
13 alias Pleroma.User
14
15 schema "following_relationships" do
16 field(:state, :string, default: "accept")
17
18 belongs_to(:follower, User, type: CompatType)
19 belongs_to(:following, User, type: CompatType)
20
21 timestamps()
22 end
23
24 def changeset(%__MODULE__{} = following_relationship, attrs) do
25 following_relationship
26 |> cast(attrs, [:state])
27 |> put_assoc(:follower, attrs.follower)
28 |> put_assoc(:following, attrs.following)
29 |> validate_required([:state, :follower, :following])
30 end
31
32 def get(%User{} = follower, %User{} = following) do
33 following_relationship =
34 __MODULE__
35 |> where(follower_id: ^follower.id, following_id: ^following.id)
36 |> Repo.one()
37
38 case {following_relationship, following.local} do
39 {nil, false} ->
40 case Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, following) do
41 %{data: %{"state" => state}} when state in ["pending", "accept"] ->
42 %{state: state}
43
44 _ ->
45 nil
46 end
47
48 {following_relationship, _} ->
49 following_relationship
50 end
51 end
52
53 def update(follower, following, "reject"), do: unfollow(follower, following)
54
55 def update(%User{} = follower, %User{} = following, state) do
56 case get(follower, following) do
57 nil ->
58 follow(follower, following, state)
59
60 following_relationship ->
61 following_relationship
62 |> cast(%{state: state}, [:state])
63 |> validate_required([:state])
64 |> Repo.update()
65 end
66 end
67
68 def follow(%User{} = follower, %User{} = following, state \\ "accept") do
69 %__MODULE__{}
70 |> changeset(%{follower: follower, following: following, state: state})
71 |> Repo.insert(on_conflict: :nothing)
72 end
73
74 def unfollow(%User{} = follower, %User{} = following) do
75 case get(follower, following) do
76 %__MODULE__{} = following_relationship -> Repo.delete(following_relationship)
77 _ -> {:ok, nil}
78 end
79 end
80
81 def follower_count(%User{} = user) do
82 %{followers: user, deactivated: false}
83 |> User.Query.build()
84 |> Repo.aggregate(:count, :id)
85 end
86
87 def following_count(%User{id: nil}), do: 0
88
89 def following_count(%User{} = user) do
90 %{friends: user, deactivated: false}
91 |> User.Query.build()
92 |> Repo.aggregate(:count, :id)
93 end
94
95 def get_follow_requests(%User{id: id}) do
96 __MODULE__
97 |> join(:inner, [r], f in assoc(r, :follower))
98 |> where([r], r.state == "pending")
99 |> where([r], r.following_id == ^id)
100 |> select([r, f], f)
101 |> Repo.all()
102 end
103
104 def following?(%User{id: follower_id}, %User{id: followed_id}) do
105 __MODULE__
106 |> where(follower_id: ^follower_id, following_id: ^followed_id, state: "accept")
107 |> Repo.exists?()
108 end
109
110 def following(%User{} = user) do
111 following =
112 __MODULE__
113 |> join(:inner, [r], u in User, on: r.following_id == u.id)
114 |> where([r], r.follower_id == ^user.id)
115 |> where([r], r.state == "accept")
116 |> select([r, u], u.follower_address)
117 |> Repo.all()
118
119 if not user.local or user.invisible do
120 following
121 else
122 [user.follower_address | following]
123 end
124 end
125
126 def move_following(origin, target) do
127 __MODULE__
128 |> join(:inner, [r], f in assoc(r, :follower))
129 |> where(following_id: ^origin.id)
130 |> where([r, f], f.allow_following_move == true)
131 |> limit(50)
132 |> preload([:follower])
133 |> Repo.all()
134 |> Enum.map(fn following_relationship ->
135 Repo.delete(following_relationship)
136 Pleroma.Web.CommonAPI.follow(following_relationship.follower, target)
137 end)
138 |> case do
139 [] ->
140 User.update_follower_count(origin)
141 :ok
142
143 _ ->
144 move_following(origin, target)
145 end
146 end
147 end