Skip to content
This repository was archived by the owner on Nov 8, 2022. It is now read-only.

Commit 51ebd24

Browse files
authored
refactor(user): move users query to own fields (#371)
* refactor(user): contribute field * refactor(user): default contributes * refactor(user): default contributes * fix(user): contrutess model setting * refactor(user): wip * refactor(user): basic follower status * refactor(user): wip * refactor(user): follow states for paged api * refactor(user): follow count move to table * refactor(user): follow queries use login * fix(user): login args in test * fix(user): login args in test * fix(user): login args in test * fix(user): login args in test
1 parent 7de9e82 commit 51ebd24

35 files changed

+633
-291
lines changed

cover/excoveralls.json

+1-1
Large diffs are not rendered by default.

lib/groupher_server/accounts/accounts.ex

+9-2
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ defmodule GroupherServer.Accounts do
1515
}
1616

1717
# profile
18+
defdelegate read_user(user), to: Profile
19+
defdelegate read_user(login, user), to: Profile
20+
defdelegate paged_users(filter), to: Profile
21+
defdelegate paged_users(filter, user), to: Profile
22+
1823
defdelegate update_profile(user, attrs), to: Profile
1924
defdelegate update_geo(user, remote_ip), to: Profile
2025
defdelegate github_signin(github_user), to: Profile
@@ -46,8 +51,10 @@ defmodule GroupherServer.Accounts do
4651
# fans
4752
defdelegate follow(user, follower), to: Fans
4853
defdelegate undo_follow(user, follower), to: Fans
49-
defdelegate fetch_followers(user, filter), to: Fans
50-
defdelegate fetch_followings(user, filter), to: Fans
54+
defdelegate paged_followers(user, filter), to: Fans
55+
defdelegate paged_followers(user, filter, cur_user), to: Fans
56+
defdelegate paged_followings(user, filter), to: Fans
57+
defdelegate paged_followings(user, filter, cur_user), to: Fans
5158

5259
# upvoted articles
5360
defdelegate paged_upvoted_articles(user_id, filter), to: UpvotedArticles

lib/groupher_server/accounts/delegates/fans.ex

