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

Commit 7ce9f06

Browse files
committed
refactor(article-emotions): wip
1 parent f2d1e7b commit 7ce9f06

File tree

13 files changed

+464
-10
lines changed

13 files changed

+464
-10
lines changed

config/config.exs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,20 @@ config :groupher_server, :customization,
6161
sidebar_communities_index: %{}
6262

6363
config :groupher_server, :article,
64-
supported_emotions: [:downvote, :beer, :heart, :biceps, :orz, :confused, :pill, :popcorn],
64+
emotionable_threads: [:post, :job],
65+
# NOTE: if you want to add/remove emotion, just edit the list below
66+
# and migrate the field to table "articles_users_emotions"
67+
supported_emotions: [
68+
:upvote,
69+
:downvote,
70+
:beer,
71+
:heart,
72+
:biceps,
73+
:orz,
74+
:confused,
75+
:pill,
76+
:popcorn
77+
],
6578
# NOTE: if you want to add/remove emotion, just edit the list below
6679
# and migrate the field to table "articles_comments_users_emotions"
6780
comment_supported_emotions: [

lib/groupher_server/cms/article_comment_user_emotion.ex

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
defmodule GroupherServer.CMS.ArticleCommentUserEmotion.Macros do
22
import Helper.Utils, only: [get_config: 2]
33

4-
alias GroupherServer.CMS
5-
alias CMS.ArticleComment
6-
74
@supported_emotions get_config(:article, :comment_supported_emotions)
85

96
defmacro emotion_fields() do
@@ -31,7 +28,6 @@ defmodule GroupherServer.CMS.ArticleCommentUserEmotion do
3128
@supported_emotions get_config(:article, :comment_supported_emotions)
3229

3330
@required_fields ~w(article_comment_id user_id recived_user_id)a
34-
# @optional_fields ~w(downvote beer heart biceps orz confused pill)a
3531
@optional_fields Enum.map(@supported_emotions, &:"#{&1}")
3632

3733
@type t :: %ArticleCommentUserEmotion{}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
defmodule GroupherServer.CMS.ArticleUserEmotion.Macros do
2+
import Helper.Utils, only: [get_config: 2]
3+
4+
alias GroupherServer.CMS
5+
6+
@supported_emotions get_config(:article, :supported_emotions)
7+
8+
defmacro emotion_fields() do
9+
@supported_emotions
10+
|> Enum.map(fn emotion ->
11+
quote do
12+
field(unquote(:"#{emotion}"), :boolean, default: false)
13+
end
14+
end)
15+
end
16+
end
17+
18+
defmodule GroupherServer.CMS.ArticleUserEmotion do
19+
@moduledoc false
20+
alias __MODULE__
21+
22+
use Ecto.Schema
23+
import Ecto.Changeset
24+
import GroupherServer.CMS.ArticleUserEmotion.Macros
25+
import Helper.Utils, only: [get_config: 2]
26+
27+
alias GroupherServer.{Accounts, CMS}
28+
alias CMS.{Post, Job}
29+
30+
@supported_emotions get_config(:article, :supported_emotions)
31+
@supported_threads get_config(:article, :emotionable_threads)
32+
33+
@required_fields ~w(user_id recived_user_id)a
34+
@optional_fields Enum.map(@supported_threads, &:"#{&1}_id") ++
35+
Enum.map(@supported_emotions, &:"#{&1}")
36+
37+
@type t :: %ArticleUserEmotion{}
38+
schema "articles_users_emotions" do
39+
belongs_to(:post, Post, foreign_key: :post_id)
40+
belongs_to(:job, Job, foreign_key: :job_id)
41+
belongs_to(:recived_user, Accounts.User, foreign_key: :recived_user_id)
42+
belongs_to(:user, Accounts.User, foreign_key: :user_id)
43+
44+
emotion_fields()
45+
timestamps(type: :utc_datetime)
46+
end
47+
48+
@doc false
49+
def changeset(%ArticleUserEmotion{} = struct, attrs) do
50+
struct
51+
|> cast(attrs, @required_fields ++ @optional_fields)
52+
|> validate_required(@required_fields)
53+
|> foreign_key_constraint(:post_id)
54+
|> foreign_key_constraint(:job_id)
55+
|> foreign_key_constraint(:user_id)
56+
|> foreign_key_constraint(:recived_user_id)
57+
end
58+
59+
def update_changeset(%ArticleUserEmotion{} = struct, attrs) do
60+
struct
61+
|> cast(attrs, @required_fields ++ @optional_fields)
62+
|> validate_required(@required_fields)
63+
|> foreign_key_constraint(:post_id)
64+
|> foreign_key_constraint(:job_id)
65+
|> foreign_key_constraint(:user_id)
66+
|> foreign_key_constraint(:recived_user_id)
67+
end
68+
end

lib/groupher_server/cms/author.ex

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ defmodule GroupherServer.CMS.Author do
77

88
import Ecto.Changeset
99

10-
alias GroupherServer.{Accounts, CMS}
11-
alias CMS.Post
10+
alias GroupherServer.Accounts
11+
# alias CMS.Post
1212

1313
@type t :: %Author{}
1414

1515
schema "cms_authors" do
1616
field(:role, :string)
1717
# field(:user_id, :id)
18-
has_many(:posts, Post)
18+
# has_many(:posts, Post)
1919
# user_id filed in own-table
2020
belongs_to(:user, Accounts.User)
2121

lib/groupher_server/cms/cms.ex

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ defmodule GroupherServer.CMS do
1111
AbuseReport,
1212
ArticleCURD,
1313
ArticleOperation,
14+
ArticleEmotion,
1415
ArticleReaction,
1516
ArticleComment,
1617
ArticleCommentAction,
@@ -111,6 +112,9 @@ defmodule GroupherServer.CMS do
111112
defdelegate set_community(community, thread, content_id), to: ArticleOperation
112113
defdelegate unset_community(community, thread, content_id), to: ArticleOperation
113114

115+
defdelegate emotion_to_article(thread, article_id, args, user), to: ArticleEmotion
116+
defdelegate undo_emotion_to_article(thread, article_id, args, user), to: ArticleEmotion
117+
114118
# Comment CURD
115119
defdelegate list_article_comments(thread, article_id, filters, mode), to: ArticleComment
116120
defdelegate list_article_comments(thread, article_id, filters, mode, user), to: ArticleComment

lib/groupher_server/cms/delegates/article_curd.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
2020

2121
alias Ecto.Multi
2222

23+
@default_emotions Embeds.ArticleEmotion.default_emotions()
2324
@default_article_meta Embeds.ArticleMeta.default_meta()
2425

2526
@doc """
@@ -389,6 +390,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
389390
target
390391
|> struct()
391392
|> target.changeset(attrs)
393+
|> Ecto.Changeset.put_change(:emotions, @default_emotions)
392394
|> Ecto.Changeset.put_change(:author_id, aid)
393395
|> Ecto.Changeset.put_change(:origial_community_id, integerfy(cid))
394396
|> Ecto.Changeset.put_embed(:meta, @default_article_meta)
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
defmodule GroupherServer.CMS.Delegate.ArticleEmotion do
2+
@moduledoc """
3+
CURD and operations for article comments
4+
"""
5+
import Ecto.Query, warn: false
6+
import GroupherServer.CMS.Helper.Matcher2
7+
8+
alias Helper.ORM
9+
alias GroupherServer.{Accounts, CMS, Repo}
10+
11+
alias Accounts.User
12+
alias CMS.{ArticleUserEmotion}
13+
14+
alias Ecto.Multi
15+
16+
@type t_user_list :: [%{login: String.t()}]
17+
@type t_mention_status :: %{user_list: t_user_list, user_count: Integer.t()}
18+
19+
# ArticleComment.max_latest_emotion_users_count()
20+
@max_latest_emotion_users_count 4
21+
22+
@doc "make emotion to a comment"
23+
def emotion_to_article(thread, article_id, emotion, %User{} = user) do
24+
with {:ok, info} <- match(thread),
25+
{:ok, article} <- ORM.find(info.model, article_id, preload: :author) do
26+
Multi.new()
27+
|> Multi.run(:create_user_emotion, fn _, _ ->
28+
target =
29+
%{recived_user_id: article.author.user_id, user_id: user.id}
30+
|> Map.put(info.foreign_key, article_id)
31+
32+
args = Map.put(target, :"#{emotion}", true)
33+
34+
case ORM.find_by(ArticleUserEmotion, target) do
35+
{:ok, article_user_emotion} -> article_user_emotion |> ORM.update(args)
36+
{:error, _} -> ArticleUserEmotion |> ORM.create(args)
37+
end
38+
end)
39+
|> Multi.run(:query_emotion_status, fn _, _ ->
40+
query_emotion_status(thread, article.id, emotion)
41+
end)
42+
|> Multi.run(:update_emotion, fn _, %{query_emotion_status: status} ->
43+
update_emotion(article, emotion, status, user)
44+
end)
45+
|> Repo.transaction()
46+
|> update_emotion_result
47+
end
48+
end
49+
50+
def undo_emotion_to_article(thread, article_id, emotion, %User{} = user) do
51+
with {:ok, info} <- match(thread),
52+
{:ok, article} <- ORM.find(info.model, article_id, preload: :author) do
53+
Multi.new()
54+
|> Multi.run(:update_user_emotion, fn _, _ ->
55+
target =
56+
%{recived_user_id: article.author.id, user_id: user.id}
57+
|> Map.put(info.foreign_key, article_id)
58+
59+
{:ok, article_user_emotion} = ORM.find_by(ArticleUserEmotion, target)
60+
args = Map.put(target, :"#{emotion}", false)
61+
article_user_emotion |> ORM.update(args)
62+
end)
63+
|> Multi.run(:query_emotion_status, fn _, _ ->
64+
query_emotion_status(thread, article.id, emotion)
65+
end)
66+
|> Multi.run(:update_emotion, fn _, %{query_emotion_status: status} ->
67+
update_emotion(article, emotion, status, user)
68+
end)
69+
|> Repo.transaction()
70+
|> update_emotion_result
71+
end
72+
end
73+
74+
# @spec query_emotion_status(ArticleComment.t(), Atom.t()) :: {:ok, t_mention_status}
75+
defp query_emotion_status(thread, article_id, emotion) do
76+
with {:ok, info} <- match(thread) do
77+
# 每次被 emotion 动作触发后重新查询,主要原因
78+
# 1.并发下保证数据准确,类似 views 阅读数的统计
79+
# 2. 前端使用 nickname 而非 login 展示,如果用户改了 nickname, 可以"自动纠正"
80+
query =
81+
from(a in ArticleUserEmotion,
82+
join: user in User,
83+
on: a.user_id == user.id,
84+
where: field(a, ^info.foreign_key) == ^article_id,
85+
where: field(a, ^emotion) == true,
86+
select: %{login: user.login, nickname: user.nickname}
87+
)
88+
89+
emotioned_user_info_list = Repo.all(query) |> Enum.uniq()
90+
emotioned_user_count = length(emotioned_user_info_list)
91+
92+
{:ok, %{user_list: emotioned_user_info_list, user_count: emotioned_user_count}}
93+
end
94+
end
95+
96+
@spec update_emotion(ArticleComment.t(), Atom.t(), t_mention_status, User.t()) ::
97+
{:ok, ArticleComment.t()} | {:error, any}
98+
defp update_emotion(comment, emotion, status, user) do
99+
%{user_count: user_count, user_list: user_list} = status
100+
101+
emotions =
102+
%{}
103+
|> Map.put(:"#{emotion}_count", user_count)
104+
|> Map.put(:"#{emotion}_user_logins", user_list |> Enum.map(& &1.login))
105+
|> Map.put(
106+
:"latest_#{emotion}_users",
107+
Enum.slice(user_list, 0, @max_latest_emotion_users_count)
108+
)
109+
110+
viewer_has_emotioned = user.login in Map.get(emotions, :"#{emotion}_user_logins")
111+
emotions = emotions |> Map.put(:"viewer_has_#{emotion}ed", viewer_has_emotioned)
112+
113+
comment
114+
|> Ecto.Changeset.change()
115+
|> Ecto.Changeset.put_embed(:emotions, emotions)
116+
|> Repo.update()
117+
# virtual field can not be updated
118+
|> add_viewer_emotioned_ifneed(emotions)
119+
end
120+
121+
defp add_viewer_emotioned_ifneed({:error, error}, _), do: {:error, error}
122+
123+
defp add_viewer_emotioned_ifneed({:ok, comment}, emotions) do
124+
# Map.merge(comment, %{emotion: emotions})
125+
{:ok, Map.merge(comment, %{emotion: emotions})}
126+
end
127+
128+
defp update_emotion_result({:ok, %{update_emotion: result}}), do: {:ok, result}
129+
130+
defp update_emotion_result({:error, _, result, _steps}) do
131+
{:error, result}
132+
end
133+
end

lib/groupher_server/cms/job.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ defmodule GroupherServer.CMS.Job do
114114
content
115115
|> validate_length(:title, min: 3, max: 50)
116116
|> validate_length(:body, min: 3, max: 10_000)
117+
# |> cast_embed(:emotions, with: &Embeds.ArticleEmotion.changeset/2)
117118
|> HTML.safe_string(:body)
118119
end
119120
end

lib/groupher_server/cms/post.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ defmodule GroupherServer.CMS.Post do
121121
defp generl_changeset(content) do
122122
content
123123
|> validate_length(:title, min: 3, max: 50)
124+
|> cast_embed(:emotions, with: &Embeds.ArticleEmotion.changeset/2)
124125
|> validate_length(:body, min: 3, max: 10_000)
125126
|> validate_length(:link_addr, min: 5, max: 400)
126127
|> HTML.safe_string(:body)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
defmodule GroupherServer.Repo.Migrations.CreateArticleUserEmotion do
2+
use Ecto.Migration
3+
4+
def change do
5+
create table(:articles_users_emotions) do
6+
add(:post_id, references(:cms_posts, on_delete: :delete_all))
7+
add(:job_id, references(:cms_jobs, on_delete: :delete_all))
8+
9+
add(:user_id, references(:users, on_delete: :delete_all), null: false)
10+
add(:recived_user_id, references(:users, on_delete: :delete_all), null: false)
11+
12+
add(:upvote, :boolean, default: false)
13+
add(:downvote, :boolean, default: false)
14+
add(:beer, :boolean, default: false)
15+
add(:heart, :boolean, default: false)
16+
add(:biceps, :boolean, default: false)
17+
add(:orz, :boolean, default: false)
18+
add(:confused, :boolean, default: false)
19+
add(:pill, :boolean, default: false)
20+
add(:popcorn, :boolean, default: false)
21+
22+
timestamps()
23+
end
24+
end
25+
end

0 commit comments

Comments
 (0)