diff --git a/config/config.exs b/config/config.exs index fcd5f1437..1a8e4ef58 100644 --- a/config/config.exs +++ b/config/config.exs @@ -61,6 +61,8 @@ config :groupher_server, :customization, sidebar_communities_index: %{} config :groupher_server, :article, + # NOTE: do not change unless you know what you are doing + article_threads: [:post, :job, :repo], # NOTE: if you want to add/remove emotion, just edit the list below # and migrate the field to table "articles_users_emotions" supported_emotions: [ diff --git a/lib/groupher_server/application.ex b/lib/groupher_server/application.ex index b7fccf212..ca13da300 100644 --- a/lib/groupher_server/application.ex +++ b/lib/groupher_server/application.ex @@ -7,8 +7,6 @@ defmodule GroupherServer.Application do @spec start(any, any) :: {:error, any} | {:ok, pid} def start(_type, _args) do import Supervisor.Spec - import Cachex.Spec - alias Helper.Cache # Define workers and child supervisors to be supervised diff --git a/lib/groupher_server/cms/abuse_report.ex b/lib/groupher_server/cms/abuse_report.ex index 24a10cf60..85146601a 100644 --- a/lib/groupher_server/cms/abuse_report.ex +++ b/lib/groupher_server/cms/abuse_report.ex @@ -4,14 +4,16 @@ defmodule GroupherServer.CMS.AbuseReport do use Ecto.Schema use Accessible + import Ecto.Changeset + import Helper.Utils, only: [get_config: 2] import GroupherServer.CMS.Helper.Macros import GroupherServer.CMS.Helper.Utils, only: [articles_foreign_key_constraint: 1] alias GroupherServer.{Accounts, CMS} alias CMS.{ArticleComment, Embeds} - @article_threads CMS.Community.article_threads() + @article_threads get_config(:article, :article_threads) # @required_fields ~w(article_comment_id user_id recived_user_id)a @optional_fields ~w(article_comment_id account_id operate_user_id deal_with report_cases_count)a @@ -31,7 +33,7 @@ defmodule GroupherServer.CMS.AbuseReport do field(:deal_with, :string) - article_belongs_to() + article_belongs_to_fields() timestamps(type: :utc_datetime) end diff --git a/lib/groupher_server/cms/article_collect.ex b/lib/groupher_server/cms/article_collect.ex index 92b9f7c78..723dcd232 100644 --- a/lib/groupher_server/cms/article_collect.ex +++ b/lib/groupher_server/cms/article_collect.ex @@ -3,14 +3,16 @@ defmodule GroupherServer.CMS.ArticleCollect do alias __MODULE__ use Ecto.Schema + import Ecto.Changeset + import Helper.Utils, only: [get_config: 2] import GroupherServer.CMS.Helper.Macros import GroupherServer.CMS.Helper.Utils, only: [articles_foreign_key_constraint: 1] alias GroupherServer.{Accounts, CMS} alias Accounts.{User, CollectFolder} - @article_threads CMS.Community.article_threads() + @article_threads get_config(:article, :article_threads) @required_fields ~w(user_id)a @optional_fields ~w(thread)a @@ -23,7 +25,7 @@ defmodule GroupherServer.CMS.ArticleCollect do belongs_to(:user, User, foreign_key: :user_id) embeds_many(:collect_folders, CollectFolder, on_replace: :delete) - article_belongs_to() + article_belongs_to_fields() timestamps(type: :utc_datetime) end diff --git a/lib/groupher_server/cms/article_comment.ex b/lib/groupher_server/cms/article_comment.ex index 850e275f5..c8055d239 100644 --- a/lib/groupher_server/cms/article_comment.ex +++ b/lib/groupher_server/cms/article_comment.ex @@ -6,6 +6,7 @@ defmodule GroupherServer.CMS.ArticleComment do use Accessible import Ecto.Changeset + import Helper.Utils, only: [get_config: 2] import GroupherServer.CMS.Helper.Macros import GroupherServer.CMS.Helper.Utils, only: [articles_foreign_key_constraint: 1] @@ -13,7 +14,7 @@ defmodule GroupherServer.CMS.ArticleComment do alias CMS.{Embeds, ArticleCommentUpvote} # alias Helper.HTML - @article_threads CMS.Community.article_threads() + @article_threads get_config(:article, :article_threads) @required_fields ~w(body_html author_id)a @optional_fields ~w(reply_to_id replies_count is_folded is_deleted floor is_article_author)a @@ -77,7 +78,7 @@ defmodule GroupherServer.CMS.ArticleComment do has_many(:upvotes, {"articles_comments_upvotes", ArticleCommentUpvote}) - article_belongs_to() + article_belongs_to_fields() timestamps(type: :utc_datetime) end diff --git a/lib/groupher_server/cms/article_pinned_comment.ex b/lib/groupher_server/cms/article_pinned_comment.ex index 14c64b810..fd470ea29 100644 --- a/lib/groupher_server/cms/article_pinned_comment.ex +++ b/lib/groupher_server/cms/article_pinned_comment.ex @@ -6,6 +6,7 @@ defmodule GroupherServer.CMS.ArticlePinnedComment do use Accessible import Ecto.Changeset + import Helper.Utils, only: [get_config: 2] import GroupherServer.CMS.Helper.Macros import GroupherServer.CMS.Helper.Utils, only: [articles_foreign_key_constraint: 1] @@ -13,7 +14,7 @@ defmodule GroupherServer.CMS.ArticlePinnedComment do alias CMS.ArticleComment # alias Helper.HTML - @article_threads CMS.Community.article_threads() + @article_threads get_config(:article, :article_threads) @required_fields ~w(article_comment_id)a # @optional_fields ~w(post_id job_id repo_id)a @@ -24,7 +25,7 @@ defmodule GroupherServer.CMS.ArticlePinnedComment do schema "articles_pinned_comments" do belongs_to(:article_comment, ArticleComment, foreign_key: :article_comment_id) - article_belongs_to() + article_belongs_to_fields() timestamps(type: :utc_datetime) end diff --git a/lib/groupher_server/cms/article_upvote.ex b/lib/groupher_server/cms/article_upvote.ex index f8447da10..bc67482a3 100644 --- a/lib/groupher_server/cms/article_upvote.ex +++ b/lib/groupher_server/cms/article_upvote.ex @@ -3,14 +3,16 @@ defmodule GroupherServer.CMS.ArticleUpvote do alias __MODULE__ use Ecto.Schema + import Ecto.Changeset + import Helper.Utils, only: [get_config: 2] import GroupherServer.CMS.Helper.Macros import GroupherServer.CMS.Helper.Utils, only: [articles_foreign_key_constraint: 1] alias GroupherServer.{Accounts, CMS} alias Accounts.User - @article_threads CMS.Community.article_threads() + @article_threads get_config(:article, :article_threads) @required_fields ~w(user_id)a @optional_fields ~w(thread)a @@ -22,7 +24,7 @@ defmodule GroupherServer.CMS.ArticleUpvote do field(:thread, :string) belongs_to(:user, User, foreign_key: :user_id) - article_belongs_to() + article_belongs_to_fields() timestamps(type: :utc_datetime) end diff --git a/lib/groupher_server/cms/article_user_emotion.ex b/lib/groupher_server/cms/article_user_emotion.ex index 5198a26ac..621fd5cd3 100644 --- a/lib/groupher_server/cms/article_user_emotion.ex +++ b/lib/groupher_server/cms/article_user_emotion.ex @@ -29,7 +29,7 @@ defmodule GroupherServer.CMS.ArticleUserEmotion do alias GroupherServer.{Accounts, CMS} @supported_emotions get_config(:article, :supported_emotions) - @article_threads CMS.Community.article_threads() + @article_threads get_config(:article, :article_threads) @required_fields ~w(user_id recived_user_id)a @optional_fields Enum.map(@article_threads, &:"#{&1}_id") ++ @@ -41,7 +41,7 @@ defmodule GroupherServer.CMS.ArticleUserEmotion do belongs_to(:user, Accounts.User, foreign_key: :user_id) emotion_fields() - article_belongs_to() + article_belongs_to_fields() timestamps(type: :utc_datetime) end diff --git a/lib/groupher_server/cms/community.ex b/lib/groupher_server/cms/community.ex index 10c1358a6..1c3da40bf 100644 --- a/lib/groupher_server/cms/community.ex +++ b/lib/groupher_server/cms/community.ex @@ -4,14 +4,12 @@ defmodule GroupherServer.CMS.Community do use Ecto.Schema import Ecto.Changeset + import GroupherServer.CMS.Helper.Macros alias GroupherServer.{Accounts, CMS} alias CMS.{ Category, - Post, - Repo, - Job, CommunityThread, CommunitySubscriber, CommunityEditor, @@ -19,14 +17,12 @@ defmodule GroupherServer.CMS.Community do CommunityCheatsheet } - @article_threads [:post, :job, :repo] @max_pinned_article_count_per_thread 2 @required_fields ~w(title desc user_id logo raw)a # @required_fields ~w(title desc user_id)a @optional_fields ~w(label geo_info index aka)a - def article_threads, do: @article_threads def max_pinned_article_count_per_thread, do: @max_pinned_article_count_per_thread schema "communities" do @@ -59,30 +55,7 @@ defmodule GroupherServer.CMS.Community do # on_replace: :delete ) - many_to_many( - :posts, - Post, - join_through: "communities_posts", - join_keys: [community_id: :id, post_id: :id] - ) - - many_to_many( - :repos, - Repo, - join_through: "communities_repos", - join_keys: [community_id: :id, repo_id: :id] - ) - - many_to_many( - :jobs, - Job, - join_through: "communities_jobs", - join_keys: [community_id: :id, job_id: :id] - ) - - # posts_managers - # jobs_managers - # tuts_managers + community_article_fields() # # posts_block_list ... timestamps(type: :utc_datetime) diff --git a/lib/groupher_server/cms/delegates/abuse_report.ex b/lib/groupher_server/cms/delegates/abuse_report.ex index 18d004cc2..116e9cde3 100644 --- a/lib/groupher_server/cms/delegates/abuse_report.ex +++ b/lib/groupher_server/cms/delegates/abuse_report.ex @@ -3,7 +3,7 @@ defmodule GroupherServer.CMS.Delegate.AbuseReport 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, strip_struct: 1, get_config: 2] import GroupherServer.CMS.Helper.Matcher2 import ShortMaps @@ -17,9 +17,9 @@ defmodule GroupherServer.CMS.Delegate.AbuseReport do alias Ecto.Multi + @article_threads get_config(:article, :article_threads) @report_threshold_for_fold ArticleComment.report_threshold_for_fold() - @article_threads Community.article_threads() @export_author_keys [:id, :login, :nickname, :avatar] @export_article_keys [:id, :title, :digest, :upvotes_count, :views] @export_report_keys [ diff --git a/lib/groupher_server/cms/delegates/article_comment_action.ex b/lib/groupher_server/cms/delegates/article_comment_action.ex index 0041d07cb..6345627ba 100644 --- a/lib/groupher_server/cms/delegates/article_comment_action.ex +++ b/lib/groupher_server/cms/delegates/article_comment_action.ex @@ -3,7 +3,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCommentAction 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, strip_struct: 1, get_config: 2] import Helper.ErrorCode import GroupherServer.CMS.Delegate.ArticleComment, @@ -30,7 +30,8 @@ defmodule GroupherServer.CMS.Delegate.ArticleCommentAction do alias Ecto.Multi - @article_threads Community.article_threads() + @article_threads get_config(:article, :article_threads) + @max_parent_replies_count ArticleComment.max_parent_replies_count() @pinned_comment_limit ArticleComment.pinned_comment_limit() diff --git a/lib/groupher_server/cms/embeds/article_meta.ex b/lib/groupher_server/cms/embeds/article_meta.ex index 1cd79d77c..bddd20745 100644 --- a/lib/groupher_server/cms/embeds/article_meta.ex +++ b/lib/groupher_server/cms/embeds/article_meta.ex @@ -6,8 +6,6 @@ defmodule GroupherServer.CMS.Embeds.ArticleMeta do use Accessible import Ecto.Changeset - alias GroupherServer.CMS - @optional_fields ~w(is_edited is_comment_locked upvoted_user_ids collected_user_ids viewed_user_ids reported_user_ids reported_count)a @default_meta %{ diff --git a/lib/groupher_server/cms/helper/macros.ex b/lib/groupher_server/cms/helper/macros.ex index 1bec4b5f4..ae69b809e 100644 --- a/lib/groupher_server/cms/helper/macros.ex +++ b/lib/groupher_server/cms/helper/macros.ex @@ -2,10 +2,14 @@ defmodule GroupherServer.CMS.Helper.Macros do @moduledoc """ macros for define article related fields in CMS models """ + import Helper.Utils, only: [get_config: 2] - alias GroupherServer.CMS + alias GroupherServer.{CMS, Accounts} - @article_threads CMS.Community.article_threads() + alias Accounts.User + alias CMS.{Author, Community, ArticleComment, ArticleUpvote, ArticleCollect} + + @article_threads get_config(:article, :article_threads) @doc """ generate belongs to fields for given thread @@ -13,9 +17,16 @@ defmodule GroupherServer.CMS.Helper.Macros do e.g: belongs_to(:post, Post, foreign_key: :post_id) - NOTE: should do migration to DB manually + MIGRATION: + should do migration to DB manually: + 数据库层面的 migration 需要手动添加,参考: + + add(:post_id, references(:cms_posts, on_delete: :delete_all)) + add(:job_id, references(:cms_jobs, on_delete: :delete_all)) + add(:repo_id, references(:cms_jobs, on_delete: :delete_all)) + ... """ - defmacro article_belongs_to() do + defmacro article_belongs_to_fields() do @article_threads |> Enum.map(fn thread -> quote do @@ -27,4 +38,204 @@ defmodule GroupherServer.CMS.Helper.Macros do end end) end + + @doc """ + for GroupherServer.CMS.[Article] + article_comments related fields + + MIGRATION: + should do migration to DB manually: + 数据库层面的 migration 需要手动添加,参考: + + TABLE: "article_comments" + ----- + add(:[article]_id, references(:cms_[article]s, on_delete: :delete_all)) + + TABLE: "cms_[article]s" + ----- + add(:article_comments_participators_count, :integer, default: 0) + add(:article_comments_count, :integer, default: 0) + add(:article_comments_participators, :map) + """ + defmacro article_comment_fields() do + quote do + field(:article_comments_participators_count, :integer, default: 0) + field(:article_comments_count, :integer, default: 0) + has_many(:article_comments, {"articles_comments", ArticleComment}) + # 评论参与者,只保留最近 5 个 + embeds_many(:article_comments_participators, User, on_replace: :delete) + end + end + + @doc """ + for GroupherServer.CMS.[Article] + viewer has xxx fields for each article + + those fields is virtual, do not need DB migration + """ + defmacro viewer_has_fields() do + quote do + field(:viewer_has_viewed, :boolean, default: false, virtual: true) + field(:viewer_has_upvoted, :boolean, default: false, virtual: true) + field(:viewer_has_collected, :boolean, default: false, virtual: true) + field(:viewer_has_reported, :boolean, default: false, virtual: true) + end + end + + @doc """ + for GroupherServer.CMS.[Article] + aritlce's upvote and collect feature + + MIGRATION: + should do migration to DB manually: + 数据库层面的 migration 需要手动添加,参考: + + TABLE: "cms_[article]s" + ----- + add(:upvotes_count, :integer, default: 0) + add(:collects_count, :integer, default: 0) + + ## TABLE: "article_upvotes" and TABLE: "article_collects" + ----- + add(:[article]_id, references(:cms_[article]s, on_delete: :delete_all)) + """ + defmacro upvote_and_collect_fields() do + quote do + has_many(:upvotes, {"article_upvotes", ArticleUpvote}) + field(:upvotes_count, :integer, default: 0) + + has_many(:collects, {"article_collects", ArticleCollect}) + field(:collects_count, :integer, default: 0) + end + end + + @doc """ + for GroupherServer.CMS.[Article] + + common casting fields for general_article_fields + """ + def general_article_fields(:cast) do + [ + :original_community_id, + :article_comments_count, + :article_comments_participators_count, + :upvotes_count, + :collects_count, + :mark_delete + ] + end + + @doc """ + for GroupherServer.CMS.[Article] + + MIGRATION: + + TABLE: "cms_[article]s" + ----- + # for :author + add(:author_id, references(:cms_authors, on_delete: :delete_all), null: false) + create(index(:cms_[article]s, [:author_id])) + + # for :views + add(:views, :integer, default: 0) + + # for :mark_delete + add(:mark_delete, :boolean, default: false) + + # for :meta + add(:meta, :map) + + # for :emotion + add(:emotions, :map) + + # for :original_community + add(:original_community_id, references(:communities, on_delete: :delete_all)) + + # for :upvote and :collect + add(:upvotes_count, :integer, default: 0) + add(:collects_count, :integer, default: 0) + + # for :article_comment + add(:article_comments_participators_count, :integer, default: 0) + add(:article_comments_count, :integer, default: 0) + add(:article_comments_participators, :map) + + # for table contains macro "article_belongs_to_fields": + # TABLE: "abuse_reports" + # TABLE: "article_collects" + # TABLE: "article_upvotes" + # TABLE: "articles_comments" + # TABLE: "articles_pinned_comments" + # TABLE: "articles_users_emotions" + # TABLE: "pinned_articles" + ----- + add(:[article]_id, references(:cms_[article]s, on_delete: :delete_all)) + + """ + defmacro general_article_fields do + quote do + belongs_to(:author, Author) + + field(:views, :integer, default: 0) + field(:is_pinned, :boolean, default: false, virtual: true) + field(:mark_delete, :boolean, default: false) + + embeds_one(:meta, CMS.Embeds.ArticleMeta, on_replace: :update) + embeds_one(:emotions, CMS.Embeds.ArticleEmotion, on_replace: :update) + + belongs_to(:original_community, CMS.Community) + + upvote_and_collect_fields() + viewer_has_fields() + article_comment_fields() + + # TODO: + # reference_articles + # related_articles + timestamps() + end + end + + @doc """ + for GroupherServer.CMS.Community + + # TABLE: "communities_[article]s" + add(:community_id, references(:communities, on_delete: :delete_all), null: false) + add(:[article]_id, references(:cms_[article]s, on_delete: :delete_all), null: false) + + create(unique_index(:communities_[article]s, [:community_id, :[article]_id])) + """ + defmacro community_article_fields() do + @article_threads + |> Enum.map(fn thread -> + quote do + many_to_many( + unquote(:"#{thread}s"), + Module.concat(CMS, unquote(thread) |> to_string |> Recase.to_pascal()), + join_through: unquote("communities_#{to_string(thread)}s"), + join_keys: [community_id: :id] ++ Keyword.new([{unquote(:"#{thread}_id"), :id}]) + ) + end + end) + end + + @doc """ + for GroupherServer.CMS.[Article] + + # TABLE: "communities_[article]s" + add(:community_id, references(:communities, on_delete: :delete_all), null: false) + add(:[article]_id, references(:cms_[article]s, on_delete: :delete_all), null: false) + + create(unique_index(:communities_[article]s, [:community_id, :[article]_id])) + """ + defmacro article_community_field(thread) do + quote do + many_to_many( + :communities, + Community, + join_through: unquote("communities_#{to_string(thread)}s"), + on_replace: :delete + ) + end + end end diff --git a/lib/groupher_server/cms/helper/matcher2.ex b/lib/groupher_server/cms/helper/matcher2.ex index d9a9805c4..a25b72b19 100644 --- a/lib/groupher_server/cms/helper/matcher2.ex +++ b/lib/groupher_server/cms/helper/matcher2.ex @@ -2,11 +2,12 @@ defmodule GroupherServer.CMS.Helper.Matcher2.Macros do @moduledoc """ generate match functions """ + import Helper.Utils, only: [get_config: 2] alias GroupherServer.CMS alias CMS.{ArticleComment, Community, Embeds} - @article_threads Community.article_threads() + @article_threads get_config(:article, :article_threads) @doc """ match basic threads diff --git a/lib/groupher_server/cms/helper/utils.ex b/lib/groupher_server/cms/helper/utils.ex index 7d2acfca3..e2b797840 100644 --- a/lib/groupher_server/cms/helper/utils.ex +++ b/lib/groupher_server/cms/helper/utils.ex @@ -3,11 +3,12 @@ defmodule GroupherServer.CMS.Helper.Utils do utils for CMS helper """ import Ecto.Changeset + import Helper.Utils, only: [get_config: 2] alias GroupherServer.CMS alias CMS.Community - @article_threads CMS.Community.article_threads() + @article_threads get_config(:article, :article_threads) @article_fields @article_threads |> Enum.map(&:"#{&1}_id") @doc """ diff --git a/lib/groupher_server/cms/job.ex b/lib/groupher_server/cms/job.ex index 12af61469..2b1da26cc 100644 --- a/lib/groupher_server/cms/job.ex +++ b/lib/groupher_server/cms/job.ex @@ -6,14 +6,17 @@ defmodule GroupherServer.CMS.Job do use Accessible import Ecto.Changeset + import GroupherServer.CMS.Helper.Macros alias GroupherServer.{CMS, Accounts} - alias CMS.{Author, Embeds, ArticleComment, Community, Tag, ArticleUpvote, ArticleCollect} + alias CMS.{Embeds, Tag} alias Helper.HTML @timestamps_opts [type: :utc_datetime_usec] @required_fields ~w(title company company_logo body digest length)a - @optional_fields ~w(original_community_id desc company_link link_addr copy_right salary exp education field finance scale article_comments_count article_comments_participators_count upvotes_count collects_count mark_delete)a + @article_cast_fields general_article_fields(:cast) + @optional_fields @article_cast_fields ++ + ~w(desc company_link link_addr copy_right salary exp education field finance scale)a @type t :: %Job{} schema "cms_jobs" do @@ -23,10 +26,6 @@ defmodule GroupherServer.CMS.Job do field(:company_link, :string) field(:desc, :string) field(:body, :string) - belongs_to(:author, Author) - field(:views, :integer, default: 0) - - embeds_one(:meta, Embeds.ArticleMeta, on_replace: :update) field(:link_addr, :string) field(:copy_right, :string) @@ -41,29 +40,6 @@ defmodule GroupherServer.CMS.Job do field(:digest, :string) field(:length, :integer) - # NOTE: this one is tricky, pin is dynamic changed when return by func: add_pin_contents_ifneed - field(:is_pinned, :boolean, default: false, virtual: true) - field(:mark_delete, :boolean, default: false) - - has_many(:upvotes, {"article_upvotes", ArticleUpvote}) - field(:upvotes_count, :integer, default: 0) - - field(:viewer_has_viewed, :boolean, default: false, virtual: true) - field(:viewer_has_upvoted, :boolean, default: false, virtual: true) - field(:viewer_has_collected, :boolean, default: false, virtual: true) - field(:viewer_has_reported, :boolean, default: false, virtual: true) - - has_many(:collects, {"article_collects", ArticleCollect}) - field(:collects_count, :integer, default: 0) - - 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) - - embeds_one(:emotions, Embeds.ArticleEmotion, on_replace: :update) - many_to_many( :tags, Tag, @@ -74,17 +50,8 @@ defmodule GroupherServer.CMS.Job do on_replace: :delete ) - belongs_to(:original_community, Community) - - many_to_many( - :communities, - Community, - join_through: "communities_jobs", - on_replace: :delete - ) - - # timestamps(type: :utc_datetime) - timestamps() + article_community_field(:job) + general_article_fields() end @doc false diff --git a/lib/groupher_server/cms/pinned_article.ex b/lib/groupher_server/cms/pinned_article.ex index 43fea5d31..e5eb13491 100644 --- a/lib/groupher_server/cms/pinned_article.ex +++ b/lib/groupher_server/cms/pinned_article.ex @@ -3,14 +3,16 @@ defmodule GroupherServer.CMS.PinnedArticle do alias __MODULE__ use Ecto.Schema + import Ecto.Changeset + import Helper.Utils, only: [get_config: 2] import GroupherServer.CMS.Helper.Macros import GroupherServer.CMS.Helper.Utils, only: [articles_foreign_key_constraint: 1] alias GroupherServer.CMS alias CMS.Community - @article_threads CMS.Community.article_threads() + @article_threads get_config(:article, :article_threads) @required_fields ~w(community_id thread)a # @optional_fields ~w(post_id job_id repo_id)a @@ -21,7 +23,7 @@ defmodule GroupherServer.CMS.PinnedArticle do belongs_to(:community, Community, foreign_key: :community_id) field(:thread, :string) - article_belongs_to() + article_belongs_to_fields() timestamps(type: :utc_datetime) end diff --git a/lib/groupher_server/cms/post.ex b/lib/groupher_server/cms/post.ex index 42c0d045e..2942a443c 100644 --- a/lib/groupher_server/cms/post.ex +++ b/lib/groupher_server/cms/post.ex @@ -6,25 +6,18 @@ defmodule GroupherServer.CMS.Post do use Accessible import Ecto.Changeset + import GroupherServer.CMS.Helper.Macros - alias GroupherServer.{CMS, Accounts} - - alias CMS.{ - Embeds, - Author, - ArticleComment, - Community, - PostComment, - Tag, - ArticleUpvote, - ArticleCollect - } + alias GroupherServer.CMS + alias CMS.{Embeds, PostComment, Tag} alias Helper.HTML @timestamps_opts [type: :utc_datetime_usec] + @required_fields ~w(title body digest length)a - @optional_fields ~w(original_community_id link_addr copy_right link_addr link_icon article_comments_count article_comments_participators_count upvotes_count collects_count mark_delete)a + @article_cast_fields general_article_fields(:cast) + @optional_fields ~w(link_addr copy_right link_addr link_icon)a ++ @article_cast_fields @type t :: %Post{} schema "cms_posts" do @@ -35,40 +28,10 @@ defmodule GroupherServer.CMS.Post do field(:link_icon, :string) field(:copy_right, :string) field(:length, :integer) - field(:views, :integer, default: 0) - - belongs_to(:author, Author) - embeds_one(:meta, Embeds.ArticleMeta, on_replace: :update) - - # NOTE: this one is tricky, pin is dynamic changed when return by func: add_pin_contents_ifneed - # field(:pin, :boolean, default_value: false, virtual: true) - field(:is_pinned, :boolean, default: false, virtual: true) - field(:mark_delete, :boolean, default: false) - field(:viewer_has_viewed, :boolean, default: false, virtual: true) - field(:viewer_has_upvoted, :boolean, default: false, virtual: true) - field(:viewer_has_collected, :boolean, default: false, virtual: true) - field(:viewer_has_reported, :boolean, default: false, virtual: true) - - has_many(:upvotes, {"article_upvotes", ArticleUpvote}) - field(:upvotes_count, :integer, default: 0) - - has_many(:collects, {"article_collects", ArticleCollect}) - field(:collects_count, :integer, default: 0) - - # TODO - # 相关文章 - # has_may(:related_post, ...) + # TODO: remove after legacy data migrated has_many(:comments, {"posts_comments", PostComment}) - 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) - - embeds_one(:emotions, Embeds.ArticleEmotion, on_replace: :update) - # The keys are inflected from the schema names! # see https://hexdocs.pm/ecto/Ecto.Schema.html many_to_many( @@ -81,19 +44,8 @@ defmodule GroupherServer.CMS.Post do on_replace: :delete ) - belongs_to(:original_community, Community) - - many_to_many( - :communities, - Community, - join_through: "communities_posts", - on_replace: :delete - ) - - # timestamps(type: :utc_datetime) - # for paged test to diff - # timestamps(type: :utc_datetime_usec) - timestamps() + article_community_field(:post) + general_article_fields() end @doc false @@ -112,15 +64,12 @@ defmodule GroupherServer.CMS.Post do |> generl_changeset end - defp generl_changeset(content) do - content + defp generl_changeset(changeset) do + changeset |> validate_length(:title, min: 3, max: 50) |> cast_embed(:emotions, with: &Embeds.ArticleEmotion.changeset/2) |> validate_length(:body, min: 3, max: 10_000) |> validate_length(:link_addr, min: 5, max: 400) |> HTML.safe_string(:body) - - # |> foreign_key_constraint(:posts_tags, name: :posts_tags_tag_id_fkey) - # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey) end end diff --git a/lib/groupher_server/cms/repo.ex b/lib/groupher_server/cms/repo.ex index b3be371b0..3ecefa91c 100644 --- a/lib/groupher_server/cms/repo.ex +++ b/lib/groupher_server/cms/repo.ex @@ -6,26 +6,17 @@ defmodule GroupherServer.CMS.Repo do use Accessible import Ecto.Changeset + import GroupherServer.CMS.Helper.Macros - alias GroupherServer.{CMS, Accounts} - - alias CMS.{ - Author, - Embeds, - ArticleComment, - Community, - RepoContributor, - RepoLang, - Tag, - ArticleUpvote, - ArticleCollect - } + alias GroupherServer.CMS + alias CMS.{Embeds, RepoContributor, RepoLang, Tag} alias Helper.HTML @timestamps_opts [type: :utc_datetime_usec] @required_fields ~w(title owner_name owner_url repo_url desc readme star_count issues_count prs_count fork_count watch_count)a - @optional_fields ~w(original_community_id last_sync homepage_url release_tag license upvotes_count collects_count mark_delete article_comments_count article_comments_participators_count)a + @article_cast_fields general_article_fields(:cast) + @optional_fields @article_cast_fields ++ ~w(last_sync homepage_url release_tag license)a @type t :: %Repo{} schema "cms_repos" do @@ -47,39 +38,9 @@ defmodule GroupherServer.CMS.Repo do field(:license, :string) field(:release_tag, :string) embeds_one(:primary_language, RepoLang, on_replace: :delete) - embeds_many(:contributors, RepoContributor, on_replace: :delete) - - field(:views, :integer, default: 0) - - embeds_one(:meta, Embeds.ArticleMeta, on_replace: :update) - embeds_one(:emotions, Embeds.ArticleEmotion, on_replace: :update) - - belongs_to(:author, Author) - - # NOTE: this one is tricky, pin is dynamic changed when return by func: add_pin_contents_ifneed - field(:is_pinned, :boolean, default: false, virtual: true) - field(:mark_delete, :boolean, default: false) - - field(:viewer_has_viewed, :boolean, default: false, virtual: true) - field(:viewer_has_upvoted, :boolean, default: false, virtual: true) - field(:viewer_has_collected, :boolean, default: false, virtual: true) - field(:viewer_has_reported, :boolean, default: false, virtual: true) - - has_many(:upvotes, {"article_upvotes", ArticleUpvote}) - field(:upvotes_count, :integer, default: 0) - - has_many(:collects, {"article_collects", ArticleCollect}) - field(:collects_count, :integer, default: 0) - field(:last_sync, :utc_datetime) - 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, Tag, @@ -89,17 +50,8 @@ defmodule GroupherServer.CMS.Repo do on_replace: :delete ) - belongs_to(:original_community, Community) - - many_to_many( - :communities, - Community, - join_through: "communities_repos", - on_replace: :delete - ) - - # timestamps(type: :utc_datetime) - timestamps() + article_community_field(:repo) + general_article_fields() end @doc false diff --git a/lib/groupher_server_web/resolvers/cms_resolver.ex b/lib/groupher_server_web/resolvers/cms_resolver.ex index 9c6fcdbee..e7c437d27 100644 --- a/lib/groupher_server_web/resolvers/cms_resolver.ex +++ b/lib/groupher_server_web/resolvers/cms_resolver.ex @@ -1,7 +1,6 @@ defmodule GroupherServerWeb.Resolvers.CMS do @moduledoc false - import GroupherServer.CMS.Helper.Matcher import ShortMaps import Ecto.Query, warn: false @@ -215,10 +214,6 @@ defmodule GroupherServerWeb.Resolvers.CMS do CMS.set_tag(thread, %Tag{id: tag_id}, id) end - def set_refined_tag(_root, ~m(community_id thread id)a, _info) do - CMS.set_refined_tag(%Community{id: community_id}, thread, id) - end - def unset_tag(_root, ~m(id thread tag_id)a, _info) do CMS.unset_tag(thread, %Tag{id: tag_id}, id) end diff --git a/lib/groupher_server_web/schema/cms/mutations/repo.ex b/lib/groupher_server_web/schema/cms/mutations/repo.ex index e316893cc..de09b4f0b 100644 --- a/lib/groupher_server_web/schema/cms/mutations/repo.ex +++ b/lib/groupher_server_web/schema/cms/mutations/repo.ex @@ -74,6 +74,7 @@ defmodule GroupherServerWeb.Schema.CMS.Mutations.Repo do article_pin_mutation(:repo) article_mark_delete_mutation(:repo) article_delete_mutation(:repo) + article_emotion_mutation(:repo) article_report_mutation(:repo) ############# end diff --git a/lib/helper/scheduler.ex b/lib/helper/scheduler.ex index fd95b4747..4783155e6 100644 --- a/lib/helper/scheduler.ex +++ b/lib/helper/scheduler.ex @@ -3,8 +3,7 @@ defmodule Helper.Scheduler do cron-like job scheduler """ use Quantum.Scheduler, otp_app: :groupher_server - - alias Helper.Cache + # alias Helper.Cache @doc """ clear all the cache in Cachex diff --git a/test/groupher_server/accounts/collect_folder_test.exs b/test/groupher_server/accounts/collect_folder_test.exs index 93774b6d6..398ab0c13 100644 --- a/test/groupher_server/accounts/collect_folder_test.exs +++ b/test/groupher_server/accounts/collect_folder_test.exs @@ -215,7 +215,6 @@ defmodule GroupherServer.Test.Accounts.CollectFolder do assert result.total_count == 0 end - @tag :wip2 test "add post to exsit colect-folder should update meta", ~m(user post post2 job)a do {:ok, folder} = Accounts.create_collect_folder(%{title: "test folder"}, user) diff --git a/test/groupher_server/cms/articles/job_pin_test.exs b/test/groupher_server/cms/articles/job_pin_test.exs index 3e1654979..9faa1f69a 100644 --- a/test/groupher_server/cms/articles/job_pin_test.exs +++ b/test/groupher_server/cms/articles/job_pin_test.exs @@ -39,7 +39,6 @@ defmodule GroupherServer.Test.CMS.Artilces.JobPin do assert reason |> Keyword.get(:code) == ecode(:too_much_pinned_article) end - @tag :wip2 test "can not pin a non-exsit job", ~m(community)a do assert {:error, _} = CMS.pin_article(:job, 8848, community.id) end diff --git a/test/groupher_server/cms/articles/post_pin_test.exs b/test/groupher_server/cms/articles/post_pin_test.exs index 26775596e..5b016c4e7 100644 --- a/test/groupher_server/cms/articles/post_pin_test.exs +++ b/test/groupher_server/cms/articles/post_pin_test.exs @@ -39,7 +39,6 @@ defmodule GroupherServer.Test.CMS.Artilces.PostPin do assert reason |> Keyword.get(:code) == ecode(:too_much_pinned_article) end - @tag :wip2 test "can not pin a non-exsit post", ~m(community)a do assert {:error, _} = CMS.pin_article(:post, 8848, community.id) end diff --git a/test/groupher_server/cms/articles/repo_pin_test.exs b/test/groupher_server/cms/articles/repo_pin_test.exs index 7577847dd..b253a185d 100644 --- a/test/groupher_server/cms/articles/repo_pin_test.exs +++ b/test/groupher_server/cms/articles/repo_pin_test.exs @@ -39,7 +39,6 @@ defmodule GroupherServer.Test.CMS.Artilces.RepoPin do assert reason |> Keyword.get(:code) == ecode(:too_much_pinned_article) end - @tag :wip2 test "can not pin a non-exsit repo", ~m(community)a do assert {:error, _} = CMS.pin_article(:repo, 8848, community.id) end diff --git a/test/groupher_server/cms/comments/post_comment_test.exs b/test/groupher_server/cms/comments/post_comment_test.exs index 1333ee4ca..69210d99a 100644 --- a/test/groupher_server/cms/comments/post_comment_test.exs +++ b/test/groupher_server/cms/comments/post_comment_test.exs @@ -350,7 +350,6 @@ defmodule GroupherServer.Test.CMS.Comments.PostComment do assert results.total_count == total_count + 1 end - @tag :wip2 test "paged article comments folded flag should be false", ~m(user post)a do total_count = 30 page_number = 1 diff --git a/test/groupher_server/cms/comments/repo_comment_emotions_test.exs b/test/groupher_server/cms/comments/repo_comment_emotions_test.exs new file mode 100644 index 000000000..e3d8afbad --- /dev/null +++ b/test/groupher_server/cms/comments/repo_comment_emotions_test.exs @@ -0,0 +1,199 @@ +defmodule GroupherServer.Test.CMS.Comments.RepoCommentEmotions 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, repo} = db_insert(:repo) + + {:ok, ~m(user user2 user3 repo)a} + end + + describe "[emotion in paged article comment]" do + test "login user should got viewer has emotioned status", ~m(repo 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(:repo, repo.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.paged_article_comments( + :repo, + repo.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 + test "comment has default emotions after created", ~m(repo user)a do + parent_content = "parent comment" + + {:ok, parent_comment} = CMS.create_article_comment(:repo, repo.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 + + test "can make emotion to comment", ~m(repo user user2)a do + parent_content = "parent comment" + {:ok, parent_comment} = CMS.create_article_comment(:repo, repo.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 + + test "can undo emotion to comment", ~m(repo user user2)a do + parent_content = "parent comment" + {:ok, parent_comment} = CMS.create_article_comment(:repo, repo.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 + + test "same user make same emotion to same comment.", ~m(repo user)a do + parent_content = "parent comment" + {:ok, parent_comment} = CMS.create_article_comment(:repo, repo.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 + + test "same user same emotion to same comment only have one user_emotion record", + ~m(repo user)a do + parent_content = "parent comment" + {:ok, parent_comment} = CMS.create_article_comment(:repo, repo.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 + + test "different user can make same emotions on same comment", ~m(repo user user2 user3)a do + {:ok, parent_comment} = CMS.create_article_comment(:repo, repo.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 + + test "same user can make differcent emotions on same comment", ~m(repo user)a do + parent_content = "parent comment" + {:ok, parent_comment} = CMS.create_article_comment(:repo, repo.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/repo_comment_replies_test.exs b/test/groupher_server/cms/comments/repo_comment_replies_test.exs new file mode 100644 index 000000000..6be589753 --- /dev/null +++ b/test/groupher_server/cms/comments/repo_comment_replies_test.exs @@ -0,0 +1,213 @@ +defmodule GroupherServer.Test.CMS.Comments.RepoCommentReplies do + @moduledoc false + + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + + alias CMS.{ArticleComment, Repo} + + @max_parent_replies_count CMS.ArticleComment.max_parent_replies_count() + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, repo} = db_insert(:repo) + + {:ok, ~m(user user2 repo)a} + end + + describe "[basic article comment replies]" do + test "exsit comment can be reply", ~m(repo user user2)a do + parent_content = "parent comment" + reply_content = "reply comment" + + {:ok, parent_comment} = CMS.create_article_comment(:repo, repo.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 + + test "deleted comment can not be reply", ~m(repo user user2)a do + parent_content = "parent comment" + reply_content = "reply comment" + + {:ok, parent_comment} = CMS.create_article_comment(:repo, repo.id, parent_content, user) + {:ok, _} = CMS.delete_article_comment(parent_comment) + + {:error, _} = CMS.reply_article_comment(parent_comment.id, reply_content, user2) + end + + test "multi reply should belong to one parent comment", ~m(repo 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(:repo, repo.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 + + test "reply to reply inside a comment should belong same parent comment", + ~m(repo user user2)a do + {:ok, parent_comment} = CMS.create_article_comment(:repo, repo.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 + + test "reply to reply inside a comment should have is_reply_to_others flag in meta", + ~m(repo user user2)a do + {:ok, parent_comment} = CMS.create_article_comment(:repo, repo.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 + + test "comment replies only contains @max_parent_replies_count replies", ~m(repo user)a do + total_reply_count = @max_parent_replies_count + 1 + + {:ok, parent_comment} = CMS.create_article_comment(:repo, repo.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 + + test "replyed user should appear in article comment participators", ~m(repo user user2)a do + {:ok, parent_comment} = CMS.create_article_comment(:repo, repo.id, "parent_conent", user) + {:ok, _} = CMS.reply_article_comment(parent_comment.id, "reply_content", user2) + + {:ok, article} = ORM.find(Repo, repo.id) + + assert exist_in?(user, article.article_comments_participators) + assert exist_in?(user2, article.article_comments_participators) + end + + test "replies count should inc by 1 after got replyed", ~m(repo user user2)a do + {:ok, parent_comment} = CMS.create_article_comment(:repo, repo.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 + test "can get paged replies of a parent comment", ~m(repo user)a do + {:ok, parent_comment} = CMS.create_article_comment(:repo, repo.id, "parent_conent", user) + {:ok, paged_replies} = CMS.paged_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.paged_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 + + test "can get reply_to info of a parent comment", ~m(repo user)a do + page_number = 1 + page_size = 10 + + {:ok, parent_comment} = CMS.create_article_comment(:repo, repo.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.paged_article_comments( + :repo, + repo.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 diff --git a/test/groupher_server/cms/comments/repo_comment_test.exs b/test/groupher_server/cms/comments/repo_comment_test.exs new file mode 100644 index 000000000..6f7c15e2f --- /dev/null +++ b/test/groupher_server/cms/comments/repo_comment_test.exs @@ -0,0 +1,566 @@ +defmodule GroupherServer.Test.CMS.Comments.JobComment do + @moduledoc false + + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.{Accounts, CMS} + + alias CMS.{ArticleComment, ArticlePinnedComment, Embeds, Job} + + @delete_hint CMS.ArticleComment.delete_hint() + @report_threshold_for_fold ArticleComment.report_threshold_for_fold() + @default_comment_meta Embeds.ArticleCommentMeta.default_meta() + @pinned_comment_limit ArticleComment.pinned_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 + 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 + + 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 + + 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 + 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 + 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 + + 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 + + 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 + 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 + + 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 + + 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 + + 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 + + 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 + + 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 + + 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 + + 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 + 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 + + 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} = ArticlePinnedComment |> 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, _} = ArticlePinnedComment |> ORM.find_by(%{article_comment_id: comment.id}) + end + + test "pinned 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..(@pinned_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 + # + # 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) + + # {:ok, comment} = CMS.report_article_comment(comment.id, "reason", "attr", user) + # {:ok, comment} = ORM.find(ArticleComment, comment.id) + # end + + # + # 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, "reason", "attr", user) + # {:ok, comment} = ORM.find(ArticleComment, comment.id) + + # {:ok, _comment} = CMS.undo_report_article_comment(comment.id, user) + # {:ok, comment} = ORM.find(ArticleComment, comment.id) + # end + + test "can undo a report with other user report it too", + ~m(user user2 job)a do + {:ok, comment} = CMS.create_article_comment(:job, job.id, "commment", user) + + {:ok, _comment} = CMS.report_article_comment(comment.id, "reason", "attr", user) + {:ok, _comment} = CMS.report_article_comment(comment.id, "reason", "attr", user2) + + filter = %{content_type: :article_comment, content_id: comment.id, page: 1, size: 20} + {:ok, all_reports} = CMS.paged_reports(filter) + assert all_reports.total_count == 1 + + report = all_reports.entries |> List.first() + assert report.report_cases |> length == 2 + assert Enum.any?(report.report_cases, &(&1.user.login == user.login)) + assert Enum.any?(report.report_cases, &(&1.user.login == user2.login)) + + {:ok, _report} = CMS.undo_report_article(:article_comment, comment.id, user) + + filter = %{content_type: :article_comment, content_id: comment.id, page: 1, size: 20} + {:ok, all_reports} = CMS.paged_reports(filter) + assert all_reports.total_count == 1 + + report = all_reports.entries |> List.first() + assert report.report_cases |> length == 1 + assert Enum.any?(report.report_cases, &(&1.user.login == user2.login)) + end + + 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_folded + + Enum.reduce(1..(@report_threshold_for_fold - 1), [], fn _, _acc -> + {:ok, user} = db_insert(:user) + {:ok, _comment} = CMS.report_article_comment(comment.id, "reason", "attr", user) + end) + + {:ok, comment} = ORM.find(ArticleComment, comment.id) + assert not comment.is_folded + end + + 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_folded + + Enum.reduce(1..(@report_threshold_for_fold + 1), [], fn _, _acc -> + {:ok, user} = db_insert(:user) + {:ok, _comment} = CMS.report_article_comment(comment.id, "reason", "attr", user) + end) + + {:ok, comment} = ORM.find(ArticleComment, comment.id) + assert comment.is_folded + end + end + + describe "paged article comments" do + 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.paged_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 + + 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.paged_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 + + test "paged article comments should contains pinned 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.paged_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 + + test "only page 1 have pinned 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.paged_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 + + 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) + + {: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, paged_comments} = + CMS.paged_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 page_number == paged_comments.page_number + assert page_size == paged_comments.page_size + assert total_count - 3 == paged_comments.total_count + end + + 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.paged_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 + end + + describe "[article comment delete]" do + 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.paged_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 + + 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 + + test "delete comment still delete pinned 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(ArticlePinnedComment, 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/emotions/repo_emotions_test.exs b/test/groupher_server/cms/emotions/repo_emotions_test.exs new file mode 100644 index 000000000..9c619e609 --- /dev/null +++ b/test/groupher_server/cms/emotions/repo_emotions_test.exs @@ -0,0 +1,177 @@ +defmodule GroupherServer.Test.CMS.Emotions.RepoEmotions do + @moduledoc false + + use GroupherServer.TestTools + + alias Helper.ORM + alias GroupherServer.CMS + + alias CMS.{Embeds, ArticleUserEmotion} + + @default_emotions Embeds.ArticleEmotion.default_emotions() + + setup do + {:ok, user} = db_insert(:user) + {:ok, community} = db_insert(:community) + {:ok, user2} = db_insert(:user) + {:ok, user3} = db_insert(:user) + + repo_attrs = mock_attrs(:repo, %{community_id: community.id}) + + {:ok, ~m(user user2 user3 community repo_attrs)a} + end + + describe "[emotion in paged repos]" do + test "login user should got viewer has emotioned status", + ~m(community repo_attrs user)a do + total_count = 10 + page_number = 10 + page_size = 20 + + all_repos = + Enum.reduce(0..total_count, [], fn _, acc -> + {:ok, repo} = CMS.create_article(community, :repo, repo_attrs, user) + acc ++ [repo] + end) + + random_repo = all_repos |> Enum.at(3) + + {:ok, _} = CMS.emotion_to_article(:repo, random_repo.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:repo, random_repo.id, :beer, user) + {:ok, _} = CMS.emotion_to_article(:repo, random_repo.id, :popcorn, user) + + {:ok, paged_articles} = + CMS.paged_articles(:repo, %{page: page_number, size: page_size}, user) + + target = Enum.find(paged_articles.entries, &(&1.id == random_repo.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 emotion]" do + test "repo has default emotions after created", ~m(community repo_attrs user)a do + {:ok, repo} = CMS.create_article(community, :repo, repo_attrs, user) + + emotions = repo.emotions |> Map.from_struct() |> Map.delete(:id) + assert @default_emotions == emotions + end + + test "can make emotion to repo", ~m(community repo_attrs user user2)a do + {:ok, repo} = CMS.create_article(community, :repo, repo_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:repo, repo.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:repo, repo.id, :downvote, user2) + + {:ok, %{emotions: emotions}} = ORM.find(CMS.Repo, repo.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 + + test "can undo emotion to repo", ~m(community repo_attrs user user2)a do + {:ok, repo} = CMS.create_article(community, :repo, repo_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:repo, repo.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:repo, repo.id, :downvote, user2) + + {:ok, _} = CMS.undo_emotion_to_article(:repo, repo.id, :downvote, user) + {:ok, _} = CMS.undo_emotion_to_article(:repo, repo.id, :downvote, user2) + + {:ok, %{emotions: emotions}} = ORM.find(CMS.Repo, repo.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 + + test "same user make same emotion to same repo.", ~m(community repo_attrs user)a do + {:ok, repo} = CMS.create_article(community, :repo, repo_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:repo, repo.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:repo, repo.id, :downvote, user) + + {:ok, repo} = ORM.find(CMS.Repo, repo.id) + + assert repo.emotions.downvote_count == 1 + assert user_exist_in?(user, repo.emotions.latest_downvote_users) + end + + test "same user same emotion to same repo only have one user_emotion record", + ~m(community repo_attrs user)a do + {:ok, repo} = CMS.create_article(community, :repo, repo_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:repo, repo.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:repo, repo.id, :heart, user) + + {:ok, repo} = ORM.find(CMS.Repo, repo.id) + + {:ok, records} = ORM.find_all(ArticleUserEmotion, %{page: 1, size: 10}) + assert records.total_count == 1 + + {:ok, record} = ORM.find_by(ArticleUserEmotion, %{repo_id: repo.id, user_id: user.id}) + assert record.downvote + assert record.heart + end + + test "different user can make same emotions on same repo", + ~m(community repo_attrs user user2 user3)a do + {:ok, repo} = CMS.create_article(community, :repo, repo_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:repo, repo.id, :beer, user) + {:ok, _} = CMS.emotion_to_article(:repo, repo.id, :beer, user2) + {:ok, _} = CMS.emotion_to_article(:repo, repo.id, :beer, user3) + + {:ok, %{emotions: emotions}} = ORM.find(CMS.Repo, repo.id) + + 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 + + test "same user can make differcent emotions on same repo", ~m(community repo_attrs user)a do + {:ok, repo} = CMS.create_article(community, :repo, repo_attrs, user) + + {:ok, _} = CMS.emotion_to_article(:repo, repo.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:repo, repo.id, :downvote, user) + {:ok, _} = CMS.emotion_to_article(:repo, repo.id, :beer, user) + {:ok, _} = CMS.emotion_to_article(:repo, repo.id, :heart, user) + {:ok, _} = CMS.emotion_to_article(:repo, repo.id, :orz, user) + + {:ok, %{emotions: emotions}} = ORM.find(CMS.Repo, repo.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_web/query/cms/paged_articles/paged_posts_test.exs b/test/groupher_server_web/query/cms/paged_articles/paged_posts_test.exs index 6d88fbfed..f8e3c0ea9 100644 --- a/test/groupher_server_web/query/cms/paged_articles/paged_posts_test.exs +++ b/test/groupher_server_web/query/cms/paged_articles/paged_posts_test.exs @@ -124,7 +124,6 @@ defmodule GroupherServer.Test.Query.PagedArticles.PagedPosts do variables = %{filter: %{}} results = guest_conn |> query_result(@query, variables, "pagedPosts") inserted_timestamps = results["entries"] |> Enum.map(& &1["inserted_at"]) - # IO.inspect(inserted_timestamps, label: "inserted_timestamps") {:ok, first_inserted_time, 0} = inserted_timestamps |> List.first() |> DateTime.from_iso8601()