+136-59
Original file line numberDiff line numberDiff line change
@@ -3,118 +3,127 @@ defmodule GroupherServer.Accounts.Delegate.Fans do
33
user followers / following related
44
"""
55
import Ecto.Query, warn: false
6-
import Helper.Utils, only: [done: 1]
6+
import Helper.Utils, only: [done: 1, ensure: 2]
77
import Helper.ErrorCode
88
import ShortMaps
99

1010
alias Helper.{ORM, QueryBuilder, SpecType}
1111
alias GroupherServer.{Accounts, Repo}
1212

13-
alias GroupherServer.Accounts.{User, UserFollower, UserFollowing}
13+
alias GroupherServer.Accounts.{User, Embeds, UserFollower, UserFollowing}
1414

1515
alias Ecto.Multi
1616

17+
@default_user_meta Embeds.UserMeta.default_meta()
18+
1719
@doc """
1820
follow a user
1921
"""
2022
@spec follow(User.t(), User.t()) :: {:ok, User.t()} | SpecType.gq_error()
2123
def follow(%User{id: user_id}, %User{id: follower_id}) do
2224
with true <- to_string(user_id) !== to_string(follower_id),
23-
{:ok, _follow_user} <- ORM.find(User, follower_id) do
25+
{:ok, target_user} <- ORM.find(User, follower_id) do
2426
Multi.new()
2527
|> Multi.insert(
2628
:create_follower,
27-
# UserFollower.changeset(%UserFollower{}, ~m(user_id follower_id)a)
28-
UserFollower.changeset(%UserFollower{}, %{user_id: follower_id, follower_id: user_id})
29+
UserFollower.changeset(%UserFollower{}, %{user_id: target_user.id, follower_id: user_id})
2930
)
3031
|> Multi.insert(
3132
:create_following,
32-
UserFollowing.changeset(%UserFollowing{}, %{user_id: user_id, following_id: follower_id})
33+
UserFollowing.changeset(%UserFollowing{}, %{
34+
user_id: user_id,
35+
following_id: target_user.id
36+
})
3337
)
38+
|> Multi.run(:update_user_follow_info, fn _, _ ->
39+
update_user_follow_info(target_user, user_id, :add)
40+
end)
3441
|> Multi.run(:add_achievement, fn _, _ ->
35-
Accounts.achieve(%User{id: follower_id}, :inc, :follow)
42+
Accounts.achieve(%User{id: target_user.id}, :inc, :follow)
3643
end)
3744
|> Repo.transaction()
38-
|> follow_result()
45+
|> result()
3946
else
40-
false ->
41-
{:error, [message: "can't follow yourself", code: ecode(:self_conflict)]}
42-
43-
{:error, reason} ->
44-
{:error, [message: reason, code: ecode(:not_exsit)]}
47+
false -> {:error, [message: "can't follow yourself", code: ecode(:self_conflict)]}
48+
{:error, reason} -> {:error, [message: reason, code: ecode(:not_exsit)]}
4549
end
4650
end
4751

48-
@spec follow_result({:ok, map()}) :: SpecType.done()
49-
defp follow_result({:ok, %{create_follower: user_follower}}) do
50-
User |> ORM.find(user_follower.user_id)
51-
end
52-
53-
defp follow_result({:error, :create_follower, %Ecto.Changeset{}, _steps}) do
54-
{:error, [message: "already followed", code: ecode(:already_did)]}
55-
end
56-
57-
defp follow_result({:error, :create_follower, _result, _steps}) do
58-
{:error, [message: "already followed", code: ecode(:already_did)]}
59-
end
60-
61-
defp follow_result({:error, :create_following, _result, _steps}) do
62-
{:error, [message: "follow fails", code: ecode(:react_fails)]}
63-
end
64-
65-
defp follow_result({:error, :add_achievement, _result, _steps}) do
66-
{:error, [message: "follow acieve fails", code: ecode(:react_fails)]}
67-
end
68-
6952
@doc """
7053
undo a follow action to a user
7154
"""
7255
@spec undo_follow(User.t(), User.t()) :: {:ok, User.t()} | SpecType.gq_error()
7356
def undo_follow(%User{id: user_id}, %User{id: follower_id}) do
7457
with true <- to_string(user_id) !== to_string(follower_id),
75-
{:ok, _follow_user} <- ORM.find(User, follower_id) do
58+
{:ok, target_user} <- ORM.find(User, follower_id) do
7659
Multi.new()
7760
|> Multi.run(:delete_follower, fn _, _ ->
78-
ORM.findby_delete!(UserFollower, %{user_id: follower_id, follower_id: user_id})
61+
ORM.findby_delete!(UserFollower, %{user_id: target_user.id, follower_id: user_id})
7962
end)
8063
|> Multi.run(:delete_following, fn _, _ ->
81-
ORM.findby_delete!(UserFollowing, %{user_id: user_id, following_id: follower_id})
64+
ORM.findby_delete!(UserFollowing, %{user_id: user_id, following_id: target_user.id})
65+
end)
66+
|> Multi.run(:update_user_follow_info, fn _, _ ->
67+
update_user_follow_info(target_user, user_id, :remove)
8268
end)
8369
|> Multi.run(:minus_achievement, fn _, _ ->
84-
Accounts.achieve(%User{id: follower_id}, :dec, :follow)
70+
Accounts.achieve(%User{id: target_user.id}, :dec, :follow)
8571
end)
8672
|> Repo.transaction()
87-
|> undo_follow_result()
73+
|> result()
8874
else
89-
false ->
90-
{:error, [message: "can't undo follow yourself", code: ecode(:self_conflict)]}
91-
92-
{:error, reason} ->
93-
{:error, [message: reason, code: ecode(:not_exsit)]}
75+
false -> {:error, [message: "can't undo follow yourself", code: ecode(:self_conflict)]}
76+
{:error, reason} -> {:error, [message: reason, code: ecode(:not_exsit)]}
9477
end
9578
end
9679

97-
defp undo_follow_result({:ok, %{delete_follower: user_follower}}) do
98-
User |> ORM.find(user_follower.user_id)
99-
end
80+
# update follow in user meta
81+
defp update_user_follow_info(%User{} = target_user, user_id, opt) do
82+
with {:ok, user} <- ORM.find(User, user_id) do
83+
target_user_meta = ensure(target_user.meta, @default_user_meta)
84+
user_meta = ensure(user.meta, @default_user_meta)
10085

101-
defp undo_follow_result({:error, :delete_follower, _result, _steps}) do
102-
{:error, [message: "already unfollowed", code: ecode(:already_did)]}
103-
end
86+
follower_user_ids =
87+
case opt do
88+
:add -> (target_user_meta.follower_user_ids ++ [user_id]) |> Enum.uniq()
89+
:remove -> (target_user_meta.follower_user_ids -- [user_id]) |> Enum.uniq()
90+
end
10491

105-
defp undo_follow_result({:error, :delete_following, _result, _steps}) do
106-
{:error, [message: "unfollow fails", code: ecode(:react_fails)]}
107-
end
92+
following_user_ids =
93+
case opt do
94+
:add -> (user_meta.following_user_ids ++ [target_user.id]) |> Enum.uniq()
95+
:remove -> (user_meta.following_user_ids -- [target_user.id]) |> Enum.uniq()
96+
end
10897

109-
defp undo_follow_result({:error, :minus_achievement, _result, _steps}) do
110-
{:error, [message: "follow acieve fails", code: ecode(:react_fails)]}
98+
Multi.new()
99+
|> Multi.run(:update_follower_meta, fn _, _ ->
100+
followers_count = length(follower_user_ids)
101+
meta = Map.merge(target_user_meta, %{follower_user_ids: follower_user_ids})
102+
103+
ORM.update_meta(target_user, meta, changes: %{followers_count: followers_count})
104+
end)
105+
|> Multi.run(:update_following_meta, fn _, _ ->
106+
followings_count = length(following_user_ids)
107+
meta = Map.merge(user_meta, %{following_user_ids: following_user_ids})
108+
109+
ORM.update_meta(user, meta, changes: %{followings_count: followings_count})
110+
end)
111+
|> Repo.transaction()
112+
|> result()
113+
end
111114
end
112115

113116
@doc """
114117
get paged followers of a user
115118
"""
116-
@spec fetch_followers(User.t(), map()) :: {:ok, map()} | {:error, String.t()}
117-
def fetch_followers(%User{id: user_id}, filter) do
119+
def paged_followers(%User{id: user_id}, filter, %User{} = cur_user) do
120+
paged_followers(%User{id: user_id}, filter)
121+
|> mark_viewer_follow_status(cur_user)
122+
|> done
123+
end
124+
125+
@spec paged_followers(User.t(), map()) :: {:ok, map()} | {:error, String.t()}
126+
def paged_followers(%User{id: user_id}, filter) do
118127
UserFollower
119128
|> where([uf], uf.user_id == ^user_id)
120129
|> join(:inner, [uf], u in assoc(uf, :follower))
@@ -124,8 +133,14 @@ defmodule GroupherServer.Accounts.Delegate.Fans do
124133
@doc """
125134
get paged followings of a user
126135
"""
127-
@spec fetch_followings(User.t(), map()) :: {:ok, map()} | {:error, String.t()}
128-
def fetch_followings(%User{id: user_id}, filter) do
136+
def paged_followings(%User{id: user_id}, filter, %User{} = cur_user) do
137+
paged_followings(%User{id: user_id}, filter)
138+
|> mark_viewer_follow_status(cur_user)
139+
|> done
140+
end
141+
142+
@spec paged_followings(User.t(), map()) :: {:ok, map()} | {:error, String.t()}
143+
def paged_followings(%User{id: user_id}, filter) do
129144
UserFollowing
130145
|> where([uf], uf.user_id == ^user_id)
131146
|> join(:inner, [uf], u in assoc(uf, :following))
@@ -140,4 +155,66 @@ defmodule GroupherServer.Accounts.Delegate.Fans do
140155
|> ORM.paginater(~m(page size)a)
141156
|> done()
142157
end
158+
159+
@doc """
160+
mark viewer's follower/followings states
161+
"""
162+
def mark_viewer_follow_status({:ok, %{entries: entries} = paged_users}, cur_user) do
163+
entries = Enum.map(entries, &Map.merge(&1, do_mark_viewer_has_states(&1.id, cur_user)))
164+
Map.merge(paged_users, %{entries: entries})
165+
end
166+
167+
def mark_viewer_follow_status({:error, reason}), do: {:error, reason}
168+
169+
defp do_mark_viewer_has_states(user_id, %User{meta: nil}) do
170+
%{viewer_been_followed: false, viewer_has_followed: false}
171+
end
172+
173+
defp do_mark_viewer_has_states(user_id, %User{meta: meta}) do
174+
%{
175+
viewer_been_followed: Enum.member?(meta.follower_user_ids, user_id),
176+
viewer_has_followed: Enum.member?(meta.following_user_ids, user_id)
177+
}
178+
end
179+
180+
@spec result({:ok, map()}) :: SpecType.done()
181+
defp result({:ok, %{create_follower: user_follower}}) do
182+
User |> ORM.find(user_follower.user_id)
183+
end
184+
185+
defp result({:ok, %{delete_follower: user_follower}}) do
186+
User |> ORM.find(user_follower.user_id)
187+
end
188+
189+
defp result({:ok, %{update_follower_meta: result}}) do
190+
{:ok, result}
191+
end
192+
193+
defp result({:error, :create_follower, %Ecto.Changeset{}, _steps}) do
194+
{:error, [message: "already followed", code: ecode(:already_did)]}
195+
end
196+
197+
defp result({:error, :create_follower, _result, _steps}) do
198+
{:error, [message: "already followed", code: ecode(:already_did)]}
199+
end
200+
201+
defp result({:error, :create_following, _result, _steps}) do
202+
{:error, [message: "follow fails", code: ecode(:react_fails)]}
203+
end
204+
205+
defp result({:error, :delete_follower, _result, _steps}) do
206+
{:error, [message: "already unfollowed", code: ecode(:already_did)]}
207+
end
208+
209+
defp result({:error, :delete_following, _result, _steps}) do
210+
{:error, [message: "unfollow fails", code: ecode(:react_fails)]}
211+
end
212+
213+
defp result({:error, :minus_achievement, _result, _steps}) do
214+
{:error, [message: "follow acieve fails", code: ecode(:react_fails)]}
215+
end
216+
217+
defp result({:error, :add_achievement, _result, _steps}) do
218+
{:error, [message: "follow acieve fails", code: ecode(:react_fails)]}
219+
end
143220
end

lib/groupher_server/accounts/delegates/profile.ex

+45-4
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,57 @@ defmodule GroupherServer.Accounts.Delegate.Profile do
66
import Helper.Utils, only: [done: 1, get_config: 2]
77
import ShortMaps
88

9-
alias GroupherServer.{Accounts, CMS, Email, Repo}
9+
alias GroupherServer.{Accounts, CMS, Email, Repo, Statistics}
1010

1111
alias Accounts.{Achievement, GithubUser, User, Social}
1212
alias Helper.{Guardian, ORM, QueryBuilder, RadarSearch}
1313

14+
alias GroupherServer.Accounts.Delegate.Fans
1415
alias Ecto.Multi
1516

1617
@default_subscribed_communities get_config(:general, :default_subscribed_communities)
1718

19+
def read_user(login) when is_binary(login) do
20+
with {:ok, user} <- ORM.read_by(User, %{login: login}, inc: :views) do
21+
case user.contributes do
22+
nil -> assign_default_contributes(user)
23+
_ -> {:ok, user}
24+
end
25+
end
26+
end
27+
28+
def read_user(login, %User{meta: nil}), do: read_user(login)
29+
30+
def read_user(login, %User{} = cur_user) do
31+
with {:ok, user} <- read_user(login) do
32+
# Ta 关注了你
33+
viewer_been_followed = user.id in cur_user.meta.follower_user_ids
34+
# 正在关注
35+
viewer_has_followed = user.id in cur_user.meta.following_user_ids
36+
37+
user =
38+
Map.merge(user, %{
39+
viewer_been_followed: viewer_been_followed,
40+
viewer_has_followed: viewer_has_followed
41+
})
42+
43+
{:ok, user}
44+
end
45+
end
46+
47+
def paged_users(filter, %User{} = user) do
48+
ORM.find_all(User, filter) |> Fans.mark_viewer_follow_status(user) |> done
49+
end
50+
51+
def paged_users(filter) do
52+
ORM.find_all(User, filter)
53+
end
54+
1855
@doc """
1956
update user's profile
2057
"""
2158
def update_profile(%User{} = user, attrs \\ %{}) do
22-
changeset =
23-
user
24-
|> Ecto.Changeset.change(attrs)
59+
changeset = user |> Ecto.Changeset.change(attrs)
2560

2661
changeset
2762
|> update_social_ifneed(user, attrs)
@@ -257,4 +292,10 @@ defmodule GroupherServer.Accounts.Delegate.Profile do
257292
changeset
258293
end
259294
end
295+
296+
# assign default contributes
297+
defp assign_default_contributes(%User{} = user) do
298+
{:ok, contributes} = Statistics.list_contributes_digest(%User{id: user.id})
299+
ORM.update_embed(user, :contributes, contributes)
300+
end
260301
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
defmodule GroupherServer.Accounts.Embeds.UserContribute do
2+
@moduledoc """
3+
user contribute
4+
"""
5+
use Ecto.Schema
6+
use Accessible
7+
import Ecto.Changeset
8+
9+
alias GroupherServer.Accounts.Embeds
10+
11+
@optional_fields ~w(reported_count)a
12+
13+
embedded_schema do
14+
field(:start_date, :date)
15+
field(:end_date, :date)
16+
field(:total_count, :integer, default: 0)
17+
embeds_many(:records, Embeds.UserContributeRecord, on_replace: :delete)
18+
end
19+
20+
def changeset(struct, params) do
21+
struct
22+
|> cast(params, @optional_fields)
23+
|> cast_embed(:records, with: &Embeds.UserContributeRecord.changeset/2)
24+
end
25+
end

0 commit comments

Comments
 (0)