Skip to content
This repository was archived by the owner on Nov 8, 2022. It is now read-only.
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
8 changes: 1 addition & 7 deletions lib/groupher_server/accounts/helper/loader.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule GroupherServer.Accounts.Helper.Loader do
import Ecto.Query, warn: false

alias Helper.QueryBuilder
alias GroupherServer.{Accounts, CMS, Repo}
alias GroupherServer.{CMS, Repo}

def data, do: Dataloader.Ecto.new(Repo, query: &query/2)

Expand All @@ -17,10 +17,4 @@ defmodule GroupherServer.Accounts.Helper.Loader do
end

def query(queryable, _args), do: queryable

defp count_contents(queryable) do
queryable
|> group_by([f], f.user_id)
|> select([f], count(f.id))
end
end
7 changes: 5 additions & 2 deletions lib/groupher_server/cms/article_comment.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ defmodule GroupherServer.CMS.ArticleComment do
@article_threads get_config(:article, :threads)

@required_fields ~w(body_html author_id)a
@optional_fields ~w(reply_to_id replies_count is_folded is_deleted floor is_article_author thread)a
@updatable_fields ~w(is_folded is_deleted floor upvotes_count is_pinned)a
@optional_fields ~w(reply_to_id replies_count is_folded is_deleted floor is_article_author thread is_for_question is_solution)a
@updatable_fields ~w(is_folded is_deleted floor upvotes_count is_pinned is_for_question is_solution)a

@article_fields @article_threads |> Enum.map(&:"#{&1}_id")

Expand Down Expand Up @@ -59,6 +59,9 @@ defmodule GroupherServer.CMS.ArticleComment do
# 楼层
field(:floor, :integer, default: 0)

field(:is_for_question, :boolean, default: false)
field(:is_solution, :boolean, default: false)

# 是否是评论文章的作者
field(:is_article_author, :boolean, default: false)
field(:upvotes_count, :integer, default: 0)
Expand Down
2 changes: 2 additions & 0 deletions lib/groupher_server/cms/cms.ex
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ defmodule GroupherServer.CMS do
defdelegate create_article_comment(thread, article_id, args, user), to: ArticleComment
defdelegate update_article_comment(comment, content), to: ArticleComment
defdelegate delete_article_comment(comment), to: ArticleComment
defdelegate mark_comment_solution(comment, user), to: ArticleComment
defdelegate undo_mark_comment_solution(comment, user), to: ArticleComment

