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

refactor(user): move users query to own fields #371

Merged
merged 14 commits into from
May 29, 2021
2 changes: 1 addition & 1 deletion cover/excoveralls.json

Large diffs are not rendered by default.

11 changes: 9 additions & 2 deletions lib/groupher_server/accounts/accounts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ defmodule GroupherServer.Accounts do
}

# profile
defdelegate read_user(user), to: Profile
defdelegate read_user(login, user), to: Profile
defdelegate paged_users(filter), to: Profile
defdelegate paged_users(filter, user), to: Profile

defdelegate update_profile(user, attrs), to: Profile
defdelegate update_geo(user, remote_ip), to: Profile
defdelegate github_signin(github_user), to: Profile
Expand Down Expand Up @@ -46,8 +51,10 @@ defmodule GroupherServer.Accounts do
# fans
defdelegate follow(user, follower), to: Fans
defdelegate undo_follow(user, follower), to: Fans
defdelegate fetch_followers(user, filter), to: Fans
defdelegate fetch_followings(user, filter), to: Fans
defdelegate paged_followers(user, filter), to: Fans
defdelegate paged_followers(user, filter, cur_user), to: Fans
defdelegate paged_followings(user, filter), to: Fans
defdelegate paged_followings(user, filter, cur_user), to: Fans

