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
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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