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

Commit b217cdf

Browse files
authored
feat(comments): pined comments (#322)
* feat(article-comments): pin/unpin * feat(article-comments): pin/unpin for paged comments * refactor(article-comments): improve tests
1 parent d27a043 commit b217cdf

File tree

9 files changed

+287
-14
lines changed

9 files changed

+287
-14
lines changed

lib/groupher_server/cms/article_comment.ex

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ defmodule GroupherServer.CMS.ArticleComment do
2020

2121
@required_fields ~w(body_html author_id)a
2222
@optional_fields ~w(post_id job_id reply_to_id replies_count is_folded is_reported is_deleted floor is_article_author)a
23-
@updatable_fields ~w(is_folded is_reported is_deleted floor upvotes_count)a
23+
@updatable_fields ~w(is_folded is_reported is_deleted floor upvotes_count is_pined)a
2424

2525
@max_participator_count 5
2626
@max_parent_replies_count 3
@@ -32,6 +32,9 @@ defmodule GroupherServer.CMS.ArticleComment do
3232
# 举报超过此数评论会被自动折叠
3333
@report_threshold_for_fold 5
3434

35+
# 每篇文章最多含有置顶评论的条数
36+
@pined_comment_limit 10
37+
3538
@doc "latest participators stores in article comment_participators field"
3639
def max_participator_count(), do: @max_participator_count
3740
@doc "latest replies stores in article_comment replies field, used for frontend display"
@@ -44,6 +47,7 @@ defmodule GroupherServer.CMS.ArticleComment do
4447
def delete_hint(), do: @delete_hint
4548

4649
def report_threshold_for_fold, do: @report_threshold_for_fold
50+
def pined_comment_limit, do: @pined_comment_limit
4751

4852
@type t :: %ArticleComment{}
4953
schema "articles_comments" do
@@ -63,6 +67,9 @@ defmodule GroupherServer.CMS.ArticleComment do
6367
field(:is_article_author, :boolean, default: false)
6468
field(:upvotes_count, :integer, default: 0)
6569

70+
# 是否置顶
71+
field(:is_pined, :boolean, default: false)
72+
6673
belongs_to(:author, Accounts.User, foreign_key: :author_id)
6774
belongs_to(:post, Post, foreign_key: :post_id)
6875
belongs_to(:job, Job, foreign_key: :job_id)
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
defmodule GroupherServer.CMS.ArticlePinedComment do
2+
@moduledoc false
3+
alias __MODULE__
4+
5+
use Ecto.Schema
6+
use Accessible
7+
8+
import Ecto.Changeset
9+
10+
alias GroupherServer.CMS
11+
12+
alias CMS.{
13+
Post,
14+
Job,
15+
ArticleComment
16+
}
17+
18+
# alias Helper.HTML
19+
20+
@required_fields ~w(article_comment_id)a
21+
@optional_fields ~w(post_id job_id)a
22+
23+
@type t :: %ArticlePinedComment{}
24+
schema "articles_pined_comments" do
25+
belongs_to(:article_comment, ArticleComment, foreign_key: :article_comment_id)
26+
belongs_to(:post, Post, foreign_key: :post_id)
27+
belongs_to(:job, Job, foreign_key: :job_id)
28+
29+
timestamps(type: :utc_datetime)
30+
end
31+
32+
@doc false
33+
def changeset(%ArticlePinedComment{} = article_pined_comment, attrs) do
34+
article_pined_comment
35+
|> cast(attrs, @required_fields ++ @optional_fields)
36+
|> validate_required(@required_fields)
37+
end
38+
39+
# @doc false
40+
def update_changeset(%ArticlePinedComment{} = article_pined_comment, attrs) do
41+
article_pined_comment
42+
|> cast(attrs, @required_fields ++ @updatable_fields)
43+
end
44+
end

lib/groupher_server/cms/cms.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,9 @@ defmodule GroupherServer.CMS do
129129
defdelegate delete_article_comment(comment_id, user), to: ArticleComment
130130
defdelegate reply_article_comment(comment_id, args, user), to: ArticleComment
131131

132+
defdelegate pin_article_comment(comment_id), to: ArticleComment
133+
defdelegate undo_pin_article_comment(comment_id), to: ArticleComment
134+
132135
defdelegate make_emotion(comment_id, args, user), to: ArticleComment
133136
defdelegate fold_article_comment(comment_id, user), to: ArticleComment
134137
defdelegate unfold_article_comment(comment_id, user), to: ArticleComment

lib/groupher_server/cms/delegates/article_comment.ex

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
1616

1717
alias CMS.{
1818
ArticleComment,
19+
ArticlePinedComment,
1920
ArticleCommentUpvote,
2021
ArticleCommentReply,
2122
ArticleCommentUserEmotion,
@@ -35,6 +36,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
3536
@report_threshold_for_fold ArticleComment.report_threshold_for_fold()
3637

3738
@default_comment_meta Embeds.ArticleCommentMeta.default_meta()
39+
@pined_comment_limit ArticleComment.pined_comment_limit()
3840

3941
@doc """
4042
list paged article comments
@@ -43,9 +45,10 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
4345
with {:ok, thread_query} <- match(thread, :query, article_id) do
4446
ArticleComment
4547
|> where(^thread_query)
46-
|> where([c], c.is_folded == false and c.is_reported == false)
48+
|> where([c], c.is_folded == false and c.is_reported == false and c.is_pined == false)
4749
|> QueryBuilder.filter_pack(filters)
4850
|> ORM.paginater(~m(page size)a)
51+
|> add_pined_comments_ifneed(thread, article_id, filters)
4952
|> done()
5053
end
5154
end
@@ -59,19 +62,53 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
5962
with {:ok, thread_query} <- match(thread, :query, article_id) do
6063
ArticleComment
6164
|> where(^thread_query)
62-
|> where([c], c.is_folded == false and c.is_reported == false)
65+
|> where([c], c.is_folded == false and c.is_reported == false and c.is_pined == false)
6366
|> QueryBuilder.filter_pack(filters)
6467
|> ORM.paginater(~m(page size)a)
6568
|> check_viewer_has_emotioned(user)
69+
|> add_pined_comments_ifneed(thread, article_id, filters)
6670
|> done()
6771
end
6872
end
6973

74+
# TODO: @pined_comment_limit 10
75+
defp add_pined_comments_ifneed(%{entries: entries} = paged_comments, thread, article_id, %{
76+
page: 1
77+
}) do
78+
with {:ok, info} <- match(thread),
79+
query <-
80+
from(p in ArticlePinedComment,
81+
join: c in ArticleComment,
82+
on: p.article_comment_id == c.id,
83+
where: field(p, ^info.foreign_key) == ^article_id,
84+
select: c
85+
),
86+
{:ok, pined_comments} <- query |> Repo.all() |> done() do
87+
case pined_comments do
88+
[] ->
89+
paged_comments
90+
91+
_ ->
92+
updated_entries =
93+
Enum.concat(Enum.slice(pined_comments, 0, @pined_comment_limit), entries)
94+
95+
pined_comment_count = length(pined_comments)
96+
97+
Map.merge(paged_comments, %{
98+
entries: updated_entries,
99+
total_count: paged_comments.total_count + pined_comment_count
100+
})
101+
end
102+
end
103+
end
104+
105+
defp add_pined_comments_ifneed(paged_comments, _thread, _article_id, _), do: paged_comments
106+
70107
def list_folded_article_comments(thread, article_id, %{page: page, size: size} = filters) do
71108
with {:ok, thread_query} <- match(thread, :query, article_id) do
72109
ArticleComment
73110
|> where(^thread_query)
74-
|> where([c], c.is_folded == true and c.is_reported == false)
111+
|> where([c], c.is_folded == true and c.is_reported == false and c.is_pined == false)
75112
|> QueryBuilder.filter_pack(filters)
76113
|> ORM.paginater(~m(page size)a)
77114
|> done()
@@ -87,7 +124,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
87124
with {:ok, thread_query} <- match(thread, :query, article_id) do
88125
ArticleComment
89126
|> where(^thread_query)
90-
|> where([c], c.is_folded == true and c.is_reported == false)
127+
|> where([c], c.is_folded == true and c.is_reported == false and c.is_pined == false)
91128
|> QueryBuilder.filter_pack(filters)
92129
|> ORM.paginater(~m(page size)a)
93130
|> check_viewer_has_emotioned(user)
@@ -136,6 +173,55 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
136173
|> done()
137174
end
138175

176+
@doc "pin a comment"
177+
def pin_article_comment(comment_id) do
178+
with {:ok, comment} <- ORM.find(ArticleComment, comment_id),
179+
{:ok, full_comment} <- get_full_comment(comment.id),
180+
{:ok, info} <- match(full_comment.thread) do
181+
Multi.new()
182+
|> Multi.run(:checked_pined_comments_count, fn _, _ ->
183+
count_query =
184+
from(p in ArticlePinedComment,
185+
where: field(p, ^info.foreign_key) == ^full_comment.article.id
186+
)
187+
188+
pined_comments_count = Repo.aggregate(count_query, :count)
189+
190+
case pined_comments_count >= @pined_comment_limit do
191+
true -> {:error, "only support #{@pined_comment_limit} pined comment for each article"}
192+
false -> {:ok, :pass}
193+
end
194+
end)
195+
|> Multi.run(:update_comment_flag, fn _, _ ->
196+
ORM.update(comment, %{is_pined: true})
197+
end)
198+
|> Multi.run(:add_pined_comment, fn _, _ ->
199+
ArticlePinedComment
200+
|> ORM.create(
201+
%{article_comment_id: comment.id}
202+
|> Map.put(info.foreign_key, full_comment.article.id)
203+
)
204+
end)
205+
|> Repo.transaction()
206+
|> upsert_comment_result()
207+
end
208+
end
209+
210+
def undo_pin_article_comment(comment_id) do
211+
with {:ok, comment} <- ORM.find(ArticleComment, comment_id) do
212+
Multi.new()
213+
|> Multi.run(:update_comment_flag, fn _, _ ->
214+
ORM.update(comment, %{is_pined: false})
215+
end)
216+
|> Multi.run(:remove_pined_comment, fn _, _ ->
217+
ORM.findby_delete(ArticlePinedComment, %{article_comment_id: comment.id})
218+
end)
219+
|> Repo.transaction()
220+
|> upsert_comment_result()
221+
end
222+
end
223+
224+
# TODO: remove pined record if need
139225
def delete_article_comment(comment_id, %User{} = _user) do
140226
with {:ok, comment} <-
141227
ORM.find(ArticleComment, comment_id) do
@@ -514,7 +600,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
514600
with {:ok, article_with_author} <- Repo.preload(article, author: :user) |> done(),
515601
article_author <- get_in(article_with_author, [:author, :user]) do
516602
#
517-
article_info = %{title: article.title}
603+
article_info = %{title: article.title, id: article.id}
518604

519605
author_info = %{
520606
id: article_author.id,
@@ -531,6 +617,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
531617
defp upsert_comment_result({:ok, %{check_article_author_upvoted: result}}), do: {:ok, result}
532618
defp upsert_comment_result({:ok, %{update_report_flag: result}}), do: {:ok, result}
533619
defp upsert_comment_result({:ok, %{update_comment_emotion: result}}), do: {:ok, result}
620+
defp upsert_comment_result({:ok, %{update_comment_flag: result}}), do: {:ok, result}
534621

535622
defp upsert_comment_result({:error, :create_comment, result, _steps}) do
536623
{:error, result}

lib/groupher_server/cms/post.ex

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ defmodule GroupherServer.CMS.Post do
1313
Embeds,
1414
Author,
1515
ArticleComment,
16+
ArticlePinedComment,
1617
Community,
1718
PostComment,
1819
PostCommunityFlag,
@@ -58,6 +59,7 @@ defmodule GroupherServer.CMS.Post do
5859
has_many(:comments, {"posts_comments", PostComment})
5960

6061
has_many(:article_comments, {"articles_comments", ArticleComment})
62+
has_many(:article_pined_comments, {"articles_pined_comments", ArticlePinedComment})
6163

6264
has_many(:favorites, {"posts_favorites", PostFavorite})
6365
has_many(:stars, {"posts_stars", PostStar})

lib/helper/types.ex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ defmodule Helper.Types do
2929
}
3030

3131
@type article_thread :: :post | :job
32+
@type article_common :: %{
33+
title: String.t()
34+
}
3235
@type article_info :: %{
3336
thread: article_thread,
3437
article: %{
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
defmodule GroupherServer.Repo.Migrations.AddPinToArticleComments do
2+
use Ecto.Migration
3+
4+
def change do
5+
alter table(:articles_comments) do
6+
add(:is_pined, :boolean, default: false)
7+
end
8+
9+
create table(:articles_pined_comments) do
10+
add(:post_id, references(:cms_posts, on_delete: :delete_all))
11+
add(:job_id, references(:cms_jobs, on_delete: :delete_all))
12+
13+
add(:article_comment_id, references(:articles_comments, on_delete: :delete_all), null: false)
14+
15+
timestamps()
16+
end
17+
18+
create(index(:articles_pined_comments, [:post_id]))
19+
create(index(:articles_pined_comments, [:job_id]))
20+
create(index(:articles_pined_comments, [:article_comment_id]))
21+
22+
create(unique_index(:articles_pined_comments, [:post_id, :job_id, :article_comment_id]))
23+
end
24+
end

0 commit comments

Comments
 (0)