# upvoted articles
defdelegate paged_upvoted_articles(user_id, filter), to: UpvotedArticles
Expand Down
195 changes: 136 additions & 59 deletions lib/groupher_server/accounts/delegates/fans.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,118 +3,127 @@ defmodule GroupherServer.Accounts.Delegate.Fans do
user followers / following related
"""
import Ecto.Query, warn: false
import Helper.Utils, only: [done: 1]
import Helper.Utils, only: [done: 1, ensure: 2]
import Helper.ErrorCode
import ShortMaps

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

alias GroupherServer.Accounts.{User, UserFollower, UserFollowing}
alias GroupherServer.Accounts.{User, Embeds, UserFollower, UserFollowing}

alias Ecto.Multi

@default_user_meta Embeds.UserMeta.default_meta()

@doc """
follow a user
"""
@spec follow(User.t(), User.t()) :: {:ok, User.t()} | SpecType.gq_error()
def follow(%User{id: user_id}, %User{id: follower_id}) do
with true <- to_string(user_id) !== to_string(follower_id),
{:ok, _follow_user} <- ORM.find(User, follower_id) do
{:ok, target_user} <- ORM.find(User, follower_id) do
Multi.new()
|> Multi.insert(
:create_follower,
# UserFollower.changeset(%UserFollower{}, ~m(user_id follower_id)a)
UserFollower.changeset(%UserFollower{}, %{user_id: follower_id, follower_id: user_id})
UserFollower.changeset(%UserFollower{}, %{user_id: target_user.id, follower_id: user_id})
)
|> Multi.insert(
:create_following,
UserFollowing.changeset(%UserFollowing{}, %{user_id: user_id, following_id: follower_id})
UserFollowing.changeset(%UserFollowing{}, %{
user_id: user_id,
following_id: target_user.id
})
)
|> Multi.run(:update_user_follow_info, fn _, _ ->
update_user_follow_info(target_user, user_id, :add)
end)
|> Multi.run(:add_achievement, fn _, _ ->
Accounts.achieve(%User{id: follower_id}, :inc, :follow)
Accounts.achieve(%User{id: target_user.id}, :inc, :follow)
end)
|> Repo.transaction()
|> follow_result()
|> result()
else
false ->
{:error, [message: "can't follow yourself", code: ecode(:self_conflict)]}

{:error, reason} ->
{:error, [message: reason, code: ecode(:not_exsit)]}
false -> {:error, [message: "can't follow yourself", code: ecode(:self_conflict)]}
{:error, reason} -> {:error, [message: reason, code: ecode(:not_exsit)]}
end
end

@spec follow_result({:ok, map()}) :: SpecType.done()
defp follow_result({:ok, %{create_follower: user_follower}}) do
User |> ORM.find(user_follower.user_id)
end

defp follow_result({:error, :create_follower, %Ecto.Changeset{}, _steps}) do
{:error, [message: "already followed", code: ecode(:already_did)]}
end

defp follow_result({:error, :create_follower, _result, _steps}) do
{:error, [message: "already followed", code: ecode(:already_did)]}
end

defp follow_result({:error, :create_following, _result, _steps}) do
{:error, [message: "follow fails", code: ecode(:react_fails)]}
end

defp follow_result({:error, :add_achievement, _result, _steps}) do
{:error, [message: "follow acieve fails", code: ecode(:react_fails)]}
end

@doc """
undo a follow action to a user
"""
@spec undo_follow(User.t(), User.t()) :: {:ok, User.t()} | SpecType.gq_error()
def undo_follow(%User{id: user_id}, %User{id: follower_id}) do
with true <- to_string(user_id) !== to_string(follower_id),
{:ok, _follow_user} <- ORM.find(User, follower_id) do
{:ok, target_user} <- ORM.find(User, follower_id) do
Multi.new()
|> Multi.run(:delete_follower, fn _, _ ->
ORM.findby_delete!(UserFollower, %{user_id: follower_id, follower_id: user_id})
ORM.findby_delete!(UserFollower, %{user_id: target_user.id, follower_id: user_id})
end)
|> Multi.run(:delete_following, fn _, _ ->
ORM.findby_delete!(UserFollowing, %{user_id: user_id, following_id: follower_id})
ORM.findby_delete!(UserFollowing, %{user_id: user_id, following_id: target_user.id})
end)
|> Multi.run(:update_user_follow_info, fn _, _ ->
update_user_follow_info(target_user, user_id, :remove)
end)
|> Multi.run(:minus_achievement, fn _, _ ->
Accounts.achieve(%User{id: follower_id}, :dec, :follow)
Accounts.achieve(%User{id: target_user.id}, :dec, :follow)
end)
|> Repo.transaction()
|> undo_follow_result()
|> result()
else
false ->
{:error, [message: "can't undo follow yourself", code: ecode(:self_conflict)]}

{:error, reason} ->
{:error, [message: reason, code: ecode(:not_exsit)]}
false -> {:error, [message: "can't undo follow yourself", code: ecode(:self_conflict)]}
{:error, reason} -> {:error, [message: reason, code: ecode(:not_exsit)]}
end
end

defp undo_follow_result({:ok, %{delete_follower: user_follower}}) do
User |> ORM.find(user_follower.user_id)
end
# update follow in user meta
defp update_user_follow_info(%User{} = target_user, user_id, opt) do
with {:ok, user} <- ORM.find(User, user_id) do
target_user_meta = ensure(target_user.meta, @default_user_meta)
user_meta = ensure(user.meta, @default_user_meta)

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

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

defp undo_follow_result({:error, :minus_achievement, _result, _steps}) do
{:error, [message: "follow acieve fails", code: ecode(:react_fails)]}
Multi.new()
|> Multi.run(:update_follower_meta, fn _, _ ->
followers_count = length(follower_user_ids)
meta = Map.merge(target_user_meta, %{follower_user_ids: follower_user_ids})

ORM.update_meta(target_user, meta, changes: %{followers_count: followers_count})
end)
|> Multi.run(:update_following_meta, fn _, _ ->
followings_count = length(following_user_ids)
meta = Map.merge(user_meta, %{following_user_ids: following_user_ids})

ORM.update_meta(user, meta, changes: %{followings_count: followings_count})
end)
|> Repo.transaction()
|> result()
end
end

@doc """
get paged followers of a user
"""
@spec fetch_followers(User.t(), map()) :: {:ok, map()} | {:error, String.t()}
def fetch_followers(%User{id: user_id}, filter) do
def paged_followers(%User{id: user_id}, filter, %User{} = cur_user) do
paged_followers(%User{id: user_id}, filter)
|> mark_viewer_follow_status(cur_user)
|> done
end

@spec paged_followers(User.t(), map()) :: {:ok, map()} | {:error, String.t()}
def paged_followers(%User{id: user_id}, filter) do
UserFollower
|> where([uf], uf.user_id == ^user_id)
|> join(:inner, [uf], u in assoc(uf, :follower))
Expand All @@ -124,8 +133,14 @@ defmodule GroupherServer.Accounts.Delegate.Fans do
@doc """
get paged followings of a user
"""
@spec fetch_followings(User.t(), map()) :: {:ok, map()} | {:error, String.t()}
def fetch_followings(%User{id: user_id}, filter) do
def paged_followings(%User{id: user_id}, filter, %User{} = cur_user) do
paged_followings(%User{id: user_id}, filter)
|> mark_viewer_follow_status(cur_user)
|> done
end

@spec paged_followings(User.t(), map()) :: {:ok, map()} | {:error, String.t()}
def paged_followings(%User{id: user_id}, filter) do
UserFollowing
|> where([uf], uf.user_id == ^user_id)
|> join(:inner, [uf], u in assoc(uf, :following))
Expand All @@ -140,4 +155,66 @@ defmodule GroupherServer.Accounts.Delegate.Fans do
|> ORM.paginater(~m(page size)a)
|> done()
end

@doc """
mark viewer's follower/followings states
"""
def mark_viewer_follow_status({:ok, %{entries: entries} = paged_users}, cur_user) do
entries = Enum.map(entries, &Map.merge(&1, do_mark_viewer_has_states(&1.id, cur_user)))
Map.merge(paged_users, %{entries: entries})
end

def mark_viewer_follow_status({:error, reason}), do: {:error, reason}

defp do_mark_viewer_has_states(user_id, %User{meta: nil}) do
%{viewer_been_followed: false, viewer_has_followed: false}
end

defp do_mark_viewer_has_states(user_id, %User{meta: meta}) do
%{
viewer_been_followed: Enum.member?(meta.follower_user_ids, user_id),
viewer_has_followed: Enum.member?(meta.following_user_ids, user_id)
}
end

@spec result({:ok, map()}) :: SpecType.done()
defp result({:ok, %{create_follower: user_follower}}) do
User |> ORM.find(user_follower.user_id)
end

defp result({:ok, %{delete_follower: user_follower}}) do
User |> ORM.find(user_follower.user_id)
end

defp result({:ok, %{update_follower_meta: result}}) do
{:ok, result}
end

defp result({:error, :create_follower, %Ecto.Changeset{}, _steps}) do
{:error, [message: "already followed", code: ecode(:already_did)]}
end

defp result({:error, :create_follower, _result, _steps}) do
{:error, [message: "already followed", code: ecode(:already_did)]}
end

defp result({:error, :create_following, _result, _steps}) do
{:error, [message: "follow fails", code: ecode(:react_fails)]}
end

defp result({:error, :delete_follower, _result, _steps}) do
{:error, [message: "already unfollowed", code: ecode(:already_did)]}
end

defp result({:error, :delete_following, _result, _steps}) do
{:error, [message: "unfollow fails", code: ecode(:react_fails)]}
end

defp result({:error, :minus_achievement, _result, _steps}) do
{:error, [message: "follow acieve fails", code: ecode(:react_fails)]}
end

defp result({:error, :add_achievement, _result, _steps}) do
{:error, [message: "follow acieve fails", code: ecode(:react_fails)]}
end
end
49 changes: 45 additions & 4 deletions lib/groupher_server/accounts/delegates/profile.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,57 @@ defmodule GroupherServer.Accounts.Delegate.Profile do
import Helper.Utils, only: [done: 1, get_config: 2]
import ShortMaps

alias GroupherServer.{Accounts, CMS, Email, Repo}
alias GroupherServer.{Accounts, CMS, Email, Repo, Statistics}

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

alias GroupherServer.Accounts.Delegate.Fans
alias Ecto.Multi

@default_subscribed_communities get_config(:general, :default_subscribed_communities)

def read_user(login) when is_binary(login) do
with {:ok, user} <- ORM.read_by(User, %{login: login}, inc: :views) do
case user.contributes do
nil -> assign_default_contributes(user)
_ -> {:ok, user}
end
end
end

def read_user(login, %User{meta: nil}), do: read_user(login)

def read_user(login, %User{} = cur_user) do
with {:ok, user} <- read_user(login) do
# Ta 关注了你
viewer_been_followed = user.id in cur_user.meta.follower_user_ids
# 正在关注
viewer_has_followed = user.id in cur_user.meta.following_user_ids

user =
Map.merge(user, %{
viewer_been_followed: viewer_been_followed,
viewer_has_followed: viewer_has_followed
})

{:ok, user}
end
end

def paged_users(filter, %User{} = user) do
ORM.find_all(User, filter) |> Fans.mark_viewer_follow_status(user) |> done
end

def paged_users(filter) do
ORM.find_all(User, filter)
end

@doc """
update user's profile
"""
def update_profile(%User{} = user, attrs \\ %{}) do
changeset =
user
|> Ecto.Changeset.change(attrs)
changeset = user |> Ecto.Changeset.change(attrs)

changeset
|> update_social_ifneed(user, attrs)
Expand Down Expand Up @@ -257,4 +292,10 @@ defmodule GroupherServer.Accounts.Delegate.Profile do
changeset
end
end

# assign default contributes
defp assign_default_contributes(%User{} = user) do
{:ok, contributes} = Statistics.list_contributes_digest(%User{id: user.id})
ORM.update_embed(user, :contributes, contributes)
end
end
25 changes: 25 additions & 0 deletions lib/groupher_server/accounts/embeds/user_contribute.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
defmodule GroupherServer.Accounts.Embeds.UserContribute do
@moduledoc """
user contribute
"""
use Ecto.Schema
use Accessible
import Ecto.Changeset

alias GroupherServer.Accounts.Embeds

@optional_fields ~w(reported_count)a

embedded_schema do
field(:start_date, :date)
field(:end_date, :date)
field(:total_count, :integer, default: 0)
embeds_many(:records, Embeds.UserContributeRecord, on_replace: :delete)
end

def changeset(struct, params) do
struct
|> cast(params, @optional_fields)
|> cast_embed(:records, with: &Embeds.UserContributeRecord.changeset/2)
end
end
Loading