defdelegate upvote_article_comment(comment_id, user), to: ArticleCommentAction
defdelegate undo_upvote_article_comment(comment_id, user), to: ArticleCommentAction
Expand Down
157 changes: 132 additions & 25 deletions lib/groupher_server/cms/delegates/article_comment.ex
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
CURD and operations for article comments
"""
import Ecto.Query, warn: false
import Helper.Utils, only: [done: 1, ensure: 2]
import Helper.Utils, only: [done: 1, ensure: 2, get_config: 2]
import Helper.ErrorCode

import GroupherServer.CMS.Delegate.Helper, only: [mark_viewer_emotion_states: 3]
Expand All @@ -13,11 +13,14 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
alias Helper.Types, as: T
alias Helper.{ORM, QueryBuilder}
alias GroupherServer.{Accounts, CMS, Repo}
alias CMS.Post

alias Accounts.User
alias CMS.{ArticleComment, ArticlePinnedComment, Embeds}
alias Ecto.Multi

@article_threads get_config(:article, :threads)

@max_participator_count ArticleComment.max_participator_count()
@default_emotions Embeds.ArticleCommentEmotion.default_emotions()
@delete_hint ArticleComment.delete_hint()
Expand Down Expand Up @@ -102,9 +105,10 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
|> Multi.run(:update_article_comments_count, fn _, %{create_article_comment: comment} ->
update_article_comments_count(comment, :inc)
end)
|> Multi.run(:add_participator, fn _, _ ->
add_participator_to_article(article, user)
|> Multi.run(:set_question_flag_ifneed, fn _, %{create_article_comment: comment} ->
set_question_flag_ifneed(article, comment)
end)
|> Multi.run(:add_participator, fn _, _ -> add_participator_to_article(article, user) end)
|> Multi.run(:update_article_active_timestamp, fn _, %{create_article_comment: comment} ->
case comment.author_id == article.author.user.id do
true -> {:ok, :pass}
Expand All @@ -130,10 +134,74 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
@doc """
update a comment for article like psot, job ...
"""
# 如果是 solution, 那么要更新对应的 post 的 solution_digest
def update_article_comment(%ArticleComment{is_solution: true} = article_comment, content) do
with {:ok, post} <- ORM.find(Post, article_comment.post_id) do
post |> ORM.update(%{solution_digest: content})
article_comment |> ORM.update(%{body_html: content})
end
end

def update_article_comment(%ArticleComment{} = article_comment, content) do
article_comment |> ORM.update(%{body_html: content})
end

@doc """
mark a comment as question post's best solution
"""
def mark_comment_solution(comment_id, user) do
with {:ok, article_comment} <- ORM.find(ArticleComment, comment_id),
{:ok, post} <- ORM.find(Post, article_comment.post_id, preload: [author: :user]) do
# 确保只有一个最佳答案
batch_update_solution_flag(post, false)
CMS.pin_article_comment(article_comment.id)
do_mark_comment_solution(post, article_comment, user, true)
end
end

@doc """
undo mark a comment as question post's best solution
"""
def undo_mark_comment_solution(comment_id, user) do
with {:ok, article_comment} <- ORM.find(ArticleComment, comment_id),
{:ok, post} <- ORM.find(Post, article_comment.post_id, preload: [author: :user]) do
do_mark_comment_solution(post, article_comment, user, false)
end
end

defp do_mark_comment_solution(post, %ArticleComment{} = article_comment, user, is_solution) do
# check if user is questioner
with true <- user.id == post.author.user.id do
Multi.new()
|> Multi.run(:mark_solution, fn _, _ ->
ORM.update(article_comment, %{is_solution: is_solution, is_for_question: true})
end)
|> Multi.run(:update_post_state, fn _, _ ->
ORM.update(post, %{is_solved: is_solution, solution_digest: article_comment.body_html})
end)
|> Repo.transaction()
|> result()
else
false -> raise_error(:require_questioner, "oops, questioner only")
{:error, error} -> {:error, error}
end
end

@doc """
batch update is_question flag for post-only article
"""
def batch_update_question_flag(%Post{is_question: is_question} = post) do
from(c in ArticleComment,
where: c.post_id == ^post.id,
update: [set: [is_for_question: ^is_question]]
)
|> Repo.update_all([])

{:ok, :pass}
end

def batch_update_question_flag(_), do: {:ok, :pass}

@doc "delete article comment"
def delete_article_comment(%ArticleComment{} = comment) do
Multi.new()
Expand All @@ -155,7 +223,9 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
%{article_comments_participators: article_comments_participators} = article,
%User{} = user
) do
total_participators = article_comments_participators |> List.insert_at(0, user) |> Enum.uniq()
total_participators =
article_comments_participators |> List.insert_at(0, user) |> Enum.uniq_by(& &1.id)

new_comment_participators = total_participators |> Enum.slice(0, @max_participator_count)
total_participators_count = length(total_participators)

Expand Down Expand Up @@ -225,10 +295,9 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
query
|> where(^thread_query)
|> where(^where_query)
# |> QueryBuilder.filter_pack(Map.merge(filters, %{sort: :asc_inserted}))
|> QueryBuilder.filter_pack(Map.merge(filters, %{sort: sort}))
|> ORM.paginater(~m(page size)a)
|> add_pined_comments_ifneed(thread, article_id, filters)
|> add_pinned_comments_ifneed(thread, article_id, filters)
|> mark_viewer_emotion_states(user, :comment)
|> mark_viewer_has_upvoted(user)
|> done()
Expand All @@ -250,37 +319,57 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
|> done()
end

defp add_pined_comments_ifneed(%{entries: entries} = paged_comments, thread, article_id, %{
page: 1
}) do
defp add_pinned_comments_ifneed(paged_comments, thread, article_id, %{page: 1}) do
with {:ok, info} <- match(thread),
query <-
from(p in ArticlePinnedComment,
join: c in ArticleComment,
on: p.article_comment_id == c.id,
where: field(p, ^info.foreign_key) == ^article_id,
select: c
),
{:ok, pined_comments} <- Repo.all(query) |> done() do
case pined_comments do
{:ok, pinned_comments} <- list_pinned_comments(info, article_id) do
case pinned_comments do
[] ->
paged_comments

_ ->
preloaded_pined_comments =
Enum.slice(pined_comments, 0, @pinned_comment_limit)
pinned_comments =
sort_solution_to_front(thread, pinned_comments)
|> Enum.slice(0, @pinned_comment_limit)
|> Repo.preload(reply_to: :author)

entries = Enum.concat(preloaded_pined_comments, entries)
pined_comment_count = length(pined_comments)
entries = pinned_comments ++ paged_comments.entries
pinned_comment_count = length(pinned_comments)

total_count = paged_comments.total_count + pined_comment_count
total_count = paged_comments.total_count + pinned_comment_count
paged_comments |> Map.merge(%{entries: entries, total_count: total_count})
end
end
end

defp add_pined_comments_ifneed(paged_comments, _thread, _article_id, _), do: paged_comments
defp add_pinned_comments_ifneed(paged_comments, _thread, _article_id, _), do: paged_comments

defp list_pinned_comments(%{foreign_key: foreign_key}, article_id) do
from(p in ArticlePinnedComment,
join: c in ArticleComment,
on: p.article_comment_id == c.id,
where: field(p, ^foreign_key) == ^article_id,
order_by: [desc: p.inserted_at],
select: c
)
|> Repo.all()
|> done
end

# only support post
defp sort_solution_to_front(:post, pinned_comments) do
solution_index = Enum.find_index(pinned_comments, & &1.is_solution)

case is_nil(solution_index) do
true ->
pinned_comments

false ->
{solution_comment, rest_comments} = List.pop_at(pinned_comments, solution_index)
[solution_comment] ++ rest_comments
end
end

defp sort_solution_to_front(_, pinned_comments), do: pinned_comments

defp mark_viewer_has_upvoted(paged_comments, nil), do: paged_comments

Expand All @@ -294,8 +383,26 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do
Map.merge(paged_comments, %{entries: entries})
end

defp result({:ok, %{create_article_comment: result}}), do: {:ok, result}
defp set_question_flag_ifneed(%{is_question: true} = article, %ArticleComment{} = comment) do
ORM.update(comment, %{is_for_question: true})
end

defp set_question_flag_ifneed(_, comment), do: ORM.update(comment, %{is_for_question: false})

# batch update is_solution flag for artilce comment
defp batch_update_solution_flag(%Post{} = post, is_question) do
from(c in ArticleComment,
where: c.post_id == ^post.id,
update: [set: [is_solution: ^is_question]]
)
|> Repo.update_all([])

{:ok, :pass}
end

defp result({:ok, %{set_question_flag_ifneed: result}}), do: {:ok, result}
defp result({:ok, %{delete_article_comment: result}}), do: {:ok, result}
defp result({:ok, %{mark_solution: result}}), do: {:ok, result}

defp result({:error, :create_article_comment, result, _steps}) do
raise_error(:create_comment, result)
Expand Down
1 change: 0 additions & 1 deletion lib/groupher_server/cms/delegates/article_community.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ defmodule GroupherServer.CMS.Delegate.ArticleCommunity do

alias Ecto.Multi

@default_article_meta Embeds.ArticleMeta.default_meta()
@max_pinned_article_count_per_thread Community.max_pinned_article_count_per_thread()

@spec pin_article(T.article_thread(), Integer.t(), Integer.t()) :: {:ok, PinnedArticle.t()}
Expand Down
11 changes: 8 additions & 3 deletions lib/groupher_server/cms/delegates/article_curd.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
alias Accounts.User
alias CMS.{Author, Community, PinnedArticle, Embeds, Delegate}

alias Delegate.{ArticleCommunity, ArticleTag, CommunityCURD}
alias Delegate.{ArticleCommunity, ArticleComment, ArticleTag, CommunityCURD}

alias Ecto.Multi

Expand Down Expand Up @@ -214,8 +214,13 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do
"""
def update_article(article, args) do
Multi.new()
|> Multi.run(:update_article, fn _, _ ->
ORM.update(article, args)
|> Multi.run(:update_article, fn _, _ -> ORM.update(article, args) end)
|> Multi.run(:update_comment_question_flag_if_need, fn _, %{update_article: update_article} ->
# 如果帖子的类型变了,那么 update 所有的 flag
case Map.has_key?(args, :is_question) do
true -> ArticleComment.batch_update_question_flag(update_article)
false -> {:ok, :pass}
end
end)
|> Multi.run(:update_edit_status, fn _, %{update_article: update_article} ->
ArticleCommunity.update_edit_status(update_article)
Expand Down
24 changes: 11 additions & 13 deletions lib/groupher_server/cms/embeds/article_comment_meta.ex
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,22 @@ defmodule GroupherServer.CMS.Embeds.ArticleCommentMeta do

import Ecto.Changeset

@optional_fields ~w(is_article_author_upvoted is_solution report_count is_reply_to_others reported_count reported_user_ids)a

@default_meta %{
is_article_author_upvoted: false,
is_solution: false,
is_reply_to_others: false,
report_count: 0,
upvoted_user_ids: [],
reported_user_ids: [],
reported_count: 0
}
@optional_fields ~w(is_article_author_upvoted report_count is_reply_to_others reported_count reported_user_ids)a

@doc "for test usage"
def default_meta(), do: @default_meta
def default_meta() do
%{
is_article_author_upvoted: false,
is_reply_to_others: false,
report_count: 0,
upvoted_user_ids: [],
reported_user_ids: [],
reported_count: 0
}
end

embedded_schema do
field(:is_article_author_upvoted, :boolean, default: false)
field(:is_solution, :boolean, default: false)
# used in replies mode, for those reply to other user in replies box (for frontend)
# 用于回复模式,指代这条回复是回复“回复列表其他人的” (方便前端展示)
field(:is_reply_to_others, :boolean, default: false)
Expand Down
7 changes: 6 additions & 1 deletion lib/groupher_server/cms/post.ex
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ defmodule GroupherServer.CMS.Post do

@required_fields ~w(title body digest length)a
@article_cast_fields general_article_fields(:cast)
@optional_fields ~w(link_addr copy_right link_addr link_icon)a ++ @article_cast_fields
@optional_fields ~w(link_addr copy_right link_addr link_icon is_question is_solved solution_digest)a ++
@article_cast_fields

@type t :: %Post{}
schema "cms_posts" do
Expand All @@ -29,6 +30,10 @@ defmodule GroupherServer.CMS.Post do
field(:copy_right, :string)
field(:length, :integer)

field(:is_question, :boolean, default: false)
field(:is_solved, :boolean, default: false)
field(:solution_digest, :string)

# TODO: remove after legacy data migrated
has_many(:comments, {"posts_comments", PostComment})

Expand Down
8 changes: 8 additions & 0 deletions lib/groupher_server_web/resolvers/cms_resolver.ex
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,14 @@ defmodule GroupherServerWeb.Resolvers.CMS do
CMS.undo_emotion_to_comment(id, emotion, user)
end

def mark_comment_solution(_root, ~m(id)a, %{context: %{cur_user: user}}) do
CMS.mark_comment_solution(id, user)
end

def undo_mark_comment_solution(_root, ~m(id)a, %{context: %{cur_user: user}}) do
CMS.undo_mark_comment_solution(id, user)
end

############
############
############
Expand Down
Loading