From 8e724776a710997dc9c4de085074c4d835f44274 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 8 May 2021 15:17:53 +0800 Subject: [PATCH 1/7] refactor(article-comment): wip --- lib/groupher_server/cms/cms.ex | 25 +- .../cms/delegates/article_comment.ex | 352 +------------- .../cms/delegates/article_comment_action.ex | 441 ++++++++++++++++++ .../cms/article_comment_replies_test.exs | 6 +- 4 files changed, 463 insertions(+), 361 deletions(-) create mode 100644 lib/groupher_server/cms/delegates/article_comment_action.ex diff --git a/lib/groupher_server/cms/cms.ex b/lib/groupher_server/cms/cms.ex index a689e80d4..c3e8b2562 100644 --- a/lib/groupher_server/cms/cms.ex +++ b/lib/groupher_server/cms/cms.ex @@ -13,6 +13,7 @@ defmodule GroupherServer.CMS do ArticleOperation, ArticleReaction, ArticleComment, + ArticleCommentAction, ArticleCommentEmotion, CommentCURD, CommunitySync, @@ -129,20 +130,24 @@ defmodule GroupherServer.CMS do defdelegate update_article_comment(comment, content), to: ArticleComment defdelegate delete_article_comment(comment), to: ArticleComment - defdelegate upvote_article_comment(comment_id, user), to: ArticleComment - defdelegate undo_upvote_article_comment(comment_id, user), to: ArticleComment - defdelegate reply_article_comment(comment_id, args, user), to: ArticleComment + defdelegate upvote_article_comment(comment_id, user), to: ArticleCommentAction + defdelegate undo_upvote_article_comment(comment_id, user), to: ArticleCommentAction + defdelegate reply_article_comment(comment_id, args, user), to: ArticleCommentAction - defdelegate pin_article_comment(comment_id), to: ArticleComment - defdelegate undo_pin_article_comment(comment_id), to: ArticleComment + defdelegate pin_article_comment(comment_id), to: ArticleCommentAction + defdelegate undo_pin_article_comment(comment_id), to: ArticleCommentAction + + defdelegate fold_article_comment(comment_id, user), to: ArticleCommentAction + defdelegate unfold_article_comment(comment_id, user), to: ArticleCommentAction + defdelegate report_article_comment(comment_id, user), to: ArticleCommentAction + defdelegate unreport_article_comment(comment_id, user), to: ArticleCommentAction defdelegate emotion_to_comment(comment_id, args, user), to: ArticleCommentEmotion defdelegate undo_emotion_to_comment(comment_id, args, user), to: ArticleCommentEmotion - defdelegate fold_article_comment(comment_id, user), to: ArticleComment - defdelegate unfold_article_comment(comment_id, user), to: ArticleComment - defdelegate report_article_comment(comment_id, user), to: ArticleComment - defdelegate unreport_article_comment(comment_id, user), to: ArticleComment - + ################### + ################### + ################### + ################### defdelegate create_comment(thread, content_id, args, user), to: CommentCURD defdelegate update_comment(thread, id, args, user), to: CommentCURD defdelegate delete_comment(thread, content_id), to: CommentCURD diff --git a/lib/groupher_server/cms/delegates/article_comment.ex b/lib/groupher_server/cms/delegates/article_comment.ex index 3f06b5bc2..55bf1d425 100644 --- a/lib/groupher_server/cms/delegates/article_comment.ex +++ b/lib/groupher_server/cms/delegates/article_comment.ex @@ -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, strip_struct: 1] + import Helper.Utils, only: [done: 1] import Helper.ErrorCode import GroupherServer.CMS.Utils.Matcher2 @@ -14,25 +14,14 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do alias GroupherServer.{Accounts, CMS, Repo} alias Accounts.User - - alias CMS.{ - ArticleComment, - ArticlePinedComment, - ArticleCommentUpvote, - ArticleCommentReply, - Embeds, - Post, - Job - } - + # TODO: why Post? + alias CMS.{ArticleComment, ArticlePinedComment, Embeds, Post} alias Ecto.Multi @max_participator_count ArticleComment.max_participator_count() - @max_parent_replies_count ArticleComment.max_parent_replies_count() @default_emotions Embeds.ArticleCommentEmotion.default_emotions() @supported_emotions ArticleComment.supported_emotions() @delete_hint ArticleComment.delete_hint() - @report_threshold_for_fold ArticleComment.report_threshold_for_fold() @default_comment_meta Embeds.ArticleCommentMeta.default_meta() @pined_comment_limit ArticleComment.pined_comment_limit() @@ -106,104 +95,6 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do end end - @spec pin_article_comment(Integer.t()) :: {:ok, ArticleComment.t()} - @doc "pin a comment" - def pin_article_comment(comment_id) do - with {:ok, comment} <- ORM.find(ArticleComment, comment_id), - {:ok, full_comment} <- get_full_comment(comment.id), - {:ok, info} <- match(full_comment.thread) do - Multi.new() - |> Multi.run(:checked_pined_comments_count, fn _, _ -> - count_query = - from(p in ArticlePinedComment, - where: field(p, ^info.foreign_key) == ^full_comment.article.id - ) - - pined_comments_count = Repo.aggregate(count_query, :count) - - case pined_comments_count >= @pined_comment_limit do - true -> {:error, "only support #{@pined_comment_limit} pined comment for each article"} - false -> {:ok, :pass} - end - end) - |> Multi.run(:update_comment_flag, fn _, _ -> - ORM.update(comment, %{is_pinned: true}) - end) - |> Multi.run(:add_pined_comment, fn _, _ -> - ArticlePinedComment - |> ORM.create( - %{article_comment_id: comment.id} - |> Map.put(info.foreign_key, full_comment.article.id) - ) - end) - |> Repo.transaction() - |> upsert_comment_result() - end - end - - def undo_pin_article_comment(comment_id) do - with {:ok, comment} <- ORM.find(ArticleComment, comment_id) do - Multi.new() - |> Multi.run(:update_comment_flag, fn _, _ -> - ORM.update(comment, %{is_pinned: false}) - end) - |> Multi.run(:remove_pined_comment, fn _, _ -> - ORM.findby_delete(ArticlePinedComment, %{article_comment_id: comment.id}) - end) - |> Repo.transaction() - |> upsert_comment_result() - end - end - - def fold_article_comment(%ArticleComment{} = comment, %User{} = _user) do - comment |> ORM.update(%{is_folded: true}) - end - - @doc "fold a comment" - def fold_article_comment(comment_id, %User{} = _user) do - with {:ok, comment} <- - ORM.find(ArticleComment, comment_id) do - comment |> ORM.update(%{is_folded: true}) - end - end - - @doc "fold a comment" - def unfold_article_comment(comment_id, %User{} = _user) do - with {:ok, comment} <- - ORM.find(ArticleComment, comment_id) do - comment |> ORM.update(%{is_folded: false}) - end - end - - @doc "fold a comment" - def report_article_comment(comment_id, %User{} = user) do - with {:ok, comment} <- - ORM.find(ArticleComment, comment_id) do - Multi.new() - |> Multi.run(:create_abuse_report, fn _, _ -> - CMS.create_report(:article_comment, comment_id, %{reason: "todo fucked"}, user) - end) - |> Multi.run(:update_report_flag, fn _, _ -> - ORM.update(comment, %{is_reported: true}) - end) - |> Multi.run(:fold_comment_report_too_many, fn _, %{create_abuse_report: abuse_report} -> - if abuse_report.report_cases_count >= @report_threshold_for_fold, - do: fold_article_comment(comment, user), - else: {:ok, comment} - end) - |> Repo.transaction() - |> upsert_comment_result() - end - end - - @doc "fold a comment" - def unreport_article_comment(comment_id, %User{} = _user) do - with {:ok, comment} <- - ORM.find(ArticleComment, comment_id) do - comment |> ORM.update(%{is_reported: false}) - end - end - @doc """ creates a comment for article like psot, job ... """ @@ -250,104 +141,6 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do |> upsert_comment_result() end - @doc "reply to exsiting comment" - def reply_article_comment(comment_id, content, %User{} = user) do - with {:ok, target_comment} <- - ORM.find_by(ArticleComment, %{id: comment_id, is_deleted: false}), - replying_comment <- Repo.preload(target_comment, reply_to: :author), - {thread, article} <- get_article(replying_comment), - {:ok, info} <- match(thread), - parent_comment <- get_parent_comment(replying_comment) do - Multi.new() - |> Multi.run(:create_reply_comment, fn _, _ -> - do_create_comment(content, info.foreign_key, article, user) - end) - |> Multi.run(:update_article_comments_count, fn _, - %{create_reply_comment: replyed_comment} -> - update_article_comments_count(replyed_comment, :inc) - end) - |> Multi.run(:create_article_comment_reply, fn _, - %{create_reply_comment: replyed_comment} -> - ArticleCommentReply - |> ORM.create(%{article_comment_id: replyed_comment.id, reply_to_id: replying_comment.id}) - end) - |> Multi.run(:inc_replies_count, fn _, _ -> - ORM.inc_field(ArticleComment, replying_comment, :replies_count) - end) - |> Multi.run(:add_replies_ifneed, fn _, %{create_reply_comment: replyed_comment} -> - add_replies_ifneed(parent_comment, replyed_comment) - end) - |> Multi.run(:add_participator, fn _, _ -> - add_participator_to_article(article, user) - end) - |> Multi.run(:set_meta_flag, fn _, %{create_reply_comment: replyed_comment} -> - update_reply_to_others_state(parent_comment, replying_comment, replyed_comment) - end) - |> Multi.run(:add_reply_to, fn _, %{create_reply_comment: replyed_comment} -> - replyed_comment - |> Repo.preload(:reply_to) - |> Ecto.Changeset.change() - |> Ecto.Changeset.put_assoc(:reply_to, replying_comment) - |> Repo.update() - end) - |> Repo.transaction() - |> upsert_comment_result() - end - end - - @doc "upvote a comment" - def upvote_article_comment(comment_id, %User{id: user_id}) do - with {:ok, comment} <- ORM.find(ArticleComment, comment_id), - false <- comment.is_deleted do - # TODO: is user upvoted before? - Multi.new() - |> Multi.run(:create_comment_upvote, fn _, _ -> - ORM.create(ArticleCommentUpvote, %{article_comment_id: comment.id, user_id: user_id}) - end) - |> Multi.run(:add_upvoted_user, fn _, _ -> - update_upvoted_user_list(comment, user_id, :add) - end) - |> Multi.run(:inc_upvotes_count, fn _, %{add_upvoted_user: comment} -> - count_query = from(c in ArticleCommentUpvote, where: c.article_comment_id == ^comment.id) - upvotes_count = Repo.aggregate(count_query, :count) - ORM.update(comment, %{upvotes_count: upvotes_count}) - end) - |> Multi.run(:check_article_author_upvoted, fn _, %{inc_upvotes_count: comment} -> - update_article_author_upvoted_info(comment, user_id) - end) - |> Repo.transaction() - |> upsert_comment_result() - end - end - - @doc "upvote a comment" - def undo_upvote_article_comment(comment_id, %User{id: user_id}) do - with {:ok, comment} <- ORM.find(ArticleComment, comment_id), - false <- comment.is_deleted do - Multi.new() - |> Multi.run(:delete_comment_upvote, fn _, _ -> - ORM.findby_delete(ArticleCommentUpvote, %{ - article_comment_id: comment.id, - user_id: user_id - }) - end) - |> Multi.run(:remove_upvoted_user, fn _, _ -> - update_upvoted_user_list(comment, user_id, :remove) - end) - |> Multi.run(:desc_upvotes_count, fn _, %{remove_upvoted_user: comment} -> - count_query = from(c in ArticleCommentUpvote, where: c.article_comment_id == ^comment_id) - upvotes_count = Repo.aggregate(count_query, :count) - - ORM.update(comment, %{upvotes_count: Enum.max([upvotes_count - 1, 0])}) - end) - |> Multi.run(:check_article_author_upvoted, fn _, %{desc_upvotes_count: updated_comment} -> - update_article_author_upvoted_info(updated_comment, user_id) - end) - |> Repo.transaction() - |> upsert_comment_result() - end - end - defp do_list_article_comment(thread, article_id, filters, where_query, user) do %{page: page, size: size} = filters @@ -417,19 +210,6 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do defp add_pined_comments_ifneed(paged_comments, _thread, _article_id, _), do: paged_comments - defp update_article_author_upvoted_info(%ArticleComment{} = comment, user_id) do - with {:ok, article} = get_full_comment(comment.id) do - is_article_author_upvoted = article.author.id == user_id - - new_meta = - comment.meta - |> Map.from_struct() - |> Map.merge(%{is_article_author_upvoted: is_article_author_upvoted}) - - comment |> ORM.update(%{meta: new_meta}) - end - end - # update comment's parent article's comments total count @spec update_article_comments_count(ArticleComment.t(), :inc | :dec) :: ArticleComment.t() defp update_article_comments_count(%ArticleComment{} = comment, opt) do @@ -474,39 +254,6 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do ) end - # 设计盖楼只保留一个层级,回复楼中的评论都会被放到顶楼的 replies 中 - defp get_parent_comment(%ArticleComment{reply_to_id: nil} = comment) do - comment - end - - defp get_parent_comment(%ArticleComment{reply_to_id: reply_to_id} = comment) - when not is_nil(reply_to_id) do - get_parent_comment(Repo.preload(comment.reply_to, reply_to: :author)) - end - - # 如果 replies 没有达到 @max_parent_replies_count, 则添加 - # "加载更多" 的逻辑使用另外的 paged 接口从 ArticleCommentReply 表中查询 - defp add_replies_ifneed( - %ArticleComment{replies: replies} = parent_comment, - %ArticleComment{} = replyed_comment - ) - when length(replies) < @max_parent_replies_count do - new_replies = - replies - |> List.insert_at(length(replies), replyed_comment) - |> Enum.slice(0, @max_parent_replies_count) - - parent_comment - |> Ecto.Changeset.change() - |> Ecto.Changeset.put_embed(:replies, new_replies) - |> Repo.update() - end - - # 如果已经有 @max_parent_replies_count 以上的回复了,直接忽略即可 - defp add_replies_ifneed(%ArticleComment{} = parent_comment, _) do - {:ok, parent_comment} - end - # add participator to article-like content (Post, Job ...) and update count defp add_participator_to_article(%Post{} = article, %User{} = user) do total_participators = @@ -515,7 +262,6 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do |> Enum.uniq() new_comment_participators = total_participators |> Enum.slice(0, @max_participator_count) - total_participators_count = length(total_participators) article @@ -563,91 +309,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do Map.merge(paged_comments, %{entries: entries}) end - defp get_article(%ArticleComment{post_id: post_id} = comment) when not is_nil(post_id) do - with {:ok, article} <- ORM.find(Post, comment.post_id, preload: [author: :user]) do - {:post, article} - end - end - - defp get_article(%ArticleComment{job_id: job_id} = comment) when not is_nil(job_id) do - with {:ok, article} <- ORM.find(Job, comment.job_id, preload: [author: :user]) do - {:job, article} - end - end - - @spec get_full_comment(String.t()) :: {:ok, T.article_info()} | {:error, nil} - defp get_full_comment(comment_id) do - query = from(c in ArticleComment, where: c.id == ^comment_id, preload: :post, preload: :job) - - with {:ok, comment} <- Repo.one(query) |> done() do - extract_article_info(comment) - end - end - - defp extract_article_info(%ArticleComment{post: %Post{} = post}) when not is_nil(post) do - do_extract_article_info(:post, post) - end - - defp extract_article_info(%ArticleComment{job: %Job{} = job}) when not is_nil(job) do - do_extract_article_info(:job, job) - end - - @spec do_extract_article_info(T.article_thread(), T.article_common()) :: {:ok, T.article_info()} - defp do_extract_article_info(thread, article) do - with {:ok, article_with_author} <- Repo.preload(article, author: :user) |> done(), - article_author <- get_in(article_with_author, [:author, :user]) do - # - article_info = %{title: article.title, id: article.id} - - author_info = %{ - id: article_author.id, - login: article_author.login, - nickname: article_author.nickname - } - - {:ok, %{thread: thread, article: article_info, author: author_info}} - end - end - - # used in replies mode, for those reply to other user in replies box (for frontend) - # 用于回复模式,指代这条回复是回复“回复列表其他人的” (方便前端展示) - defp update_reply_to_others_state(parent_comment, replying_comment, replyed_comment) do - replying_comment = replying_comment |> Repo.preload(:author) - parent_comment = parent_comment |> Repo.preload(:author) - is_reply_to_others = parent_comment.author.id !== replying_comment.author.id - - case is_reply_to_others do - true -> - new_meta = - replyed_comment.meta - |> Map.from_struct() - |> Map.merge(%{is_reply_to_others: is_reply_to_others}) - - ORM.update(replyed_comment, %{meta: new_meta}) - - false -> - {:ok, :pass} - end - end - - defp update_upvoted_user_list(comment, user_id, opt) do - cur_user_ids = get_in(comment, [:meta, :upvoted_user_ids]) - - user_ids = - case opt do - :add -> [user_id] ++ cur_user_ids - :remove -> cur_user_ids -- [user_id] - end - - meta = comment.meta |> Map.merge(%{upvoted_user_ids: user_ids}) |> strip_struct - ORM.update_meta(comment, meta) - end - defp upsert_comment_result({:ok, %{create_article_comment: result}}), do: {:ok, result} - defp upsert_comment_result({:ok, %{add_reply_to: result}}), do: {:ok, result} - defp upsert_comment_result({:ok, %{check_article_author_upvoted: result}}), do: {:ok, result} - defp upsert_comment_result({:ok, %{update_report_flag: result}}), do: {:ok, result} - defp upsert_comment_result({:ok, %{update_comment_flag: result}}), do: {:ok, result} defp upsert_comment_result({:ok, %{delete_article_comment: result}}), do: {:ok, result} defp upsert_comment_result({:error, :create_article_comment, result, _steps}) do @@ -658,11 +320,5 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do {:error, result} end - defp upsert_comment_result({:error, :create_abuse_report, result, _steps}) do - {:error, result} - end - - defp upsert_comment_result({:error, _, result, _steps}) do - {:error, result} - end + defp upsert_comment_result({:error, _, result, _steps}), do: {:error, result} end diff --git a/lib/groupher_server/cms/delegates/article_comment_action.ex b/lib/groupher_server/cms/delegates/article_comment_action.ex new file mode 100644 index 000000000..3879ada88 --- /dev/null +++ b/lib/groupher_server/cms/delegates/article_comment_action.ex @@ -0,0 +1,441 @@ +defmodule GroupherServer.CMS.Delegate.ArticleCommentAction do + @moduledoc """ + CURD and operations for article comments + """ + import Ecto.Query, warn: false + import Helper.Utils, only: [done: 1, strip_struct: 1] + import Helper.ErrorCode + + import GroupherServer.CMS.Utils.Matcher2 + + alias Helper.Types, as: T + alias Helper.ORM + alias GroupherServer.{Accounts, CMS, Repo} + + alias Accounts.User + + alias CMS.{ + ArticleComment, + ArticlePinedComment, + ArticleCommentUpvote, + ArticleCommentReply, + Embeds, + Post, + Job + } + + alias Ecto.Multi + + @max_participator_count ArticleComment.max_participator_count() + @max_parent_replies_count ArticleComment.max_parent_replies_count() + @default_emotions Embeds.ArticleCommentEmotion.default_emotions() + @report_threshold_for_fold ArticleComment.report_threshold_for_fold() + + @default_comment_meta Embeds.ArticleCommentMeta.default_meta() + @pined_comment_limit ArticleComment.pined_comment_limit() + + @spec pin_article_comment(Integer.t()) :: {:ok, ArticleComment.t()} + @doc "pin a comment" + def pin_article_comment(comment_id) do + with {:ok, comment} <- ORM.find(ArticleComment, comment_id), + {:ok, full_comment} <- get_full_comment(comment.id), + {:ok, info} <- match(full_comment.thread) do + Multi.new() + |> Multi.run(:checked_pined_comments_count, fn _, _ -> + count_query = + from(p in ArticlePinedComment, + where: field(p, ^info.foreign_key) == ^full_comment.article.id + ) + + pined_comments_count = Repo.aggregate(count_query, :count) + + case pined_comments_count >= @pined_comment_limit do + true -> {:error, "only support #{@pined_comment_limit} pined comment for each article"} + false -> {:ok, :pass} + end + end) + |> Multi.run(:update_comment_flag, fn _, _ -> + ORM.update(comment, %{is_pinned: true}) + end) + |> Multi.run(:add_pined_comment, fn _, _ -> + ArticlePinedComment + |> ORM.create( + %{article_comment_id: comment.id} + |> Map.put(info.foreign_key, full_comment.article.id) + ) + end) + |> Repo.transaction() + |> upsert_comment_result() + end + end + + def undo_pin_article_comment(comment_id) do + with {:ok, comment} <- ORM.find(ArticleComment, comment_id) do + Multi.new() + |> Multi.run(:update_comment_flag, fn _, _ -> + ORM.update(comment, %{is_pinned: false}) + end) + |> Multi.run(:remove_pined_comment, fn _, _ -> + ORM.findby_delete(ArticlePinedComment, %{article_comment_id: comment.id}) + end) + |> Repo.transaction() + |> upsert_comment_result() + end + end + + def fold_article_comment(%ArticleComment{} = comment, %User{} = _user) do + comment |> ORM.update(%{is_folded: true}) + end + + @doc "fold a comment" + def fold_article_comment(comment_id, %User{} = _user) do + with {:ok, comment} <- ORM.find(ArticleComment, comment_id) do + comment |> ORM.update(%{is_folded: true}) + end + end + + @doc "fold a comment" + def unfold_article_comment(comment_id, %User{} = _user) do + with {:ok, comment} <- ORM.find(ArticleComment, comment_id) do + comment |> ORM.update(%{is_folded: false}) + end + end + + @doc "fold a comment" + def report_article_comment(comment_id, %User{} = user) do + with {:ok, comment} <- + ORM.find(ArticleComment, comment_id) do + Multi.new() + |> Multi.run(:create_abuse_report, fn _, _ -> + CMS.create_report(:article_comment, comment_id, %{reason: "todo fucked"}, user) + end) + |> Multi.run(:update_report_flag, fn _, _ -> + ORM.update(comment, %{is_reported: true}) + end) + |> Multi.run(:fold_comment_report_too_many, fn _, %{create_abuse_report: abuse_report} -> + if abuse_report.report_cases_count >= @report_threshold_for_fold, + do: fold_article_comment(comment, user), + else: {:ok, comment} + end) + |> Repo.transaction() + |> upsert_comment_result() + end + end + + @doc "fold a comment" + def unreport_article_comment(comment_id, %User{} = _user) do + with {:ok, comment} <- + ORM.find(ArticleComment, comment_id) do + comment |> ORM.update(%{is_reported: false}) + end + end + + @doc "reply to exsiting comment" + def reply_article_comment(comment_id, content, %User{} = user) do + with {:ok, target_comment} <- + ORM.find_by(ArticleComment, %{id: comment_id, is_deleted: false}), + replying_comment <- Repo.preload(target_comment, reply_to: :author), + {thread, article} <- get_article(replying_comment), + {:ok, info} <- match(thread), + parent_comment <- get_parent_comment(replying_comment) do + Multi.new() + |> Multi.run(:create_reply_comment, fn _, _ -> + do_create_comment(content, info.foreign_key, article, user) + end) + |> Multi.run(:update_article_comments_count, fn _, + %{create_reply_comment: replyed_comment} -> + update_article_comments_count(replyed_comment, :inc) + end) + |> Multi.run(:create_article_comment_reply, fn _, + %{create_reply_comment: replyed_comment} -> + ArticleCommentReply + |> ORM.create(%{article_comment_id: replyed_comment.id, reply_to_id: replying_comment.id}) + end) + |> Multi.run(:inc_replies_count, fn _, _ -> + ORM.inc_field(ArticleComment, replying_comment, :replies_count) + end) + |> Multi.run(:add_replies_ifneed, fn _, %{create_reply_comment: replyed_comment} -> + add_replies_ifneed(parent_comment, replyed_comment) + end) + |> Multi.run(:add_participator, fn _, _ -> + add_participator_to_article(article, user) + end) + |> Multi.run(:set_meta_flag, fn _, %{create_reply_comment: replyed_comment} -> + update_reply_to_others_state(parent_comment, replying_comment, replyed_comment) + end) + |> Multi.run(:add_reply_to, fn _, %{create_reply_comment: replyed_comment} -> + replyed_comment + |> Repo.preload(:reply_to) + |> Ecto.Changeset.change() + |> Ecto.Changeset.put_assoc(:reply_to, replying_comment) + |> Repo.update() + end) + |> Repo.transaction() + |> upsert_comment_result() + end + end + + @doc "upvote a comment" + def upvote_article_comment(comment_id, %User{id: user_id}) do + with {:ok, comment} <- ORM.find(ArticleComment, comment_id), + false <- comment.is_deleted do + # TODO: is user upvoted before? + Multi.new() + |> Multi.run(:create_comment_upvote, fn _, _ -> + ORM.create(ArticleCommentUpvote, %{article_comment_id: comment.id, user_id: user_id}) + end) + |> Multi.run(:add_upvoted_user, fn _, _ -> + update_upvoted_user_list(comment, user_id, :add) + end) + |> Multi.run(:inc_upvotes_count, fn _, %{add_upvoted_user: comment} -> + count_query = from(c in ArticleCommentUpvote, where: c.article_comment_id == ^comment.id) + upvotes_count = Repo.aggregate(count_query, :count) + ORM.update(comment, %{upvotes_count: upvotes_count}) + end) + |> Multi.run(:check_article_author_upvoted, fn _, %{inc_upvotes_count: comment} -> + update_article_author_upvoted_info(comment, user_id) + end) + |> Repo.transaction() + |> upsert_comment_result() + end + end + + @doc "upvote a comment" + def undo_upvote_article_comment(comment_id, %User{id: user_id}) do + with {:ok, comment} <- ORM.find(ArticleComment, comment_id), + false <- comment.is_deleted do + Multi.new() + |> Multi.run(:delete_comment_upvote, fn _, _ -> + ORM.findby_delete(ArticleCommentUpvote, %{ + article_comment_id: comment.id, + user_id: user_id + }) + end) + |> Multi.run(:remove_upvoted_user, fn _, _ -> + update_upvoted_user_list(comment, user_id, :remove) + end) + |> Multi.run(:desc_upvotes_count, fn _, %{remove_upvoted_user: comment} -> + count_query = from(c in ArticleCommentUpvote, where: c.article_comment_id == ^comment_id) + upvotes_count = Repo.aggregate(count_query, :count) + + ORM.update(comment, %{upvotes_count: Enum.max([upvotes_count - 1, 0])}) + end) + |> Multi.run(:check_article_author_upvoted, fn _, %{desc_upvotes_count: updated_comment} -> + update_article_author_upvoted_info(updated_comment, user_id) + end) + |> Repo.transaction() + |> upsert_comment_result() + end + end + + defp update_article_author_upvoted_info(%ArticleComment{} = comment, user_id) do + with {:ok, article} = get_full_comment(comment.id) do + is_article_author_upvoted = article.author.id == user_id + + new_meta = + comment.meta + |> Map.from_struct() + |> Map.merge(%{is_article_author_upvoted: is_article_author_upvoted}) + + comment |> ORM.update(%{meta: new_meta}) + end + end + + # update comment's parent article's comments total count + @spec update_article_comments_count(ArticleComment.t(), :inc | :dec) :: ArticleComment.t() + defp update_article_comments_count(%ArticleComment{} = comment, opt) do + with {:ok, article_info} <- match(:comment_article, comment), + {:ok, article} <- ORM.find(article_info.model, article_info.id) do + count_query = + from(c in ArticleComment, where: field(c, ^article_info.foreign_key) == ^article_info.id) + + cur_count = Repo.aggregate(count_query, :count) + + # dec 是 comment 还没有删除的时候的操作,和 inc 不同 + # 因为 dec 操作如果放在 delete 后面,那么 update 会失败 + case opt do + :inc -> ORM.update(article, %{article_comments_count: cur_count}) + :dec -> ORM.update(article, %{article_comments_count: Enum.max([1, cur_count]) - 1}) + end + end + end + + # creat article comment for parent or reply + # set floor + # TODO: parse editor-json + # set default emotions + defp do_create_comment(content, foreign_key, article, %User{id: user_id}) do + count_query = from(c in ArticleComment, where: field(c, ^foreign_key) == ^article.id) + floor = Repo.aggregate(count_query, :count) + 1 + + ArticleComment + |> ORM.create( + Map.put( + %{ + author_id: user_id, + body_html: content, + emotions: @default_emotions, + floor: floor, + is_article_author: user_id == article.author.user.id, + meta: @default_comment_meta + }, + foreign_key, + article.id + ) + ) + end + + # 设计盖楼只保留一个层级,回复楼中的评论都会被放到顶楼的 replies 中 + defp get_parent_comment(%ArticleComment{reply_to_id: nil} = comment) do + comment + end + + defp get_parent_comment(%ArticleComment{reply_to_id: reply_to_id} = comment) + when not is_nil(reply_to_id) do + get_parent_comment(Repo.preload(comment.reply_to, reply_to: :author)) + end + + # 如果 replies 没有达到 @max_parent_replies_count, 则添加 + # "加载更多" 的逻辑使用另外的 paged 接口从 ArticleCommentReply 表中查询 + defp add_replies_ifneed( + %ArticleComment{replies: replies} = parent_comment, + %ArticleComment{} = replyed_comment + ) + when length(replies) < @max_parent_replies_count do + new_replies = + replies + |> List.insert_at(length(replies), replyed_comment) + |> Enum.slice(0, @max_parent_replies_count) + + parent_comment + |> Ecto.Changeset.change() + |> Ecto.Changeset.put_embed(:replies, new_replies) + |> Repo.update() + end + + # 如果已经有 @max_parent_replies_count 以上的回复了,直接忽略即可 + defp add_replies_ifneed(%ArticleComment{} = parent_comment, _) do + {:ok, parent_comment} + end + + # add participator to article-like content (Post, Job ...) and update count + defp add_participator_to_article(%Post{} = article, %User{} = user) do + total_participators = + article.article_comments_participators + |> List.insert_at(0, user) + |> Enum.uniq() + + new_comment_participators = total_participators |> Enum.slice(0, @max_participator_count) + + total_participators_count = length(total_participators) + + article + |> Ecto.Changeset.change() + |> Ecto.Changeset.put_change(:article_comments_participators_count, total_participators_count) + |> Ecto.Changeset.put_embed(:article_comments_participators, new_comment_participators) + |> Repo.update() + end + + defp add_participator_to_article(_, _), do: {:ok, :pass} + + defp get_article(%ArticleComment{post_id: post_id} = comment) when not is_nil(post_id) do + with {:ok, article} <- ORM.find(Post, comment.post_id, preload: [author: :user]) do + {:post, article} + end + end + + defp get_article(%ArticleComment{job_id: job_id} = comment) when not is_nil(job_id) do + with {:ok, article} <- ORM.find(Job, comment.job_id, preload: [author: :user]) do + {:job, article} + end + end + + @spec get_full_comment(String.t()) :: {:ok, T.article_info()} | {:error, nil} + defp get_full_comment(comment_id) do + query = from(c in ArticleComment, where: c.id == ^comment_id, preload: :post, preload: :job) + + with {:ok, comment} <- Repo.one(query) |> done() do + extract_article_info(comment) + end + end + + defp extract_article_info(%ArticleComment{post: %Post{} = post}) when not is_nil(post) do + do_extract_article_info(:post, post) + end + + defp extract_article_info(%ArticleComment{job: %Job{} = job}) when not is_nil(job) do + do_extract_article_info(:job, job) + end + + @spec do_extract_article_info(T.article_thread(), T.article_common()) :: {:ok, T.article_info()} + defp do_extract_article_info(thread, article) do + with {:ok, article_with_author} <- Repo.preload(article, author: :user) |> done(), + article_author <- get_in(article_with_author, [:author, :user]) do + # + article_info = %{title: article.title, id: article.id} + + author_info = %{ + id: article_author.id, + login: article_author.login, + nickname: article_author.nickname + } + + {:ok, %{thread: thread, article: article_info, author: author_info}} + end + end + + # used in replies mode, for those reply to other user in replies box (for frontend) + # 用于回复模式,指代这条回复是回复“回复列表其他人的” (方便前端展示) + defp update_reply_to_others_state(parent_comment, replying_comment, replyed_comment) do + replying_comment = replying_comment |> Repo.preload(:author) + parent_comment = parent_comment |> Repo.preload(:author) + is_reply_to_others = parent_comment.author.id !== replying_comment.author.id + + case is_reply_to_others do + true -> + new_meta = + replyed_comment.meta + |> Map.from_struct() + |> Map.merge(%{is_reply_to_others: is_reply_to_others}) + + ORM.update(replyed_comment, %{meta: new_meta}) + + false -> + {:ok, :pass} + end + end + + defp update_upvoted_user_list(comment, user_id, opt) do + cur_user_ids = get_in(comment, [:meta, :upvoted_user_ids]) + + user_ids = + case opt do + :add -> [user_id] ++ cur_user_ids + :remove -> cur_user_ids -- [user_id] + end + + meta = comment.meta |> Map.merge(%{upvoted_user_ids: user_ids}) |> strip_struct + ORM.update_meta(comment, meta) + end + + defp upsert_comment_result({:ok, %{create_article_comment: result}}), do: {:ok, result} + defp upsert_comment_result({:ok, %{add_reply_to: result}}), do: {:ok, result} + defp upsert_comment_result({:ok, %{check_article_author_upvoted: result}}), do: {:ok, result} + defp upsert_comment_result({:ok, %{update_report_flag: result}}), do: {:ok, result} + defp upsert_comment_result({:ok, %{update_comment_flag: result}}), do: {:ok, result} + defp upsert_comment_result({:ok, %{delete_article_comment: result}}), do: {:ok, result} + + defp upsert_comment_result({:error, :create_article_comment, result, _steps}) do + raise_error(:create_comment, result) + end + + defp upsert_comment_result({:error, :add_participator, result, _steps}) do + {:error, result} + end + + defp upsert_comment_result({:error, :create_abuse_report, result, _steps}) do + {:error, result} + end + + defp upsert_comment_result({:error, _, result, _steps}), do: {:error, result} +end diff --git a/test/groupher_server/cms/article_comment_replies_test.exs b/test/groupher_server/cms/article_comment_replies_test.exs index 9d9c8ce1d..09c39f5f8 100644 --- a/test/groupher_server/cms/article_comment_replies_test.exs +++ b/test/groupher_server/cms/article_comment_replies_test.exs @@ -65,7 +65,7 @@ defmodule GroupherServer.Test.CMS.ArticleCommentReplies do assert exist_in?(replyed_comment_2, parent_comment.replies) end - @tag :wip + @tag :wip2 test "reply to reply inside a comment should belong same parent comment", ~m(post user user2)a do {:ok, parent_comment} = CMS.create_article_comment(:post, post.id, "parent comment", user) @@ -91,7 +91,7 @@ defmodule GroupherServer.Test.CMS.ArticleCommentReplies do assert replyed_comment_3.reply_to_id == replyed_comment_2.id end - @tag :wip + @tag :wip2 test "reply to reply inside a comment should have is_reply_to_others flag in meta", ~m(post user user2)a do {:ok, parent_comment} = CMS.create_article_comment(:post, post.id, "parent comment", user) @@ -111,7 +111,7 @@ defmodule GroupherServer.Test.CMS.ArticleCommentReplies do assert replyed_comment_3.meta.is_reply_to_others end - @tag :wip + @tag :wip2 test "comment replies only contains @max_parent_replies_count replies", ~m(post user)a do total_reply_count = @max_parent_replies_count + 1 From c55ddbaf43728dddc27ab92f75d712f816e06425 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 8 May 2021 15:38:48 +0800 Subject: [PATCH 2/7] refactor(article-comment): wip --- .../cms/delegates/article_comment.ex | 128 +++++++++--------- .../cms/delegates/article_comment_action.ex | 71 +--------- lib/groupher_server/cms/job.ex | 4 +- ..._article_comments_participators_to_job.exs | 9 ++ 4 files changed, 78 insertions(+), 134 deletions(-) create mode 100644 priv/repo/migrations/20210508072209_add_article_comments_participators_to_job.exs diff --git a/lib/groupher_server/cms/delegates/article_comment.ex b/lib/groupher_server/cms/delegates/article_comment.ex index 55bf1d425..9bee1f4d3 100644 --- a/lib/groupher_server/cms/delegates/article_comment.ex +++ b/lib/groupher_server/cms/delegates/article_comment.ex @@ -14,8 +14,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do alias GroupherServer.{Accounts, CMS, Repo} alias Accounts.User - # TODO: why Post? - alias CMS.{ArticleComment, ArticlePinedComment, Embeds, Post} + alias CMS.{ArticleComment, ArticlePinedComment, Embeds} alias Ecto.Multi @max_participator_count ArticleComment.max_participator_count() @@ -141,6 +140,68 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do |> upsert_comment_result() end + # add participator to article-like content (Post, Job ...) and update count + def add_participator_to_article( + %{article_comments_participators: article_comments_participators} = article, + %User{} = user + ) do + total_participators = article_comments_participators |> List.insert_at(0, user) |> Enum.uniq() + new_comment_participators = total_participators |> Enum.slice(0, @max_participator_count) + total_participators_count = length(total_participators) + + article + |> Ecto.Changeset.change() + |> Ecto.Changeset.put_change(:article_comments_participators_count, total_participators_count) + |> Ecto.Changeset.put_embed(:article_comments_participators, new_comment_participators) + |> Repo.update() + end + + def add_participator_to_article(_, _), do: {:ok, :pass} + + # update comment's parent article's comments total count + @spec update_article_comments_count(ArticleComment.t(), :inc | :dec) :: ArticleComment.t() + def update_article_comments_count(%ArticleComment{} = comment, opt) do + with {:ok, article_info} <- match(:comment_article, comment), + {:ok, article} <- ORM.find(article_info.model, article_info.id) do + count_query = + from(c in ArticleComment, where: field(c, ^article_info.foreign_key) == ^article_info.id) + + cur_count = Repo.aggregate(count_query, :count) + + # dec 是 comment 还没有删除的时候的操作,和 inc 不同 + # 因为 dec 操作如果放在 delete 后面,那么 update 会失败 + case opt do + :inc -> ORM.update(article, %{article_comments_count: cur_count}) + :dec -> ORM.update(article, %{article_comments_count: Enum.max([1, cur_count]) - 1}) + end + end + end + + # creat article comment for parent or reply + # set floor + # TODO: parse editor-json + # set default emotions + def do_create_comment(content, foreign_key, article, %User{id: user_id}) do + count_query = from(c in ArticleComment, where: field(c, ^foreign_key) == ^article.id) + floor = Repo.aggregate(count_query, :count) + 1 + + ArticleComment + |> ORM.create( + Map.put( + %{ + author_id: user_id, + body_html: content, + emotions: @default_emotions, + floor: floor, + is_article_author: user_id == article.author.user.id, + meta: @default_comment_meta + }, + foreign_key, + article.id + ) + ) + end + defp do_list_article_comment(thread, article_id, filters, where_query, user) do %{page: page, size: size} = filters @@ -210,69 +271,6 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do defp add_pined_comments_ifneed(paged_comments, _thread, _article_id, _), do: paged_comments - # update comment's parent article's comments total count - @spec update_article_comments_count(ArticleComment.t(), :inc | :dec) :: ArticleComment.t() - defp update_article_comments_count(%ArticleComment{} = comment, opt) do - with {:ok, article_info} <- match(:comment_article, comment), - {:ok, article} <- ORM.find(article_info.model, article_info.id) do - count_query = - from(c in ArticleComment, where: field(c, ^article_info.foreign_key) == ^article_info.id) - - cur_count = Repo.aggregate(count_query, :count) - - # dec 是 comment 还没有删除的时候的操作,和 inc 不同 - # 因为 dec 操作如果放在 delete 后面,那么 update 会失败 - case opt do - :inc -> ORM.update(article, %{article_comments_count: cur_count}) - :dec -> ORM.update(article, %{article_comments_count: Enum.max([1, cur_count]) - 1}) - end - end - end - - # creat article comment for parent or reply - # set floor - # TODO: parse editor-json - # set default emotions - defp do_create_comment(content, foreign_key, article, %User{id: user_id}) do - count_query = from(c in ArticleComment, where: field(c, ^foreign_key) == ^article.id) - floor = Repo.aggregate(count_query, :count) + 1 - - ArticleComment - |> ORM.create( - Map.put( - %{ - author_id: user_id, - body_html: content, - emotions: @default_emotions, - floor: floor, - is_article_author: user_id == article.author.user.id, - meta: @default_comment_meta - }, - foreign_key, - article.id - ) - ) - end - - # add participator to article-like content (Post, Job ...) and update count - defp add_participator_to_article(%Post{} = article, %User{} = user) do - total_participators = - article.article_comments_participators - |> List.insert_at(0, user) - |> Enum.uniq() - - new_comment_participators = total_participators |> Enum.slice(0, @max_participator_count) - total_participators_count = length(total_participators) - - article - |> Ecto.Changeset.change() - |> Ecto.Changeset.put_change(:article_comments_participators_count, total_participators_count) - |> Ecto.Changeset.put_embed(:article_comments_participators, new_comment_participators) - |> Repo.update() - end - - defp add_participator_to_article(_, _), do: {:ok, :pass} - defp user_in_logins?([], _), do: false defp user_in_logins?(ids_list, %User{login: login}), do: Enum.member?(ids_list, login) diff --git a/lib/groupher_server/cms/delegates/article_comment_action.ex b/lib/groupher_server/cms/delegates/article_comment_action.ex index 3879ada88..9ea8eae2a 100644 --- a/lib/groupher_server/cms/delegates/article_comment_action.ex +++ b/lib/groupher_server/cms/delegates/article_comment_action.ex @@ -6,6 +6,9 @@ defmodule GroupherServer.CMS.Delegate.ArticleCommentAction do import Helper.Utils, only: [done: 1, strip_struct: 1] import Helper.ErrorCode + import GroupherServer.CMS.Delegate.ArticleComment, + only: [add_participator_to_article: 2, do_create_comment: 4, update_article_comments_count: 2] + import GroupherServer.CMS.Utils.Matcher2 alias Helper.Types, as: T @@ -26,12 +29,8 @@ defmodule GroupherServer.CMS.Delegate.ArticleCommentAction do alias Ecto.Multi - @max_participator_count ArticleComment.max_participator_count() @max_parent_replies_count ArticleComment.max_parent_replies_count() - @default_emotions Embeds.ArticleCommentEmotion.default_emotions() @report_threshold_for_fold ArticleComment.report_threshold_for_fold() - - @default_comment_meta Embeds.ArticleCommentMeta.default_meta() @pined_comment_limit ArticleComment.pined_comment_limit() @spec pin_article_comment(Integer.t()) :: {:ok, ArticleComment.t()} @@ -241,50 +240,6 @@ defmodule GroupherServer.CMS.Delegate.ArticleCommentAction do end end - # update comment's parent article's comments total count - @spec update_article_comments_count(ArticleComment.t(), :inc | :dec) :: ArticleComment.t() - defp update_article_comments_count(%ArticleComment{} = comment, opt) do - with {:ok, article_info} <- match(:comment_article, comment), - {:ok, article} <- ORM.find(article_info.model, article_info.id) do - count_query = - from(c in ArticleComment, where: field(c, ^article_info.foreign_key) == ^article_info.id) - - cur_count = Repo.aggregate(count_query, :count) - - # dec 是 comment 还没有删除的时候的操作,和 inc 不同 - # 因为 dec 操作如果放在 delete 后面,那么 update 会失败 - case opt do - :inc -> ORM.update(article, %{article_comments_count: cur_count}) - :dec -> ORM.update(article, %{article_comments_count: Enum.max([1, cur_count]) - 1}) - end - end - end - - # creat article comment for parent or reply - # set floor - # TODO: parse editor-json - # set default emotions - defp do_create_comment(content, foreign_key, article, %User{id: user_id}) do - count_query = from(c in ArticleComment, where: field(c, ^foreign_key) == ^article.id) - floor = Repo.aggregate(count_query, :count) + 1 - - ArticleComment - |> ORM.create( - Map.put( - %{ - author_id: user_id, - body_html: content, - emotions: @default_emotions, - floor: floor, - is_article_author: user_id == article.author.user.id, - meta: @default_comment_meta - }, - foreign_key, - article.id - ) - ) - end - # 设计盖楼只保留一个层级,回复楼中的评论都会被放到顶楼的 replies 中 defp get_parent_comment(%ArticleComment{reply_to_id: nil} = comment) do comment @@ -318,26 +273,6 @@ defmodule GroupherServer.CMS.Delegate.ArticleCommentAction do {:ok, parent_comment} end - # add participator to article-like content (Post, Job ...) and update count - defp add_participator_to_article(%Post{} = article, %User{} = user) do - total_participators = - article.article_comments_participators - |> List.insert_at(0, user) - |> Enum.uniq() - - new_comment_participators = total_participators |> Enum.slice(0, @max_participator_count) - - total_participators_count = length(total_participators) - - article - |> Ecto.Changeset.change() - |> Ecto.Changeset.put_change(:article_comments_participators_count, total_participators_count) - |> Ecto.Changeset.put_embed(:article_comments_participators, new_comment_participators) - |> Repo.update() - end - - defp add_participator_to_article(_, _), do: {:ok, :pass} - defp get_article(%ArticleComment{post_id: post_id} = comment) when not is_nil(post_id) do with {:ok, article} <- ORM.find(Post, comment.post_id, preload: [author: :user]) do {:post, article} diff --git a/lib/groupher_server/cms/job.ex b/lib/groupher_server/cms/job.ex index 1aecc75db..0cfbeaaf3 100644 --- a/lib/groupher_server/cms/job.ex +++ b/lib/groupher_server/cms/job.ex @@ -7,7 +7,7 @@ defmodule GroupherServer.CMS.Job do import Ecto.Changeset - alias GroupherServer.CMS + alias GroupherServer.{CMS, Accounts} alias CMS.{ Author, @@ -67,6 +67,8 @@ defmodule GroupherServer.CMS.Job do has_many(:article_comments, {"articles_comments", ArticleComment}) field(:article_comments_count, :integer, default: 0) field(:article_comments_participators_count, :integer, default: 0) + # 评论参与者,只保留最近 5 个 + embeds_many(:article_comments_participators, Accounts.User, on_replace: :delete) many_to_many( :tags, diff --git a/priv/repo/migrations/20210508072209_add_article_comments_participators_to_job.exs b/priv/repo/migrations/20210508072209_add_article_comments_participators_to_job.exs new file mode 100644 index 000000000..36cc6aaf5 --- /dev/null +++ b/priv/repo/migrations/20210508072209_add_article_comments_participators_to_job.exs @@ -0,0 +1,9 @@ +defmodule GroupherServer.Repo.Migrations.AddArticleCommentsParticipatorsToJob do + use Ecto.Migration + + def change do + alter table(:cms_jobs) do + add(:article_comments_participators, :map) + end + end +end From e230bb21a046723e0c1453b3ca7da73ce6d707a8 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 8 May 2021 15:59:04 +0800 Subject: [PATCH 3/7] refactor(article-comment): wip --- .../cms/comments/job_comment_test.exs | 620 ++++++++++++++++++ .../post_comment_emotions_test.exs} | 5 +- .../post_comment_replies_test.exs} | 5 +- .../post_comment_test.exs} | 40 +- 4 files changed, 633 insertions(+), 37 deletions(-) create mode 100644 test/groupher_server/cms/comments/job_comment_test.exs rename test/groupher_server/cms/{article_comment_emotions_test.exs => comments/post_comment_emotions_test.exs} (98%) rename test/groupher_server/cms/{article_comment_replies_test.exs => comments/post_comment_replies_test.exs} (98%) rename test/groupher_server/cms/{article_comment_test.exs => comments/post_comment_test.exs} (93%) diff --git a/test/groupher_server/cms/comments/job_comment_test.exs b/test/groupher_server/cms/comments/job_comment_test.exs new file mode 100644 index 000000000..01a44dae0 --- /dev/null +++ b/test/groupher_server/cms/comments/job_comment_test.exs @@ -0,0 +1,620 @@ +defmodule GroupherServer.Test.CMS.Comments.JobComment do + @moduledoc false + + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.{Accounts, CMS} + + alias CMS.{ArticleComment, ArticlePinedComment, Embeds, Job} + + @delete_hint CMS.ArticleComment.delete_hint() + @report_threshold_for_fold ArticleComment.report_threshold_for_fold() + @default_comment_meta Embeds.ArticleCommentMeta.default_meta() + @pined_comment_limit ArticleComment.pined_comment_limit() + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, job} = db_insert(:job) + + {:ok, ~m(user user2 job)a} + end + + describe "[basic article comment]" do + @tag :wip + test "job are supported by article comment.", ~m(user job)a do + {:ok, job_comment_1} = CMS.create_article_comment(:job, job.id, "job_comment 1", user) + {:ok, job_comment_2} = CMS.create_article_comment(:job, job.id, "job_comment 2", user) + + {:ok, job} = ORM.find(Job, job.id, preload: :article_comments) + + assert exist_in?(job_comment_1, job.article_comments) + assert exist_in?(job_comment_2, job.article_comments) + end + + @tag :wip + test "comment should have default meta after create", ~m(user job)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "job comment", user) + assert comment.meta |> Map.from_struct() |> Map.delete(:id) == @default_comment_meta + end + + @tag :wip3 + test "comment can be updated", ~m(job user)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "job comment", user) + + {:ok, updated_comment} = CMS.update_article_comment(comment, "updated content") + + assert updated_comment.body_html == "updated content" + end + end + + describe "[article comment floor]" do + @tag :wip + test "comment will have a floor number after created", ~m(user job)a do + {:ok, job_comment} = CMS.create_article_comment(:job, job.id, "comment", user) + {:ok, job_comment2} = CMS.create_article_comment(:job, job.id, "comment2", user) + + {:ok, job_comment} = ORM.find(ArticleComment, job_comment.id) + {:ok, job_comment2} = ORM.find(ArticleComment, job_comment2.id) + + assert job_comment.floor == 1 + assert job_comment2.floor == 2 + end + end + + describe "[article comment participator for job]" do + @tag :wip + test "job will have participator after comment created", ~m(user job)a do + job_comment_1 = "job_comment 1" + + {:ok, _} = CMS.create_article_comment(:job, job.id, job_comment_1, user) + + {:ok, job} = ORM.find(Job, job.id) + + participator = List.first(job.article_comments_participators) + assert participator.id == user.id + end + + @tag :wip + test "psot participator will not contains same user", ~m(user job)a do + job_comment_1 = "job_comment 1" + + {:ok, _} = CMS.create_article_comment(:job, job.id, job_comment_1, user) + {:ok, _} = CMS.create_article_comment(:job, job.id, job_comment_1, user) + + {:ok, job} = ORM.find(Job, job.id) + + assert 1 == length(job.article_comments_participators) + end + + @tag :wip + test "recent comment user should appear at first of the psot participators", + ~m(user user2 job)a do + job_comment_1 = "job_comment 1" + + {:ok, _} = CMS.create_article_comment(:job, job.id, job_comment_1, user) + {:ok, _} = CMS.create_article_comment(:job, job.id, job_comment_1, user2) + + {:ok, job} = ORM.find(Job, job.id) + + participator = List.first(job.article_comments_participators) + + assert participator.id == user2.id + end + end + + describe "[article comment upvotes]" do + @tag :wip + test "user can upvote a job comment", ~m(user job)a do + comment = "job_comment" + {:ok, comment} = CMS.create_article_comment(:job, job.id, comment, user) + + CMS.upvote_article_comment(comment.id, user) + + {:ok, comment} = ORM.find(ArticleComment, comment.id, preload: :upvotes) + + assert 1 == length(comment.upvotes) + assert List.first(comment.upvotes).user_id == user.id + end + + @tag :wip + test "article author upvote job comment will have flag", ~m(job user)a do + comment = "job_comment" + {:ok, comment} = CMS.create_article_comment(:job, job.id, comment, user) + {:ok, author_user} = ORM.find(Accounts.User, job.author.user.id) + + CMS.upvote_article_comment(comment.id, author_user) + + {:ok, comment} = ORM.find(ArticleComment, comment.id, preload: :upvotes) + assert comment.meta.is_article_author_upvoted + end + + @tag :wip3 + test "user upvote job comment will add id to upvoted_user_ids", ~m(job user)a do + comment = "job_comment" + {:ok, comment} = CMS.create_article_comment(:job, job.id, comment, user) + {:ok, comment} = CMS.upvote_article_comment(comment.id, user) + + assert user.id in comment.meta.upvoted_user_ids + end + + @tag :wip3 + test "user undo upvote job comment will remove id from upvoted_user_ids", + ~m(job user user2)a do + comment = "job_comment" + {:ok, comment} = CMS.create_article_comment(:job, job.id, comment, user) + {:ok, _comment} = CMS.upvote_article_comment(comment.id, user) + {:ok, comment} = CMS.upvote_article_comment(comment.id, user2) + + assert user2.id in comment.meta.upvoted_user_ids + assert user.id in comment.meta.upvoted_user_ids + + {:ok, comment} = CMS.undo_upvote_article_comment(comment.id, user2) + + assert user.id in comment.meta.upvoted_user_ids + assert user2.id not in comment.meta.upvoted_user_ids + end + + @tag :wip + test "user upvote a already-upvoted comment fails", ~m(user job)a do + comment = "job_comment" + {:ok, comment} = CMS.create_article_comment(:job, job.id, comment, user) + + CMS.upvote_article_comment(comment.id, user) + {:error, _} = CMS.upvote_article_comment(comment.id, user) + end + + @tag :wip + test "upvote comment should inc the comment's upvotes_count", ~m(user user2 job)a do + comment = "job_comment" + {:ok, comment} = CMS.create_article_comment(:job, job.id, comment, user) + {:ok, comment} = ORM.find(ArticleComment, comment.id) + assert comment.upvotes_count == 0 + + {:ok, _} = CMS.upvote_article_comment(comment.id, user) + {:ok, _} = CMS.upvote_article_comment(comment.id, user2) + + {:ok, comment} = ORM.find(ArticleComment, comment.id) + assert comment.upvotes_count == 2 + end + + @tag :wip + test "user can undo upvote a job comment", ~m(user job)a do + content = "job_comment" + {:ok, comment} = CMS.create_article_comment(:job, job.id, content, user) + CMS.upvote_article_comment(comment.id, user) + + {:ok, comment} = ORM.find(ArticleComment, comment.id, preload: :upvotes) + assert 1 == length(comment.upvotes) + + {:ok, comment} = CMS.undo_upvote_article_comment(comment.id, user) + assert 0 == comment.upvotes_count + end + + @tag :wip + test "user can undo upvote a job comment with no upvote", ~m(user job)a do + content = "job_comment" + {:ok, comment} = CMS.create_article_comment(:job, job.id, content, user) + {:ok, comment} = CMS.undo_upvote_article_comment(comment.id, user) + assert 0 == comment.upvotes_count + + {:ok, comment} = CMS.undo_upvote_article_comment(comment.id, user) + assert 0 == comment.upvotes_count + end + end + + describe "[article comment fold/unfold]" do + @tag :wip + test "user can fold a comment", ~m(user job)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + {:ok, comment} = ORM.find(ArticleComment, comment.id) + + assert not comment.is_folded + + {:ok, comment} = CMS.fold_article_comment(comment.id, user) + {:ok, comment} = ORM.find(ArticleComment, comment.id) + assert comment.is_folded + end + + @tag :wip + test "user can unfold a comment", ~m(user job)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + {:ok, _comment} = CMS.fold_article_comment(comment.id, user) + {:ok, comment} = ORM.find(ArticleComment, comment.id) + + assert comment.is_folded + + {:ok, _comment} = CMS.unfold_article_comment(comment.id, user) + {:ok, comment} = ORM.find(ArticleComment, comment.id) + assert not comment.is_folded + end + end + + describe "[article comment pin/unpin]" do + test "user can pin a comment", ~m(user job)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + {:ok, comment} = ORM.find(ArticleComment, comment.id) + + assert not comment.is_pinned + + {:ok, comment} = CMS.pin_article_comment(comment.id) + {:ok, comment} = ORM.find(ArticleComment, comment.id) + + assert comment.is_pinned + + {:ok, pined_record} = ArticlePinedComment |> ORM.find_by(%{job_id: job.id}) + assert pined_record.job_id == job.id + end + + test "user can unpin a comment", ~m(user job)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + + {:ok, _comment} = CMS.pin_article_comment(comment.id) + {:ok, comment} = CMS.undo_pin_article_comment(comment.id) + + assert not comment.is_pinned + assert {:error, _} = ArticlePinedComment |> ORM.find_by(%{article_comment_id: comment.id}) + end + + @tag :wip + test "pined comments has a limit for each article", ~m(user job)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + + Enum.reduce(0..(@pined_comment_limit - 1), [], fn _, _acc -> + {:ok, _comment} = CMS.pin_article_comment(comment.id) + end) + + assert {:error, _} = CMS.pin_article_comment(comment.id) + end + end + + describe "[article comment report/unreport]" do + @tag :wip + test "user can report a comment", ~m(user job)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + {:ok, comment} = ORM.find(ArticleComment, comment.id) + + assert not comment.is_reported + + {:ok, comment} = CMS.report_article_comment(comment.id, user) + {:ok, comment} = ORM.find(ArticleComment, comment.id) + assert comment.is_reported + end + + @tag :wip + test "user can unreport a comment", ~m(user job)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + {:ok, _comment} = CMS.report_article_comment(comment.id, user) + {:ok, comment} = ORM.find(ArticleComment, comment.id) + + assert comment.is_reported + + {:ok, _comment} = CMS.unreport_article_comment(comment.id, user) + {:ok, comment} = ORM.find(ArticleComment, comment.id) + assert not comment.is_reported + end + + @tag :wip + test "report user < @report_threshold_for_fold will not fold comment", ~m(user job)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + + assert not comment.is_reported + assert not comment.is_folded + + Enum.reduce(1..(@report_threshold_for_fold - 1), [], fn _, _acc -> + {:ok, user} = db_insert(:user) + {:ok, _comment} = CMS.report_article_comment(comment.id, user) + end) + + {:ok, comment} = ORM.find(ArticleComment, comment.id) + assert comment.is_reported + assert not comment.is_folded + end + + @tag :wip + test "report user > @report_threshold_for_fold will cause comment fold", ~m(user job)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + + assert not comment.is_reported + assert not comment.is_folded + + Enum.reduce(1..(@report_threshold_for_fold + 1), [], fn _, _acc -> + {:ok, user} = db_insert(:user) + {:ok, _comment} = CMS.report_article_comment(comment.id, user) + end) + + {:ok, comment} = ORM.find(ArticleComment, comment.id) + assert comment.is_reported + assert comment.is_folded + end + end + + describe "paged article comments" do + @tag :wip + test "can load paged comments participators of a article", ~m(user job)a do + total_count = 30 + page_size = 10 + thread = :job + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, new_user} = db_insert(:user) + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", new_user) + + acc ++ [comment] + end) + + {:ok, _comment} = CMS.create_article_comment(:job, job.id, "commment", user) + {:ok, _comment} = CMS.create_article_comment(:job, job.id, "commment", user) + + {:ok, results} = + CMS.list_article_comments_participators(thread, job.id, %{page: 1, size: page_size}) + + assert results |> is_valid_pagination?(:raw) + assert results.total_count == total_count + 1 + end + + @tag :wip + test "paged article comments folded flag should be false", ~m(user job)a do + total_count = 30 + page_number = 1 + page_size = 10 + + all_comments = + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + + acc ++ [comment] + end) + + {:ok, paged_comments} = + CMS.list_article_comments(:job, job.id, %{page: page_number, size: page_size}, :replies) + + random_comment = all_comments |> Enum.at(Enum.random(0..total_count)) + + assert not random_comment.is_folded + + assert page_number == paged_comments.page_number + assert page_size == paged_comments.page_size + assert total_count == paged_comments.total_count + end + + @tag :wip + test "paged article comments should contains pined comments at top position", + ~m(user job)a do + total_count = 20 + page_number = 1 + page_size = 5 + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + + acc ++ [comment] + end) + + {:ok, random_comment_1} = CMS.create_article_comment(:job, job.id, "pin commment", user) + {:ok, random_comment_2} = CMS.create_article_comment(:job, job.id, "pin commment2", user) + + {:ok, pined_comment_1} = CMS.pin_article_comment(random_comment_1.id) + {:ok, pined_comment_2} = CMS.pin_article_comment(random_comment_2.id) + + {:ok, paged_comments} = + CMS.list_article_comments(:job, job.id, %{page: page_number, size: page_size}, :replies) + + assert pined_comment_1.id == List.first(paged_comments.entries) |> Map.get(:id) + assert pined_comment_2.id == Enum.at(paged_comments.entries, 1) |> Map.get(:id) + + assert paged_comments.total_count == total_count + 2 + end + + @tag :wip + test "only page 1 have pined coments", + ~m(user job)a do + total_count = 20 + page_number = 2 + page_size = 5 + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + + acc ++ [comment] + end) + + {:ok, random_comment_1} = CMS.create_article_comment(:job, job.id, "pin commment", user) + {:ok, random_comment_2} = CMS.create_article_comment(:job, job.id, "pin commment2", user) + + {:ok, pined_comment_1} = CMS.pin_article_comment(random_comment_1.id) + {:ok, pined_comment_2} = CMS.pin_article_comment(random_comment_2.id) + + {:ok, paged_comments} = + CMS.list_article_comments(:job, job.id, %{page: page_number, size: page_size}, :replies) + + assert not exist_in?(pined_comment_1, paged_comments.entries) + assert not exist_in?(pined_comment_2, paged_comments.entries) + + assert paged_comments.total_count == total_count + end + + @tag :wip + test "paged article comments should not contains folded and repoted comments", + ~m(user job)a do + total_count = 15 + page_number = 1 + page_size = 20 + + all_comments = + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + + acc ++ [comment] + end) + + random_comment_1 = all_comments |> Enum.at(0) + random_comment_2 = all_comments |> Enum.at(1) + random_comment_3 = all_comments |> Enum.at(3) + + random_comment_4 = all_comments |> Enum.at(2) + random_comment_5 = all_comments |> Enum.at(4) + random_comment_6 = all_comments |> Enum.at(8) + + {:ok, _comment} = CMS.fold_article_comment(random_comment_1.id, user) + {:ok, _comment} = CMS.fold_article_comment(random_comment_2.id, user) + {:ok, _comment} = CMS.fold_article_comment(random_comment_3.id, user) + + {:ok, _comment} = CMS.report_article_comment(random_comment_4.id, user) + {:ok, _comment} = CMS.report_article_comment(random_comment_5.id, user) + {:ok, _comment} = CMS.report_article_comment(random_comment_6.id, user) + + {:ok, paged_comments} = + CMS.list_article_comments(:job, job.id, %{page: page_number, size: page_size}, :replies) + + assert not exist_in?(random_comment_1, paged_comments.entries) + assert not exist_in?(random_comment_2, paged_comments.entries) + assert not exist_in?(random_comment_3, paged_comments.entries) + + assert not exist_in?(random_comment_4, paged_comments.entries) + assert not exist_in?(random_comment_5, paged_comments.entries) + assert not exist_in?(random_comment_6, paged_comments.entries) + + assert page_number == paged_comments.page_number + assert page_size == paged_comments.page_size + assert total_count - 6 == paged_comments.total_count + end + + @tag :wip + test "can loaded paged folded comment", ~m(user job)a do + total_count = 10 + page_number = 1 + page_size = 20 + + all_folded_comments = + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + CMS.fold_article_comment(comment.id, user) + + acc ++ [comment] + end) + + random_comment_1 = all_folded_comments |> Enum.at(1) + random_comment_2 = all_folded_comments |> Enum.at(3) + random_comment_3 = all_folded_comments |> Enum.at(5) + + {:ok, paged_comments} = + CMS.list_folded_article_comments(:job, job.id, %{page: page_number, size: page_size}) + + assert exist_in?(random_comment_1, paged_comments.entries) + assert exist_in?(random_comment_2, paged_comments.entries) + assert exist_in?(random_comment_3, paged_comments.entries) + + assert page_number == paged_comments.page_number + assert page_size == paged_comments.page_size + assert total_count == paged_comments.total_count + end + + @tag :wip + test "can loaded paged reported comment", ~m(user job)a do + total_count = 10 + page_number = 1 + page_size = 20 + + all_reported_comments = + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + CMS.report_article_comment(comment.id, user) + + acc ++ [comment] + end) + + random_comment_1 = all_reported_comments |> Enum.at(1) + random_comment_2 = all_reported_comments |> Enum.at(3) + random_comment_3 = all_reported_comments |> Enum.at(5) + + {:ok, paged_comments} = + CMS.list_reported_article_comments(:job, job.id, %{page: page_number, size: page_size}) + + assert exist_in?(random_comment_1, paged_comments.entries) + assert exist_in?(random_comment_2, paged_comments.entries) + assert exist_in?(random_comment_3, paged_comments.entries) + + assert page_number == paged_comments.page_number + assert page_size == paged_comments.page_size + assert total_count == paged_comments.total_count + end + end + + describe "[article comment delete]" do + @tag :wip3 + test "delete comment still exsit in paged list and content is gone", ~m(user job)a do + total_count = 10 + page_number = 1 + page_size = 20 + + all_comments = + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + + acc ++ [comment] + end) + + random_comment = all_comments |> Enum.at(1) + + {:ok, deleted_comment} = CMS.delete_article_comment(random_comment) + + {:ok, paged_comments} = + CMS.list_article_comments(:job, job.id, %{page: page_number, size: page_size}, :replies) + + assert exist_in?(deleted_comment, paged_comments.entries) + assert deleted_comment.is_deleted + assert deleted_comment.body_html == @delete_hint + end + + @tag :wip3 + test "delete comment still update article's comments_count field", ~m(user job)a do + {:ok, _comment} = CMS.create_article_comment(:job, job.id, "commment", user) + {:ok, _comment} = CMS.create_article_comment(:job, job.id, "commment", user) + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + {:ok, _comment} = CMS.create_article_comment(:job, job.id, "commment", user) + {:ok, _comment} = CMS.create_article_comment(:job, job.id, "commment", user) + + {:ok, job} = ORM.find(Job, job.id) + + assert job.article_comments_count == 5 + + {:ok, _} = CMS.delete_article_comment(comment) + + {:ok, job} = ORM.find(Job, job.id) + assert job.article_comments_count == 4 + end + + @tag :wip3 + test "delete comment still delete pined record if needed", ~m(user job)a do + total_count = 10 + + all_comments = + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + + acc ++ [comment] + end) + + random_comment = all_comments |> Enum.at(1) + + {:ok, _comment} = CMS.pin_article_comment(random_comment.id) + {:ok, _comment} = ORM.find(ArticleComment, random_comment.id) + + {:ok, _} = CMS.delete_article_comment(random_comment) + assert {:error, _comment} = ORM.find(ArticlePinedComment, random_comment.id) + end + end + + describe "[article comment info]" do + test "author of the article comment a comment should have flag", ~m(user job)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + assert not comment.is_article_author + + author_user = job.author.user + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", author_user) + assert comment.is_article_author + end + end +end diff --git a/test/groupher_server/cms/article_comment_emotions_test.exs b/test/groupher_server/cms/comments/post_comment_emotions_test.exs similarity index 98% rename from test/groupher_server/cms/article_comment_emotions_test.exs rename to test/groupher_server/cms/comments/post_comment_emotions_test.exs index 7a6ccbad5..2b9ccdd4f 100644 --- a/test/groupher_server/cms/article_comment_emotions_test.exs +++ b/test/groupher_server/cms/comments/post_comment_emotions_test.exs @@ -1,4 +1,4 @@ -defmodule GroupherServer.Test.CMS.ArticleCommentEmotions do +defmodule GroupherServer.Test.CMS.Comments.PostCommentEmotions do @moduledoc false use GroupherServer.TestTools @@ -16,9 +16,8 @@ defmodule GroupherServer.Test.CMS.ArticleCommentEmotions do {:ok, user3} = db_insert(:user) {:ok, post} = db_insert(:post) - {:ok, job} = db_insert(:job) - {:ok, ~m(user user2 user3 post job)a} + {:ok, ~m(user user2 user3 post)a} end describe "[emotion in paged article comment]" do diff --git a/test/groupher_server/cms/article_comment_replies_test.exs b/test/groupher_server/cms/comments/post_comment_replies_test.exs similarity index 98% rename from test/groupher_server/cms/article_comment_replies_test.exs rename to test/groupher_server/cms/comments/post_comment_replies_test.exs index 09c39f5f8..acf9eb8b2 100644 --- a/test/groupher_server/cms/article_comment_replies_test.exs +++ b/test/groupher_server/cms/comments/post_comment_replies_test.exs @@ -1,4 +1,4 @@ -defmodule GroupherServer.Test.CMS.ArticleCommentReplies do +defmodule GroupherServer.Test.CMS.Comments.PostCommentReplies do @moduledoc false use GroupherServer.TestTools @@ -14,9 +14,8 @@ defmodule GroupherServer.Test.CMS.ArticleCommentReplies do {:ok, user} = db_insert(:user) {:ok, user2} = db_insert(:user) {:ok, post} = db_insert(:post) - {:ok, job} = db_insert(:job) - {:ok, ~m(user user2 post job)a} + {:ok, ~m(user user2 post)a} end describe "[basic article comment replies]" do diff --git a/test/groupher_server/cms/article_comment_test.exs b/test/groupher_server/cms/comments/post_comment_test.exs similarity index 93% rename from test/groupher_server/cms/article_comment_test.exs rename to test/groupher_server/cms/comments/post_comment_test.exs index ee1d198c4..a635353da 100644 --- a/test/groupher_server/cms/article_comment_test.exs +++ b/test/groupher_server/cms/comments/post_comment_test.exs @@ -1,4 +1,4 @@ -defmodule GroupherServer.Test.CMS.ArticleComment do +defmodule GroupherServer.Test.CMS.Comments.PostComment do @moduledoc false use GroupherServer.TestTools @@ -6,7 +6,7 @@ defmodule GroupherServer.Test.CMS.ArticleComment do alias Helper.ORM alias GroupherServer.{Accounts, CMS} - alias CMS.{ArticleComment, ArticlePinedComment, Embeds, Post, Job} + alias CMS.{ArticleComment, ArticlePinedComment, Embeds, Post} @delete_hint CMS.ArticleComment.delete_hint() @report_threshold_for_fold ArticleComment.report_threshold_for_fold() @@ -17,28 +17,20 @@ defmodule GroupherServer.Test.CMS.ArticleComment do {:ok, user} = db_insert(:user) {:ok, user2} = db_insert(:user) {:ok, post} = db_insert(:post) - {:ok, job} = db_insert(:job) - {:ok, ~m(user user2 post job)a} + {:ok, ~m(user user2 post)a} end describe "[basic article comment]" do @tag :wip - test "post, job are supported by article comment.", ~m(user post job)a do + test "post are supported by article comment.", ~m(user post)a do {:ok, post_comment_1} = CMS.create_article_comment(:post, post.id, "post_comment 1", user) {:ok, post_comment_2} = CMS.create_article_comment(:post, post.id, "post_comment 2", user) - {:ok, job_comment_1} = CMS.create_article_comment(:job, job.id, "job_comment 1", user) - {:ok, job_comment_2} = CMS.create_article_comment(:job, job.id, "job_comment 2", user) - {:ok, post} = ORM.find(Post, post.id, preload: :article_comments) - {:ok, job} = ORM.find(Job, job.id, preload: :article_comments) assert exist_in?(post_comment_1, post.article_comments) assert exist_in?(post_comment_2, post.article_comments) - - assert exist_in?(job_comment_1, job.article_comments) - assert exist_in?(job_comment_2, job.article_comments) end @tag :wip @@ -59,20 +51,19 @@ defmodule GroupherServer.Test.CMS.ArticleComment do describe "[article comment floor]" do @tag :wip - test "comment will have a floor number after created", ~m(user post job)a do - {:ok, _comment} = CMS.create_article_comment(:job, job.id, "comment", user) - {:ok, job_comment} = CMS.create_article_comment(:job, job.id, "comment", user) + test "comment will have a floor number after created", ~m(user post)a do {:ok, post_comment} = CMS.create_article_comment(:post, post.id, "comment", user) + {:ok, post_comment2} = CMS.create_article_comment(:post, post.id, "comment2", user) {:ok, post_comment} = ORM.find(ArticleComment, post_comment.id) - {:ok, job_comment} = ORM.find(ArticleComment, job_comment.id) + {:ok, post_comment2} = ORM.find(ArticleComment, post_comment2.id) assert post_comment.floor == 1 - assert job_comment.floor == 2 + assert post_comment2.floor == 2 end end - describe "[article comment participator]" do + describe "[article comment participator for post]" do @tag :wip test "post will have participator after comment created", ~m(user post)a do post_comment_1 = "post_comment 1" @@ -165,19 +156,6 @@ defmodule GroupherServer.Test.CMS.ArticleComment do assert user2.id not in comment.meta.upvoted_user_ids end - @tag :wip - test "user can upvote a job comment", ~m(user job)a do - comment = "job_comment" - {:ok, comment} = CMS.create_article_comment(:job, job.id, comment, user) - - CMS.upvote_article_comment(comment.id, user) - - {:ok, comment} = ORM.find(ArticleComment, comment.id, preload: :upvotes) - - assert 1 == length(comment.upvotes) - assert List.first(comment.upvotes).user_id == user.id - end - @tag :wip test "user upvote a already-upvoted comment fails", ~m(user post)a do comment = "post_comment" From 8469d02f8d116b777752bcf8a5ddabd7e18a2d71 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 8 May 2021 16:05:51 +0800 Subject: [PATCH 4/7] chore(comments): tests for job --- .../comments/job_comment_emotions_test.exs | 207 ++++++++++++++++ .../cms/comments/job_comment_replies_test.exs | 223 ++++++++++++++++++ 2 files changed, 430 insertions(+) create mode 100644 test/groupher_server/cms/comments/job_comment_emotions_test.exs create mode 100644 test/groupher_server/cms/comments/job_comment_replies_test.exs diff --git a/test/groupher_server/cms/comments/job_comment_emotions_test.exs b/test/groupher_server/cms/comments/job_comment_emotions_test.exs new file mode 100644 index 000000000..ff19c4a6f --- /dev/null +++ b/test/groupher_server/cms/comments/job_comment_emotions_test.exs @@ -0,0 +1,207 @@ +defmodule GroupherServer.Test.CMS.Comments.JobCommentEmotions do + @moduledoc false + + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + + alias CMS.{ArticleComment, Embeds, ArticleCommentUserEmotion} + + @default_emotions Embeds.ArticleCommentEmotion.default_emotions() + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, user3} = db_insert(:user) + + {:ok, job} = db_insert(:job) + + {:ok, ~m(user user2 user3 job)a} + end + + describe "[emotion in paged article comment]" do + @tag :wip3 + test "login user should got viewer has emotioned status", ~m(job user)a do + total_count = 0 + page_number = 10 + page_size = 20 + + all_comment = + Enum.reduce(0..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + acc ++ [comment] + end) + + first_comment = List.first(all_comment) + + {:ok, _} = CMS.emotion_to_comment(first_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(first_comment.id, :beer, user) + {:ok, _} = CMS.emotion_to_comment(first_comment.id, :popcorn, user) + + {:ok, paged_comments} = + CMS.list_article_comments( + :job, + job.id, + %{page: page_number, size: page_size}, + :replies, + user + ) + + target = Enum.find(paged_comments.entries, &(&1.id == first_comment.id)) + + assert target.emotions.downvote_count == 1 + assert user_exist_in?(user, target.emotions.latest_downvote_users) + assert target.emotions.viewer_has_downvoteed + + assert target.emotions.beer_count == 1 + assert user_exist_in?(user, target.emotions.latest_beer_users) + assert target.emotions.viewer_has_beered + + assert target.emotions.popcorn_count == 1 + assert user_exist_in?(user, target.emotions.latest_popcorn_users) + assert target.emotions.viewer_has_popcorned + end + end + + describe "[basic article comment emotion]" do + @tag :wip + test "comment has default emotions after created", ~m(job user)a do + parent_content = "parent comment" + + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, parent_content, user) + {:ok, parent_comment} = ORM.find(ArticleComment, parent_comment.id) + + emotions = parent_comment.emotions |> Map.from_struct() |> Map.delete(:id) + assert @default_emotions == emotions + end + + @tag :wip2 + test "can make emotion to comment", ~m(job user user2)a do + parent_content = "parent comment" + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, parent_content, user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user2) + + {:ok, %{emotions: emotions}} = ORM.find(ArticleComment, parent_comment.id) + + assert emotions.downvote_count == 2 + assert user_exist_in?(user, emotions.latest_downvote_users) + assert user_exist_in?(user2, emotions.latest_downvote_users) + end + + @tag :wip2 + test "can undo emotion to comment", ~m(job user user2)a do + parent_content = "parent comment" + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, parent_content, user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user2) + + {:ok, %{emotions: emotions}} = ORM.find(ArticleComment, parent_comment.id) + + assert emotions.downvote_count == 2 + assert user_exist_in?(user, emotions.latest_downvote_users) + assert user_exist_in?(user2, emotions.latest_downvote_users) + + {:ok, _} = CMS.undo_emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.undo_emotion_to_comment(parent_comment.id, :downvote, user2) + + {:ok, %{emotions: emotions}} = ORM.find(ArticleComment, parent_comment.id) + assert emotions.downvote_count == 0 + assert not user_exist_in?(user, emotions.latest_downvote_users) + assert not user_exist_in?(user2, emotions.latest_downvote_users) + end + + @tag :wip2 + test "same user make same emotion to same comment.", ~m(job user)a do + parent_content = "parent comment" + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, parent_content, user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + + {:ok, parent_comment} = ORM.find(ArticleComment, parent_comment.id) + + assert parent_comment.emotions.downvote_count == 1 + assert user_exist_in?(user, parent_comment.emotions.latest_downvote_users) + end + + @tag :wip2 + test "same user same emotion to same comment only have one user_emotion record", + ~m(job user)a do + parent_content = "parent comment" + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, parent_content, user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :heart, user) + + {:ok, parent_comment} = ORM.find(ArticleComment, parent_comment.id) + + {:ok, records} = ORM.find_all(ArticleCommentUserEmotion, %{page: 1, size: 10}) + assert records.total_count == 1 + + {:ok, record} = + ORM.find_by(ArticleCommentUserEmotion, %{ + article_comment_id: parent_comment.id, + user_id: user.id + }) + + assert record.downvote + assert record.heart + end + + @tag :wip2 + test "different user can make same emotions on same comment", ~m(job user user2 user3)a do + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, "parent comment", user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :beer, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :beer, user2) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :beer, user3) + + {:ok, %{emotions: emotions}} = ORM.find(ArticleComment, parent_comment.id) + # IO.inspect(emotions, label: "the parent_comment") + + assert emotions.beer_count == 3 + assert user_exist_in?(user, emotions.latest_beer_users) + assert user_exist_in?(user2, emotions.latest_beer_users) + assert user_exist_in?(user3, emotions.latest_beer_users) + end + + @tag :wip + test "same user can make differcent emotions on same comment", ~m(job user)a do + parent_content = "parent comment" + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, parent_content, user) + + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :beer, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :heart, user) + {:ok, _} = CMS.emotion_to_comment(parent_comment.id, :orz, user) + + {:ok, %{emotions: emotions}} = ORM.find(ArticleComment, parent_comment.id) + + assert emotions.downvote_count == 1 + assert user_exist_in?(user, emotions.latest_downvote_users) + + assert emotions.beer_count == 1 + assert user_exist_in?(user, emotions.latest_beer_users) + + assert emotions.heart_count == 1 + assert user_exist_in?(user, emotions.latest_heart_users) + + assert emotions.orz_count == 1 + assert user_exist_in?(user, emotions.latest_orz_users) + + assert emotions.pill_count == 0 + assert not user_exist_in?(user, emotions.latest_pill_users) + + assert emotions.biceps_count == 0 + assert not user_exist_in?(user, emotions.latest_biceps_users) + + assert emotions.confused_count == 0 + assert not user_exist_in?(user, emotions.latest_confused_users) + end + end +end diff --git a/test/groupher_server/cms/comments/job_comment_replies_test.exs b/test/groupher_server/cms/comments/job_comment_replies_test.exs new file mode 100644 index 000000000..66d154bd2 --- /dev/null +++ b/test/groupher_server/cms/comments/job_comment_replies_test.exs @@ -0,0 +1,223 @@ +defmodule GroupherServer.Test.CMS.Comments.JobCommentReplies do + @moduledoc false + + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + + alias CMS.{ArticleComment, Job} + + @max_parent_replies_count CMS.ArticleComment.max_parent_replies_count() + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, job} = db_insert(:job) + + {:ok, ~m(user user2 job)a} + end + + describe "[basic article comment replies]" do + @tag :wip + test "exsit comment can be reply", ~m(job user user2)a do + parent_content = "parent comment" + reply_content = "reply comment" + + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, parent_content, user) + {:ok, replyed_comment} = CMS.reply_article_comment(parent_comment.id, reply_content, user2) + assert replyed_comment.reply_to.id == parent_comment.id + + {:ok, parent_comment} = ORM.find(ArticleComment, parent_comment.id) + + assert exist_in?(replyed_comment, parent_comment.replies) + end + + @tag :wip3 + test "deleted comment can not be reply", ~m(job user user2)a do + parent_content = "parent comment" + reply_content = "reply comment" + + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, parent_content, user) + {:ok, _} = CMS.delete_article_comment(parent_comment) + + {:error, _} = CMS.reply_article_comment(parent_comment.id, reply_content, user2) + end + + @tag :wip + test "multi reply should belong to one parent comment", ~m(job user user2)a do + parent_content = "parent comment" + reply_content_1 = "reply comment 1" + reply_content_2 = "reply comment 2" + + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, parent_content, user) + + {:ok, replyed_comment_1} = + CMS.reply_article_comment(parent_comment.id, reply_content_1, user2) + + {:ok, replyed_comment_2} = + CMS.reply_article_comment(parent_comment.id, reply_content_2, user2) + + {:ok, parent_comment} = ORM.find(ArticleComment, parent_comment.id) + + assert exist_in?(replyed_comment_1, parent_comment.replies) + assert exist_in?(replyed_comment_2, parent_comment.replies) + end + + @tag :wip2 + test "reply to reply inside a comment should belong same parent comment", + ~m(job user user2)a do + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, "parent comment", user) + + {:ok, replyed_comment_1} = CMS.reply_article_comment(parent_comment.id, "reply 1", user2) + {:ok, replyed_comment_2} = CMS.reply_article_comment(replyed_comment_1.id, "reply 2", user2) + {:ok, replyed_comment_3} = CMS.reply_article_comment(replyed_comment_2.id, "reply 3", user) + + {:ok, parent_comment} = ORM.find(ArticleComment, parent_comment.id) + + # IO.inspect(parent_comment.replies, label: "parent_comment.replies") + + assert exist_in?(replyed_comment_1, parent_comment.replies) + assert exist_in?(replyed_comment_2, parent_comment.replies) + assert exist_in?(replyed_comment_3, parent_comment.replies) + + {:ok, replyed_comment_1} = ORM.find(ArticleComment, replyed_comment_1.id) + {:ok, replyed_comment_2} = ORM.find(ArticleComment, replyed_comment_2.id) + {:ok, replyed_comment_3} = ORM.find(ArticleComment, replyed_comment_3.id) + + assert replyed_comment_1.reply_to_id == parent_comment.id + assert replyed_comment_2.reply_to_id == replyed_comment_1.id + assert replyed_comment_3.reply_to_id == replyed_comment_2.id + end + + @tag :wip2 + test "reply to reply inside a comment should have is_reply_to_others flag in meta", + ~m(job user user2)a do + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, "parent comment", user) + + {:ok, replyed_comment_1} = CMS.reply_article_comment(parent_comment.id, "reply 1", user2) + {:ok, replyed_comment_2} = CMS.reply_article_comment(replyed_comment_1.id, "reply 2", user2) + {:ok, replyed_comment_3} = CMS.reply_article_comment(replyed_comment_2.id, "reply 3", user) + + {:ok, _parent_comment} = ORM.find(ArticleComment, parent_comment.id) + + {:ok, replyed_comment_1} = ORM.find(ArticleComment, replyed_comment_1.id) + {:ok, replyed_comment_2} = ORM.find(ArticleComment, replyed_comment_2.id) + {:ok, replyed_comment_3} = ORM.find(ArticleComment, replyed_comment_3.id) + + assert not replyed_comment_1.meta.is_reply_to_others + assert replyed_comment_2.meta.is_reply_to_others + assert replyed_comment_3.meta.is_reply_to_others + end + + @tag :wip2 + test "comment replies only contains @max_parent_replies_count replies", ~m(job user)a do + total_reply_count = @max_parent_replies_count + 1 + + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, "parent_conent", user) + + reply_comment_list = + Enum.reduce(1..total_reply_count, [], fn n, acc -> + {:ok, replyed_comment} = + CMS.reply_article_comment(parent_comment.id, "reply_content_#{n}", user) + + acc ++ [replyed_comment] + end) + + {:ok, parent_comment} = ORM.find(ArticleComment, parent_comment.id) + + assert length(parent_comment.replies) == @max_parent_replies_count + assert exist_in?(Enum.at(reply_comment_list, 0), parent_comment.replies) + assert exist_in?(Enum.at(reply_comment_list, 1), parent_comment.replies) + assert exist_in?(Enum.at(reply_comment_list, 2), parent_comment.replies) + assert not exist_in?(List.last(reply_comment_list), parent_comment.replies) + end + + @tag :wip + test "replyed user should appear in article comment participators", ~m(job user user2)a do + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, "parent_conent", user) + {:ok, _} = CMS.reply_article_comment(parent_comment.id, "reply_content", user2) + + {:ok, article} = ORM.find(Job, job.id) + + assert exist_in?(user, article.article_comments_participators) + assert exist_in?(user2, article.article_comments_participators) + end + + @tag :wip + test "replies count should inc by 1 after got replyed", ~m(job user user2)a do + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, "parent_conent", user) + assert parent_comment.replies_count === 0 + + {:ok, _} = CMS.reply_article_comment(parent_comment.id, "reply_content", user2) + {:ok, parent_comment} = ORM.find(ArticleComment, parent_comment.id) + assert parent_comment.replies_count === 1 + + {:ok, _} = CMS.reply_article_comment(parent_comment.id, "reply_content", user2) + {:ok, parent_comment} = ORM.find(ArticleComment, parent_comment.id) + assert parent_comment.replies_count === 2 + end + end + + describe "[paged article comment replies]" do + @tag :wip + test "can get paged replies of a parent comment", ~m(job user)a do + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, "parent_conent", user) + {:ok, paged_replies} = CMS.list_comment_replies(parent_comment.id, %{page: 1, size: 20}) + assert is_valid_pagination?(paged_replies, :raw, :empty) + + total_reply_count = 30 + + reply_comment_list = + Enum.reduce(1..total_reply_count, [], fn n, acc -> + {:ok, replyed_comment} = + CMS.reply_article_comment(parent_comment.id, "reply_content_#{n}", user) + + acc ++ [replyed_comment] + end) + + {:ok, paged_replies} = CMS.list_comment_replies(parent_comment.id, %{page: 1, size: 20}) + + assert total_reply_count == paged_replies.total_count + assert is_valid_pagination?(paged_replies, :raw) + + assert exist_in?(Enum.at(reply_comment_list, 0), paged_replies.entries) + assert exist_in?(Enum.at(reply_comment_list, 1), paged_replies.entries) + assert exist_in?(Enum.at(reply_comment_list, 2), paged_replies.entries) + assert exist_in?(Enum.at(reply_comment_list, 3), paged_replies.entries) + end + + @tag :wip + test "can get reply_to info of a parent comment", ~m(job user)a do + page_number = 1 + page_size = 10 + + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, "parent_conent", user) + + {:ok, reply_comment} = CMS.reply_article_comment(parent_comment.id, "reply_content_1", user) + + {:ok, reply_comment2} = + CMS.reply_article_comment(parent_comment.id, "reply_content_2", user) + + {:ok, paged_comments} = + CMS.list_article_comments( + :job, + job.id, + %{page: page_number, size: page_size}, + :timeline + ) + + reply_comment = Enum.find(paged_comments.entries, &(&1.id == reply_comment.id)) + + assert reply_comment.reply_to.id == parent_comment.id + assert reply_comment.reply_to.body_html == parent_comment.body_html + assert reply_comment.reply_to.author.id == parent_comment.author_id + + reply_comment2 = Enum.find(paged_comments.entries, &(&1.id == reply_comment2.id)) + + assert reply_comment2.reply_to.id == parent_comment.id + assert reply_comment2.reply_to.body_html == parent_comment.body_html + assert reply_comment2.reply_to.author.id == parent_comment.author_id + end + end +end From ed7d00bc1dc551aaedd2883b0c723bbf6716fcf1 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 8 May 2021 16:53:04 +0800 Subject: [PATCH 5/7] refactor(article-comment): wip --- .../cms/delegates/article_comment.ex | 1 - .../schema/cms/cms_types.ex | 9 +- .../schema/utils/helper.ex | 8 + .../comments/job_comment_emotions_test.exs | 10 +- .../cms/comments/job_comment_replies_test.exs | 6 +- .../comments/post_comment_emotions_test.exs | 10 +- .../comments/post_comment_replies_test.exs | 6 +- .../post_comment_test.exs} | 4 +- ...ent_test.exs => old_post_comment_test.exs} | 0 .../query/cms/comments/job_comment_test.exs | 597 ++++++++++++++++++ .../post_comment_test.exs} | 26 +- ...ent_test.exs => old_post_comment_test.exs} | 2 +- 12 files changed, 630 insertions(+), 49 deletions(-) rename test/groupher_server_web/mutation/cms/{article_comment_test.exs => comments/post_comment_test.exs} (98%) rename test/groupher_server_web/mutation/cms/{post_comment_test.exs => old_post_comment_test.exs} (100%) create mode 100644 test/groupher_server_web/query/cms/comments/job_comment_test.exs rename test/groupher_server_web/query/cms/{article_comment_test.exs => comments/post_comment_test.exs} (96%) rename test/groupher_server_web/query/cms/{post_comment_test.exs => old_post_comment_test.exs} (99%) diff --git a/lib/groupher_server/cms/delegates/article_comment.ex b/lib/groupher_server/cms/delegates/article_comment.ex index 9bee1f4d3..c50c2f6d9 100644 --- a/lib/groupher_server/cms/delegates/article_comment.ex +++ b/lib/groupher_server/cms/delegates/article_comment.ex @@ -79,7 +79,6 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do {:ok, T.paged_users()} def list_article_comments_participators(thread, article_id, filters) do %{page: page, size: size} = filters - with {:ok, thread_query} <- match(thread, :query, article_id) do ArticleComment |> where(^thread_query) diff --git a/lib/groupher_server_web/schema/cms/cms_types.ex b/lib/groupher_server_web/schema/cms/cms_types.ex index 5094eac3e..f39da83ca 100644 --- a/lib/groupher_server_web/schema/cms/cms_types.ex +++ b/lib/groupher_server_web/schema/cms/cms_types.ex @@ -44,8 +44,6 @@ defmodule GroupherServerWeb.Schema.CMS.Types do field(:communities, list_of(:community), resolve: dataloader(CMS, :communities)) field(:meta, :article_meta) - field(:article_comments_participators, list_of(:user)) - field(:article_comments_participators_count, :integer) field :comments, list_of(:comment) do arg(:filter, :members_filter) @@ -69,6 +67,7 @@ defmodule GroupherServerWeb.Schema.CMS.Types do end) end + article_comments_fields() viewer_has_state_fields() # upvoted_count # collected_count @@ -99,6 +98,8 @@ defmodule GroupherServerWeb.Schema.CMS.Types do field(:origial_community, :community, resolve: dataloader(CMS, :origial_community)) field(:communities, list_of(:community), resolve: dataloader(CMS, :communities)) + field(:meta, :article_meta) + field(:salary, :string) field(:exp, :string) field(:education, :string) @@ -108,8 +109,7 @@ defmodule GroupherServerWeb.Schema.CMS.Types do # comments_count # comments_participators - comments_counter_fields(:job) - + article_comments_fields() viewer_has_state_fields() timestamp_fields() end @@ -154,7 +154,6 @@ defmodule GroupherServerWeb.Schema.CMS.Types do viewer_has_state_fields() # comments_count # comments_participators - comments_counter_fields(:repo) timestamp_fields() end diff --git a/lib/groupher_server_web/schema/utils/helper.ex b/lib/groupher_server_web/schema/utils/helper.ex index d06c5b6e8..afeb95bfa 100644 --- a/lib/groupher_server_web/schema/utils/helper.ex +++ b/lib/groupher_server_web/schema/utils/helper.ex @@ -191,6 +191,14 @@ defmodule GroupherServerWeb.Schema.Utils.Helper do end end + defmacro article_comments_fields do + quote do + field(:article_comments_participators, list_of(:user)) + field(:article_comments_participators_count, :integer) + field(:article_comments_count, :integer) + end + end + defmacro comments_counter_fields(thread) do quote do # @dec "total comments of the post" diff --git a/test/groupher_server/cms/comments/job_comment_emotions_test.exs b/test/groupher_server/cms/comments/job_comment_emotions_test.exs index ff19c4a6f..7c3cc5369 100644 --- a/test/groupher_server/cms/comments/job_comment_emotions_test.exs +++ b/test/groupher_server/cms/comments/job_comment_emotions_test.exs @@ -76,7 +76,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentEmotions do assert @default_emotions == emotions end - @tag :wip2 + @tag :wip3 test "can make emotion to comment", ~m(job user user2)a do parent_content = "parent comment" {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, parent_content, user) @@ -91,7 +91,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentEmotions do assert user_exist_in?(user2, emotions.latest_downvote_users) end - @tag :wip2 + @tag :wip3 test "can undo emotion to comment", ~m(job user user2)a do parent_content = "parent comment" {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, parent_content, user) @@ -114,7 +114,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentEmotions do assert not user_exist_in?(user2, emotions.latest_downvote_users) end - @tag :wip2 + @tag :wip3 test "same user make same emotion to same comment.", ~m(job user)a do parent_content = "parent comment" {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, parent_content, user) @@ -128,7 +128,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentEmotions do assert user_exist_in?(user, parent_comment.emotions.latest_downvote_users) end - @tag :wip2 + @tag :wip3 test "same user same emotion to same comment only have one user_emotion record", ~m(job user)a do parent_content = "parent comment" @@ -152,7 +152,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentEmotions do assert record.heart end - @tag :wip2 + @tag :wip3 test "different user can make same emotions on same comment", ~m(job user user2 user3)a do {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, "parent comment", user) diff --git a/test/groupher_server/cms/comments/job_comment_replies_test.exs b/test/groupher_server/cms/comments/job_comment_replies_test.exs index 66d154bd2..6a127ca62 100644 --- a/test/groupher_server/cms/comments/job_comment_replies_test.exs +++ b/test/groupher_server/cms/comments/job_comment_replies_test.exs @@ -64,7 +64,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentReplies do assert exist_in?(replyed_comment_2, parent_comment.replies) end - @tag :wip2 + @tag :wip3 test "reply to reply inside a comment should belong same parent comment", ~m(job user user2)a do {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, "parent comment", user) @@ -90,7 +90,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentReplies do assert replyed_comment_3.reply_to_id == replyed_comment_2.id end - @tag :wip2 + @tag :wip3 test "reply to reply inside a comment should have is_reply_to_others flag in meta", ~m(job user user2)a do {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, "parent comment", user) @@ -110,7 +110,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentReplies do assert replyed_comment_3.meta.is_reply_to_others end - @tag :wip2 + @tag :wip3 test "comment replies only contains @max_parent_replies_count replies", ~m(job user)a do total_reply_count = @max_parent_replies_count + 1 diff --git a/test/groupher_server/cms/comments/post_comment_emotions_test.exs b/test/groupher_server/cms/comments/post_comment_emotions_test.exs index 2b9ccdd4f..0e55ecfd3 100644 --- a/test/groupher_server/cms/comments/post_comment_emotions_test.exs +++ b/test/groupher_server/cms/comments/post_comment_emotions_test.exs @@ -76,7 +76,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentEmotions do assert @default_emotions == emotions end - @tag :wip2 + @tag :wip3 test "can make emotion to comment", ~m(post user user2)a do parent_content = "parent comment" {:ok, parent_comment} = CMS.create_article_comment(:post, post.id, parent_content, user) @@ -91,7 +91,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentEmotions do assert user_exist_in?(user2, emotions.latest_downvote_users) end - @tag :wip2 + @tag :wip3 test "can undo emotion to comment", ~m(post user user2)a do parent_content = "parent comment" {:ok, parent_comment} = CMS.create_article_comment(:post, post.id, parent_content, user) @@ -114,7 +114,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentEmotions do assert not user_exist_in?(user2, emotions.latest_downvote_users) end - @tag :wip2 + @tag :wip3 test "same user make same emotion to same comment.", ~m(post user)a do parent_content = "parent comment" {:ok, parent_comment} = CMS.create_article_comment(:post, post.id, parent_content, user) @@ -128,7 +128,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentEmotions do assert user_exist_in?(user, parent_comment.emotions.latest_downvote_users) end - @tag :wip2 + @tag :wip3 test "same user same emotion to same comment only have one user_emotion record", ~m(post user)a do parent_content = "parent comment" @@ -152,7 +152,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentEmotions do assert record.heart end - @tag :wip2 + @tag :wip3 test "different user can make same emotions on same comment", ~m(post user user2 user3)a do {:ok, parent_comment} = CMS.create_article_comment(:post, post.id, "parent comment", user) diff --git a/test/groupher_server/cms/comments/post_comment_replies_test.exs b/test/groupher_server/cms/comments/post_comment_replies_test.exs index acf9eb8b2..ab96e2ddc 100644 --- a/test/groupher_server/cms/comments/post_comment_replies_test.exs +++ b/test/groupher_server/cms/comments/post_comment_replies_test.exs @@ -64,7 +64,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentReplies do assert exist_in?(replyed_comment_2, parent_comment.replies) end - @tag :wip2 + @tag :wip3 test "reply to reply inside a comment should belong same parent comment", ~m(post user user2)a do {:ok, parent_comment} = CMS.create_article_comment(:post, post.id, "parent comment", user) @@ -90,7 +90,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentReplies do assert replyed_comment_3.reply_to_id == replyed_comment_2.id end - @tag :wip2 + @tag :wip3 test "reply to reply inside a comment should have is_reply_to_others flag in meta", ~m(post user user2)a do {:ok, parent_comment} = CMS.create_article_comment(:post, post.id, "parent comment", user) @@ -110,7 +110,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentReplies do assert replyed_comment_3.meta.is_reply_to_others end - @tag :wip2 + @tag :wip3 test "comment replies only contains @max_parent_replies_count replies", ~m(post user)a do total_reply_count = @max_parent_replies_count + 1 diff --git a/test/groupher_server_web/mutation/cms/article_comment_test.exs b/test/groupher_server_web/mutation/cms/comments/post_comment_test.exs similarity index 98% rename from test/groupher_server_web/mutation/cms/article_comment_test.exs rename to test/groupher_server_web/mutation/cms/comments/post_comment_test.exs index f045bbc63..522997d4b 100644 --- a/test/groupher_server_web/mutation/cms/article_comment_test.exs +++ b/test/groupher_server_web/mutation/cms/comments/post_comment_test.exs @@ -1,4 +1,4 @@ -defmodule GroupherServer.Test.Mutation.ArticleComment do +defmodule GroupherServer.Test.Mutation.Comments.PostComment do use GroupherServer.TestTools alias GroupherServer.CMS @@ -130,7 +130,7 @@ defmodule GroupherServer.Test.Mutation.ArticleComment do } } """ - @tag :wip2 + @tag :wip3 test "login user can undo emotion to a comment", ~m(post user owner_conn)a do {:ok, comment} = CMS.create_article_comment(:post, post.id, "post comment", user) {:ok, _} = CMS.emotion_to_comment(comment.id, :beer, user) diff --git a/test/groupher_server_web/mutation/cms/post_comment_test.exs b/test/groupher_server_web/mutation/cms/old_post_comment_test.exs similarity index 100% rename from test/groupher_server_web/mutation/cms/post_comment_test.exs rename to test/groupher_server_web/mutation/cms/old_post_comment_test.exs diff --git a/test/groupher_server_web/query/cms/comments/job_comment_test.exs b/test/groupher_server_web/query/cms/comments/job_comment_test.exs new file mode 100644 index 000000000..f74f82b01 --- /dev/null +++ b/test/groupher_server_web/query/cms/comments/job_comment_test.exs @@ -0,0 +1,597 @@ +defmodule GroupherServer.Test.Query.Comments.JobComment do + @moduledoc false + + use GroupherServer.TestTools + + alias GroupherServer.CMS + + setup do + {:ok, job} = db_insert(:job) + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user, user) + + {:ok, ~m(user_conn guest_conn job user user2)a} + end + + describe "[baisc article job comment]" do + @query """ + query($id: ID!) { + job(id: $id) { + id + title + body + articleCommentsParticipators { + id + nickname + } + articleCommentsParticipatorsCount + } + } + """ + @tag :wip2 + test "guest user can get comment participators after comment created", + ~m(guest_conn job user user2)a do + comment = "test comment" + total_count = 5 + thread = :job + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_article_comment(thread, job.id, comment, user) + + acc ++ [comment] + end) + + {:ok, _} = CMS.create_article_comment(thread, job.id, comment, user2) + + variables = %{id: job.id} + results = guest_conn |> query_result(@query, variables, "job") + + article_comments_participators = results["articleCommentsParticipators"] + article_comments_participators_count = results["articleCommentsParticipatorsCount"] + + assert is_list(article_comments_participators) + assert length(article_comments_participators) == 2 + assert article_comments_participators_count == 2 + end + + @query """ + query($id: ID!, $thread: CmsThread, $mode: ArticleCommentsMode, $filter: CommentsFilter!) { + pagedArticleComments(id: $id, thread: $thread, mode: $mode, filter: $filter) { + entries { + id + bodyHtml + author { + id + nickname + } + isPinned + floor + upvotesCount + + emotions { + downvoteCount + latestDownvoteUsers { + login + nickname + } + viewerHasDownvoteed + beerCount + latestBeerUsers { + login + nickname + } + viewerHasBeered + + popcornCount + viewerHasPopcorned + } + isArticleAuthor + meta { + isArticleAuthorUpvoted + } + replyTo { + id + bodyHtml + floor + isArticleAuthor + author { + id + login + } + } + viewerHasUpvoted + replies { + id + bodyHtml + author { + id + login + } + } + repliesCount + } + totalPages + totalCount + pageSize + pageNumber + } + } + """ + @tag :wip2 + test "list comments with default replies-mode", ~m(guest_conn job user user2)a do + total_count = 10 + page_size = 20 + thread = :job + + all_comments = + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, comment} = CMS.create_article_comment(thread, job.id, "comment #{i}", user) + acc ++ [comment] + end) + + random_comment = all_comments |> Enum.at(Enum.random(0..(total_count - 1))) + {:ok, replyed_comment_1} = CMS.reply_article_comment(random_comment.id, "reply 1", user2) + {:ok, replyed_comment_2} = CMS.reply_article_comment(random_comment.id, "reply 2", user2) + + variables = %{id: job.id, thread: "JOB", filter: %{page: 1, size: page_size}} + results = guest_conn |> query_result(@query, variables, "pagedArticleComments") + assert results["entries"] |> length == total_count + + assert not exist_in?(replyed_comment_1, results["entries"], :string_key) + assert not exist_in?(replyed_comment_2, results["entries"], :string_key) + + random_comment = Enum.find(results["entries"], &(&1["id"] == to_string(random_comment.id))) + assert random_comment["replies"] |> length == 2 + assert random_comment["repliesCount"] == 2 + + assert random_comment["replies"] |> List.first() |> Map.get("id") == + to_string(replyed_comment_1.id) + + assert random_comment["replies"] |> List.last() |> Map.get("id") == + to_string(replyed_comment_2.id) + end + + @tag :wip + test "timeline-mode paged comments", ~m(guest_conn job user user2)a do + total_count = 3 + page_size = 20 + thread = :job + + all_comments = + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, comment} = CMS.create_article_comment(thread, job.id, "comment #{i}", user) + acc ++ [comment] + end) + + random_comment = all_comments |> Enum.at(Enum.random(0..(total_count - 1))) + + {:ok, replyed_comment_1} = CMS.reply_article_comment(random_comment.id, "reply 1", user2) + {:ok, replyed_comment_2} = CMS.reply_article_comment(random_comment.id, "reply 2", user2) + + variables = %{ + id: job.id, + thread: "JOB", + mode: "TIMELINE", + filter: %{page: 1, size: page_size} + } + + results = guest_conn |> query_result(@query, variables, "pagedArticleComments") + assert results["entries"] |> length == total_count + 2 + + assert exist_in?(replyed_comment_1, results["entries"], :string_key) + assert exist_in?(replyed_comment_2, results["entries"], :string_key) + + random_comment = Enum.find(results["entries"], &(&1["id"] == to_string(random_comment.id))) + assert random_comment["replies"] |> length == 2 + assert random_comment["repliesCount"] == 2 + end + + @tag :wip + test "comment should have reply_to content if need", ~m(guest_conn job user user2)a do + total_count = 2 + thread = :job + + Enum.reduce(0..total_count, [], fn i, acc -> + {:ok, comment} = CMS.create_article_comment(thread, job.id, "comment #{i}", user) + acc ++ [comment] + end) + + {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, "parent_content", user) + + {:ok, replyed_comment_1} = CMS.reply_article_comment(parent_comment.id, "reply 1", user2) + {:ok, replyed_comment_2} = CMS.reply_article_comment(parent_comment.id, "reply 2", user2) + + variables = %{id: job.id, thread: "JOB", filter: %{page: 1, size: 10}, mode: "TIMELINE"} + results = guest_conn |> query_result(@query, variables, "pagedArticleComments") + + replyed_comment_1 = + Enum.find(results["entries"], &(&1["id"] == to_string(replyed_comment_1.id))) + + assert replyed_comment_1 |> get_in(["replyTo", "id"]) == to_string(parent_comment.id) + + assert replyed_comment_1 |> get_in(["replyTo", "author", "id"]) == + to_string(parent_comment.author_id) + + replyed_comment_2 = + Enum.find(results["entries"], &(&1["id"] == to_string(replyed_comment_2.id))) + + assert replyed_comment_2 |> get_in(["replyTo", "id"]) == to_string(parent_comment.id) + + assert replyed_comment_2 |> get_in(["replyTo", "author", "id"]) == + to_string(parent_comment.author_id) + end + + @tag :wip + test "guest user can get paged comment for job", ~m(guest_conn job user)a do + comment = "test comment" + total_count = 30 + thread = :job + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, value} = CMS.create_article_comment(thread, job.id, comment, user) + + acc ++ [value] + end) + + variables = %{id: job.id, thread: "JOB", filter: %{page: 1, size: 10}} + results = guest_conn |> query_result(@query, variables, "pagedArticleComments") + + assert results |> is_valid_pagination? + assert results["totalCount"] == total_count + end + + @tag :wip + test "guest user can get paged comment with pined comment in it", ~m(guest_conn job user)a do + total_count = 20 + thread = :job + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_article_comment(thread, job.id, "test comment", user) + + acc ++ [comment] + end) + + {:ok, comment} = CMS.create_article_comment(thread, job.id, "pined comment", user) + {:ok, pined_comment} = CMS.pin_article_comment(comment.id) + + {:ok, comment} = CMS.create_article_comment(thread, job.id, "pined comment 2", user) + {:ok, pined_comment2} = CMS.pin_article_comment(comment.id) + + variables = %{id: job.id, thread: "JOB", filter: %{page: 1, size: 10}} + results = guest_conn |> query_result(@query, variables, "pagedArticleComments") + + assert results["entries"] |> List.first() |> Map.get("id") == to_string(pined_comment.id) + assert results["entries"] |> Enum.at(1) |> Map.get("id") == to_string(pined_comment2.id) + + assert results["totalCount"] == total_count + 2 + end + + @tag :wip + test "guest user can get paged comment with floor it", ~m(guest_conn job user)a do + total_count = 5 + thread = :job + page_size = 10 + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, comment} = CMS.create_article_comment(thread, job.id, "test comment", user) + Process.sleep(1000) + acc ++ [comment] + end) + + variables = %{id: job.id, thread: "JOB", filter: %{page: 1, size: page_size}} + results = guest_conn |> query_result(@query, variables, "pagedArticleComments") + + assert results["entries"] |> List.first() |> Map.get("floor") == 1 + assert results["entries"] |> List.last() |> Map.get("floor") == 5 + end + + @tag :wip + test "the comments is loaded in asc order", ~m(guest_conn job user)a do + page_size = 10 + thread = :job + + {:ok, comment} = CMS.create_article_comment(thread, job.id, "test comment #{1}", user) + Process.sleep(1000) + {:ok, _comment2} = CMS.create_article_comment(thread, job.id, "test comment #{2}", user) + Process.sleep(1000) + {:ok, comment3} = CMS.create_article_comment(thread, job.id, "test comment #{3}", user) + + variables = %{id: job.id, thread: "JOB", filter: %{page: 1, size: page_size}} + results = guest_conn |> query_result(@query, variables, "pagedArticleComments") + + assert List.first(results["entries"]) |> Map.get("id") == to_string(comment.id) + assert List.last(results["entries"]) |> Map.get("id") == to_string(comment3.id) + end + + @tag :wip + test "guest user can get paged comment with upvotes_count", ~m(guest_conn job user user2)a do + total_count = 10 + page_size = 10 + thread = :job + + all_comment = + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, comment} = CMS.create_article_comment(thread, job.id, "test comment #{i}", user) + Process.sleep(1000) + acc ++ [comment] + end) + + upvote_comment = all_comment |> Enum.at(3) + upvote_comment2 = all_comment |> Enum.at(4) + {:ok, _} = CMS.upvote_article_comment(upvote_comment.id, user) + {:ok, _} = CMS.upvote_article_comment(upvote_comment2.id, user) + {:ok, _} = CMS.upvote_article_comment(upvote_comment2.id, user2) + + variables = %{id: job.id, thread: "JOB", filter: %{page: 1, size: page_size}} + results = guest_conn |> query_result(@query, variables, "pagedArticleComments") + + assert results["entries"] |> Enum.at(3) |> Map.get("upvotesCount") == 1 + assert results["entries"] |> Enum.at(4) |> Map.get("upvotesCount") == 2 + assert results["entries"] |> List.first() |> Map.get("upvotesCount") == 0 + assert results["entries"] |> List.last() |> Map.get("upvotesCount") == 0 + end + + @tag :wip + test "article author upvote a comment can get is_article_author and/or is_article_author_upvoted flag", + ~m(guest_conn job user)a do + total_count = 5 + page_size = 12 + thread = :job + + author_user = job.author.user + + all_comments = + Enum.reduce(0..total_count, [], fn i, acc -> + {:ok, comment} = CMS.create_article_comment(thread, job.id, "test comment #{i}", user) + acc ++ [comment] + end) + + random_comment = all_comments |> Enum.at(Enum.random(0..(total_count - 1))) + {:ok, _} = CMS.upvote_article_comment(random_comment.id, author_user) + + {:ok, author_comment} = + CMS.create_article_comment(thread, job.id, "test comment", author_user) + + {:ok, _} = CMS.upvote_article_comment(author_comment.id, author_user) + + variables = %{id: job.id, thread: "JOB", filter: %{page: 1, size: page_size}} + results = guest_conn |> query_result(@query, variables, "pagedArticleComments") + + the_author_comment = + Enum.find(results["entries"], &(&1["id"] == to_string(author_comment.id))) + + assert the_author_comment["isArticleAuthor"] + assert the_author_comment |> get_in(["meta", "isArticleAuthorUpvoted"]) + + the_random_comment = + Enum.find(results["entries"], &(&1["id"] == to_string(random_comment.id))) + + assert not the_random_comment["isArticleAuthor"] + assert the_random_comment |> get_in(["meta", "isArticleAuthorUpvoted"]) + end + + @tag :wip3 + test "guest user can get paged comment with emotions info", + ~m(guest_conn job user user2)a do + total_count = 2 + page_size = 10 + thread = :job + + all_comment = + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, comment} = CMS.create_article_comment(thread, job.id, "test comment #{i}", user) + Process.sleep(1000) + acc ++ [comment] + end) + + comment = all_comment |> Enum.at(0) + comment2 = all_comment |> Enum.at(1) + + {:ok, _} = CMS.emotion_to_comment(comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(comment.id, :downvote, user2) + {:ok, _} = CMS.emotion_to_comment(comment2.id, :beer, user2) + + variables = %{id: job.id, thread: "JOB", filter: %{page: 1, size: page_size}} + results = guest_conn |> query_result(@query, variables, "pagedArticleComments") + + comment_emotion = + Enum.find(results["entries"], &(&1["id"] == to_string(comment.id))) |> Map.get("emotions") + + assert comment_emotion["popcornCount"] == 0 + + assert comment_emotion["downvoteCount"] == 2 + assert comment_emotion["latestDownvoteUsers"] |> length == 2 + assert not comment_emotion["viewerHasDownvoteed"] + + latest_downvote_users_logins = + Enum.map(comment_emotion["latestDownvoteUsers"], & &1["login"]) + + assert user.login in latest_downvote_users_logins + assert user2.login in latest_downvote_users_logins + + comment2_emotion = + Enum.find(results["entries"], &(&1["id"] == to_string(comment2.id))) + |> Map.get("emotions") + + assert comment2_emotion["beerCount"] == 1 + assert comment2_emotion["latestBeerUsers"] |> length == 1 + assert not comment2_emotion["viewerHasBeered"] + + latest_beer_users_logins = Enum.map(comment2_emotion["latestBeerUsers"], & &1["login"]) + assert user2.login in latest_beer_users_logins + end + + @tag :wip + test "user make emotion can get paged comment with emotions has_motioned field", + ~m(user_conn job user user2)a do + total_count = 10 + page_size = 12 + thread = :job + + all_comment = + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, comment} = CMS.create_article_comment(thread, job.id, "test comment #{i}", user) + Process.sleep(1000) + acc ++ [comment] + end) + + comment = all_comment |> Enum.at(0) + comment2 = all_comment |> Enum.at(1) + + {:ok, _} = CMS.emotion_to_comment(comment.id, :downvote, user) + {:ok, _} = CMS.emotion_to_comment(comment2.id, :downvote, user2) + + variables = %{id: job.id, thread: "JOB", filter: %{page: 1, size: page_size}} + results = user_conn |> query_result(@query, variables, "pagedArticleComments") + + assert Enum.find(results["entries"], &(&1["id"] == to_string(comment.id))) + |> get_in(["emotions", "viewerHasDownvoteed"]) + end + + @tag :wip3 + test "comment should have viewer has upvoted flag", ~m(user_conn job user)a do + total_count = 10 + page_size = 12 + thread = :job + + all_comments = + Enum.reduce(0..total_count, [], fn i, acc -> + {:ok, comment} = CMS.create_article_comment(thread, job.id, "comment #{i}", user) + acc ++ [comment] + end) + + random_comment = all_comments |> Enum.at(Enum.random(0..(total_count - 1))) + + {:ok, _} = CMS.upvote_article_comment(random_comment.id, user) + + variables = %{id: job.id, thread: "JOB", filter: %{page: 1, size: page_size}} + results = user_conn |> query_result(@query, variables, "pagedArticleComments") + + upvoted_comment = Enum.find(results["entries"], &(&1["id"] == to_string(random_comment.id))) + + assert upvoted_comment["viewerHasUpvoted"] + end + end + + describe "paged paticipators" do + @query """ + query($id: ID!, $thread: CmsThread, $filter: PagedFilter!) { + pagedArticleCommentsParticipators(id: $id, thread: $thread, filter: $filter) { + entries { + id + nickname + } + totalPages + totalCount + pageSize + pageNumber + } + } + """ + @tag :wip2 + test "guest user can get paged participators", ~m(guest_conn job user)a do + total_count = 30 + page_size = 10 + thread = "JOB" + + Enum.reduce(1..total_count, [], fn _, acc -> + {:ok, new_user} = db_insert(:user) + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", new_user) + + acc ++ [comment] + end) + + {:ok, _comment} = CMS.create_article_comment(:job, job.id, "commment", user) + {:ok, _comment} = CMS.create_article_comment(:job, job.id, "commment", user) + + variables = %{id: job.id, thread: thread, filter: %{page: 1, size: page_size}} + results = guest_conn |> query_result(@query, variables, "pagedArticleCommentsParticipators") + + assert results |> is_valid_pagination? + assert results["totalCount"] == total_count + 1 + end + end + + describe "paged replies" do + @query """ + query($id: ID!, $filter: CommentsFilter!) { + pagedCommentReplies(id: $id, filter: $filter) { + entries { + id + bodyHtml + author { + id + nickname + } + upvotesCount + emotions { + downvoteCount + latestDownvoteUsers { + login + nickname + } + viewerHasDownvoteed + beerCount + latestBeerUsers { + login + nickname + } + viewerHasBeered + } + isArticleAuthor + meta { + isArticleAuthorUpvoted + } + replyTo { + id + bodyHtml + floor + isArticleAuthor + author { + id + login + } + } + repliesCount + viewerHasUpvoted + } + totalPages + totalCount + pageSize + pageNumber + } + } + """ + @tag :wip + test "guest user can get paged replies", ~m(guest_conn job user user2)a do + comment = "test comment" + total_count = 2 + page_size = 10 + thread = :job + + author_user = job.author.user + {:ok, parent_comment} = CMS.create_article_comment(thread, job.id, comment, user) + + Enum.reduce(1..total_count, [], fn i, acc -> + {:ok, reply_comment} = CMS.reply_article_comment(parent_comment.id, "reply #{i}", user2) + + acc ++ [reply_comment] + end) + + {:ok, author_reply_comment} = + CMS.reply_article_comment(parent_comment.id, "author reply", author_user) + + variables = %{id: parent_comment.id, filter: %{page: 1, size: page_size}} + results = guest_conn |> query_result(@query, variables, "pagedCommentReplies") + + author_reply_comment = + Enum.find(results["entries"], &(&1["id"] == to_string(author_reply_comment.id))) + + assert author_reply_comment["isArticleAuthor"] + assert results["entries"] |> length == total_count + 1 + end + end +end diff --git a/test/groupher_server_web/query/cms/article_comment_test.exs b/test/groupher_server_web/query/cms/comments/post_comment_test.exs similarity index 96% rename from test/groupher_server_web/query/cms/article_comment_test.exs rename to test/groupher_server_web/query/cms/comments/post_comment_test.exs index 35e0626b6..8e1dcb613 100644 --- a/test/groupher_server_web/query/cms/article_comment_test.exs +++ b/test/groupher_server_web/query/cms/comments/post_comment_test.exs @@ -1,4 +1,4 @@ -defmodule GroupherServer.Test.Query.ArticleComment do +defmodule GroupherServer.Test.Query.Comments.PostComment do @moduledoc false use GroupherServer.TestTools @@ -7,14 +7,13 @@ defmodule GroupherServer.Test.Query.ArticleComment do setup do {:ok, post} = db_insert(:post) - {:ok, job} = db_insert(:job) {:ok, user} = db_insert(:user) {:ok, user2} = db_insert(:user) guest_conn = simu_conn(:guest) user_conn = simu_conn(:user, user) - {:ok, ~m(user_conn guest_conn post job user user2)a} + {:ok, ~m(user_conn guest_conn post user user2)a} end describe "[baisc article post comment]" do @@ -244,27 +243,6 @@ defmodule GroupherServer.Test.Query.ArticleComment do assert results["totalCount"] == total_count end - @tag :wip - test "guest user can get paged comment for job", ~m(guest_conn job user)a do - comment = "test comment" - total_count = 30 - thread = :job - - Enum.reduce(1..total_count, [], fn _, acc -> - {:ok, value} = CMS.create_article_comment(thread, job.id, comment, user) - - acc ++ [value] - end) - - variables = %{id: job.id, thread: "JOB", filter: %{page: 1, size: 10}} - results = guest_conn |> query_result(@query, variables, "pagedArticleComments") - - # IO.inspect(results, label: "results-") - - assert results |> is_valid_pagination? - assert results["totalCount"] == total_count - end - @tag :wip test "guest user can get paged comment with pined comment in it", ~m(guest_conn post user)a do total_count = 20 diff --git a/test/groupher_server_web/query/cms/post_comment_test.exs b/test/groupher_server_web/query/cms/old_post_comment_test.exs similarity index 99% rename from test/groupher_server_web/query/cms/post_comment_test.exs rename to test/groupher_server_web/query/cms/old_post_comment_test.exs index a33918d6a..f9d694b87 100644 --- a/test/groupher_server_web/query/cms/post_comment_test.exs +++ b/test/groupher_server_web/query/cms/old_post_comment_test.exs @@ -1,4 +1,4 @@ -defmodule GroupherServer.Test.Query.PostComment do +defmodule GroupherServer.Test.Query.OldPostComment do use GroupherServer.TestTools alias GroupherServer.CMS From 53a5a1fe255836de3e0ed7886c3f561257e84dc4 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 8 May 2021 16:54:10 +0800 Subject: [PATCH 6/7] refactor(article-comment): wip --- lib/groupher_server/cms/delegates/article_comment.ex | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/groupher_server/cms/delegates/article_comment.ex b/lib/groupher_server/cms/delegates/article_comment.ex index c50c2f6d9..9bee1f4d3 100644 --- a/lib/groupher_server/cms/delegates/article_comment.ex +++ b/lib/groupher_server/cms/delegates/article_comment.ex @@ -79,6 +79,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do {:ok, T.paged_users()} def list_article_comments_participators(thread, article_id, filters) do %{page: page, size: size} = filters + with {:ok, thread_query} <- match(thread, :query, article_id) do ArticleComment |> where(^thread_query) From 78017381a5095a7c1cdb30a6a9bba2e975b1c2cc Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 8 May 2021 17:12:23 +0800 Subject: [PATCH 7/7] chore(comments): tests re-org --- .../cms/delegates/article_comment_action.ex | 2 +- .../resolvers/cms_resolver.ex | 4 + .../schema/cms/mutations/comment.ex | 11 ++ .../cms/comments/job_comment_test.exs | 167 ++++++++++++++++++ .../cms/comments/post_comment_test.exs | 20 +++ 5 files changed, 203 insertions(+), 1 deletion(-) create mode 100644 test/groupher_server_web/mutation/cms/comments/job_comment_test.exs diff --git a/lib/groupher_server/cms/delegates/article_comment_action.ex b/lib/groupher_server/cms/delegates/article_comment_action.ex index 9ea8eae2a..fb32a5d07 100644 --- a/lib/groupher_server/cms/delegates/article_comment_action.ex +++ b/lib/groupher_server/cms/delegates/article_comment_action.ex @@ -22,7 +22,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCommentAction do ArticlePinedComment, ArticleCommentUpvote, ArticleCommentReply, - Embeds, + # TODO: remove spec type Post, Job } diff --git a/lib/groupher_server_web/resolvers/cms_resolver.ex b/lib/groupher_server_web/resolvers/cms_resolver.ex index ad11ffd9a..4dd8c86ef 100644 --- a/lib/groupher_server_web/resolvers/cms_resolver.ex +++ b/lib/groupher_server_web/resolvers/cms_resolver.ex @@ -322,6 +322,10 @@ defmodule GroupherServerWeb.Resolvers.CMS do CMS.delete_article_comment(comment) end + def reply_article_comment(_root, ~m(id content)a, %{context: %{cur_user: user}}) do + CMS.reply_article_comment(id, content, user) + end + def emotion_to_comment(_root, ~m(id emotion)a, %{context: %{cur_user: user}}) do CMS.emotion_to_comment(id, emotion, user) end diff --git a/lib/groupher_server_web/schema/cms/mutations/comment.ex b/lib/groupher_server_web/schema/cms/mutations/comment.ex index d1ed51030..ba54b2346 100644 --- a/lib/groupher_server_web/schema/cms/mutations/comment.ex +++ b/lib/groupher_server_web/schema/cms/mutations/comment.ex @@ -44,6 +44,17 @@ defmodule GroupherServerWeb.Schema.CMS.Mutations.Comment do resolve(&R.CMS.delete_article_comment/3) end + @desc "reply to a comment" + field :reply_article_comment, :article_comment do + arg(:id, non_null(:id)) + arg(:content, non_null(:string)) + + middleware(M.Authorize, :login) + # TODO: 文章作者可以删除评论,文章可以设置禁止评论 + resolve(&R.CMS.reply_article_comment/3) + middleware(M.Statistics.MakeContribute, for: :user) + end + @desc "emotion to a comment" field :emotion_to_comment, :article_comment do arg(:id, non_null(:id)) diff --git a/test/groupher_server_web/mutation/cms/comments/job_comment_test.exs b/test/groupher_server_web/mutation/cms/comments/job_comment_test.exs new file mode 100644 index 000000000..787b5cfea --- /dev/null +++ b/test/groupher_server_web/mutation/cms/comments/job_comment_test.exs @@ -0,0 +1,167 @@ +defmodule GroupherServer.Test.Mutation.Comments.JobComment do + use GroupherServer.TestTools + + alias GroupherServer.CMS + + setup do + {:ok, job} = db_insert(:job) + {:ok, user} = db_insert(:user) + {:ok, community} = db_insert(:community) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user) + owner_conn = simu_conn(:user, user) + + {:ok, ~m(user_conn user guest_conn owner_conn community job)a} + end + + describe "[article comment CURD]" do + @write_comment_query """ + mutation($thread: CmsThread!, $id: ID!, $content: String!) { + createArticleComment(thread: $thread,id: $id, content: $content) { + id + bodyHtml + } + } + """ + @tag :wip3 + test "write article comment to a exsit job", ~m(job user_conn)a do + comment = "a test comment" + variables = %{thread: "JOB", id: job.id, content: comment} + + result = + user_conn |> mutation_result(@write_comment_query, variables, "createArticleComment") + + assert result["bodyHtml"] == comment + end + + @reply_comment_query """ + mutation($id: ID!, $content: String!) { + replyArticleComment(id: $id, content: $content) { + id + bodyHtml + } + } + """ + @tag :wip2 + test "login user can reply to a comment", ~m(job user user_conn)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + variables = %{id: comment.id, content: "reply content"} + + result = + user_conn + |> mutation_result(@reply_comment_query, variables, "replyArticleComment") + + assert result["bodyHtml"] == "reply content" + end + + @update_comment_query """ + mutation($id: ID!, $content: String!) { + updateArticleComment(id: $id, content: $content) { + id + bodyHtml + } + } + """ + @tag :wip3 + test "only owner can update a exsit comment", + ~m(job user guest_conn user_conn owner_conn)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "job comment", user) + variables = %{id: comment.id, content: "updated comment"} + + assert user_conn |> mutation_get_error?(@update_comment_query, variables, ecode(:passport)) + + assert guest_conn + |> mutation_get_error?(@update_comment_query, variables, ecode(:account_login)) + + updated = + owner_conn |> mutation_result(@update_comment_query, variables, "updateArticleComment") + + assert updated["bodyHtml"] == "updated comment" + end + + @delete_comment_query """ + mutation($id: ID!) { + deleteArticleComment(id: $id) { + id + isDeleted + } + } + """ + @tag :wip3 + test "only owner can delete a exsit comment", + ~m(job user guest_conn user_conn owner_conn)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "job comment", user) + variables = %{id: comment.id} + + assert user_conn |> mutation_get_error?(@delete_comment_query, variables, ecode(:passport)) + + assert guest_conn + |> mutation_get_error?(@delete_comment_query, variables, ecode(:account_login)) + + deleted = + owner_conn |> mutation_result(@delete_comment_query, variables, "deleteArticleComment") + + assert deleted["id"] == to_string(comment.id) + assert deleted["isDeleted"] + end + end + + describe "[article comment emotion]" do + @emotion_comment_query """ + mutation($id: ID!, $emotion: ArticleCommentEmotion!) { + emotionToComment(id: $id, emotion: $emotion) { + id + emotions { + beerCount + viewerHasBeered + latestBeerUsers { + login + nickname + } + } + } + } + """ + @tag :wip3 + test "login user can emotion to a comment", ~m(job user user_conn)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "job comment", user) + variables = %{id: comment.id, emotion: "BEER"} + + comment = + user_conn |> mutation_result(@emotion_comment_query, variables, "emotionToComment") + + assert comment |> get_in(["emotions", "beerCount"]) == 1 + assert get_in(comment, ["emotions", "viewerHasBeered"]) + end + + @emotion_comment_query """ + mutation($id: ID!, $emotion: ArticleCommentEmotion!) { + undoEmotionToComment(id: $id, emotion: $emotion) { + id + emotions { + beerCount + viewerHasBeered + latestBeerUsers { + login + nickname + } + } + } + } + """ + @tag :wip3 + test "login user can undo emotion to a comment", ~m(job user owner_conn)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "job comment", user) + {:ok, _} = CMS.emotion_to_comment(comment.id, :beer, user) + + variables = %{id: comment.id, emotion: "BEER"} + + comment = + owner_conn |> mutation_result(@emotion_comment_query, variables, "undoEmotionToComment") + + assert comment |> get_in(["emotions", "beerCount"]) == 0 + assert not get_in(comment, ["emotions", "viewerHasBeered"]) + end + end +end diff --git a/test/groupher_server_web/mutation/cms/comments/post_comment_test.exs b/test/groupher_server_web/mutation/cms/comments/post_comment_test.exs index 522997d4b..c28013669 100644 --- a/test/groupher_server_web/mutation/cms/comments/post_comment_test.exs +++ b/test/groupher_server_web/mutation/cms/comments/post_comment_test.exs @@ -35,6 +35,26 @@ defmodule GroupherServer.Test.Mutation.Comments.PostComment do assert result["bodyHtml"] == comment end + @reply_comment_query """ + mutation($id: ID!, $content: String!) { + replyArticleComment(id: $id, content: $content) { + id + bodyHtml + } + } + """ + @tag :wip2 + test "login user can reply to a comment", ~m(post user user_conn)a do + {:ok, comment} = CMS.create_article_comment(:post, post.id, "commment", user) + variables = %{id: comment.id, content: "reply content"} + + result = + user_conn + |> mutation_result(@reply_comment_query, variables, "replyArticleComment") + + assert result["bodyHtml"] == "reply content" + end + @update_comment_query """ mutation($id: ID!, $content: String!) { updateArticleComment(id: $id, content: $content) {