From e36a8a133494de5847580ee8bf9c60967f2ecf11 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Wed, 19 May 2021 17:52:23 +0800 Subject: [PATCH 01/23] refactor(community): rename table && clean up warnings --- .../accounts/delegates/upvoted_articles.ex | 1 - lib/groupher_server/cms/cms.ex | 6 ------ .../cms/delegates/article_community.ex | 1 - .../cms/delegates/article_tag.ex | 2 +- .../cms/delegates/community_curd.ex | 3 +-- lib/groupher_server/cms/helper/macros.ex | 10 +++++----- ...05_rename_communiites_article_join_table.exs | 17 +++++++++++++++++ 7 files changed, 24 insertions(+), 16 deletions(-) create mode 100644 priv/repo/migrations/20210519094205_rename_communiites_article_join_table.exs diff --git a/lib/groupher_server/accounts/delegates/upvoted_articles.ex b/lib/groupher_server/accounts/delegates/upvoted_articles.ex index 5e0c72c82..4f8cc65e7 100644 --- a/lib/groupher_server/accounts/delegates/upvoted_articles.ex +++ b/lib/groupher_server/accounts/delegates/upvoted_articles.ex @@ -2,7 +2,6 @@ defmodule GroupherServer.Accounts.Delegate.UpvotedArticles do @moduledoc """ get contents(posts, jobs ...) that user upvotes """ - # import GroupherServer.CMS.Helper.Matcher import Ecto.Query, warn: false import Helper.Utils, only: [done: 1] import ShortMaps diff --git a/lib/groupher_server/cms/cms.ex b/lib/groupher_server/cms/cms.ex index 782b5186a..4d89e2ecd 100644 --- a/lib/groupher_server/cms/cms.ex +++ b/lib/groupher_server/cms/cms.ex @@ -51,9 +51,6 @@ defmodule GroupherServer.CMS do defdelegate unset_article_tag(thread, article_id, tag_id), to: ArticleTag defdelegate paged_article_tags(filter), to: ArticleTag - defdelegate create_tag(community, thread, attrs, user), to: CommunityCURD - defdelegate update_tag(attrs), to: CommunityCURD - # >> wiki & cheatsheet (sync with github) defdelegate get_wiki(community), to: CommunitySync defdelegate get_cheatsheet(community), to: CommunitySync @@ -113,9 +110,6 @@ defmodule GroupherServer.CMS do defdelegate undo_pin_article(thread, id, community_id), to: ArticleCommunity defdelegate lock_article_comment(article), to: ArticleCommunity - # >> tag: set / unset - defdelegate set_tag(thread, tag, content_id), to: ArticleCommunity - defdelegate unset_tag(thread, tag, content_id), to: ArticleCommunity # >> community: set / unset defdelegate mirror_article(thread, article_id, community_id), to: ArticleCommunity defdelegate unmirror_article(thread, article_id, community_id), to: ArticleCommunity diff --git a/lib/groupher_server/cms/delegates/article_community.ex b/lib/groupher_server/cms/delegates/article_community.ex index 2ae1c57c1..4bc48ce52 100644 --- a/lib/groupher_server/cms/delegates/article_community.ex +++ b/lib/groupher_server/cms/delegates/article_community.ex @@ -2,7 +2,6 @@ defmodule GroupherServer.CMS.Delegate.ArticleCommunity do @moduledoc """ set / unset operations for Article-like resource """ - import GroupherServer.CMS.Helper.Matcher import GroupherServer.CMS.Helper.Matcher2 import Ecto.Query, warn: false diff --git a/lib/groupher_server/cms/delegates/article_tag.ex b/lib/groupher_server/cms/delegates/article_tag.ex index b124bc96c..5f8f3538e 100644 --- a/lib/groupher_server/cms/delegates/article_tag.ex +++ b/lib/groupher_server/cms/delegates/article_tag.ex @@ -5,7 +5,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleTag do import Ecto.Query, warn: false import GroupherServer.CMS.Helper.Matcher2 import Helper.Validator.Guards, only: [g_is_id: 1] - import Helper.Utils, only: [done: 1, camelize_map_key: 2, map_atom_values_to_upcase_str: 1] + import Helper.Utils, only: [done: 1, map_atom_values_to_upcase_str: 1] import GroupherServer.CMS.Delegate.ArticleCURD, only: [ensure_author_exists: 1] import ShortMaps import Helper.ErrorCode diff --git a/lib/groupher_server/cms/delegates/community_curd.ex b/lib/groupher_server/cms/delegates/community_curd.ex index 96b61c53a..fb10f3060 100644 --- a/lib/groupher_server/cms/delegates/community_curd.ex +++ b/lib/groupher_server/cms/delegates/community_curd.ex @@ -3,8 +3,7 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do community curd """ import Ecto.Query, warn: false - import GroupherServer.CMS.Helper.Matcher - import Helper.Utils, only: [done: 1, map_atom_value: 2] + import Helper.Utils, only: [done: 1] import GroupherServer.CMS.Delegate.ArticleCURD, only: [ensure_author_exists: 1] import ShortMaps diff --git a/lib/groupher_server/cms/helper/macros.ex b/lib/groupher_server/cms/helper/macros.ex index 988fd458d..0c5b6c67f 100644 --- a/lib/groupher_server/cms/helper/macros.ex +++ b/lib/groupher_server/cms/helper/macros.ex @@ -222,18 +222,18 @@ defmodule GroupherServer.CMS.Helper.Macros do @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) + # TABLE: "communities_join_[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])) + create(unique_index(:communities_job_[article]s, [:community_id, :[article]_id])) """ defmacro article_communities_field(thread) do quote do many_to_many( :communities, Community, - join_through: unquote("communities_#{to_string(thread)}s"), + join_through: unquote("communities_join_#{to_string(thread)}s"), on_replace: :delete ) end diff --git a/priv/repo/migrations/20210519094205_rename_communiites_article_join_table.exs b/priv/repo/migrations/20210519094205_rename_communiites_article_join_table.exs new file mode 100644 index 000000000..288efcfaf --- /dev/null +++ b/priv/repo/migrations/20210519094205_rename_communiites_article_join_table.exs @@ -0,0 +1,17 @@ +defmodule GroupherServer.Repo.Migrations.RenameCommuniitesArticleJoinTable do + use Ecto.Migration + + def change do + rename(table(:communities_posts), to: table(:communities_join_posts)) + rename(table(:communities_jobs), to: table(:communities_join_jobs)) + rename(table(:communities_repos), to: table(:communities_join_repos)) + + # drop(unique_index(:communities_posts, [:community_id, :post_id])) + # drop(unique_index(:communities_posts, [:community_id, :post_id])) + # drop(unique_index(:communities_posts, [:community_id, :post_id])) + + create(unique_index(:communities_join_posts, [:community_id, :post_id])) + create(unique_index(:communities_join_jobs, [:community_id, :job_id])) + create(unique_index(:communities_join_repos, [:community_id, :repo_id])) + end +end From e507108e6420e7e7760a694209ce448fd3704601 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Wed, 19 May 2021 18:02:46 +0800 Subject: [PATCH 02/23] refactor(community): re-org macro helper file --- cover/excoveralls.json | 2 +- .../accounts/delegates/collect_folder.ex | 2 +- .../accounts/delegates/publish.ex | 2 +- .../cms/delegates/abuse_report.ex | 2 +- .../cms/delegates/article_collect.ex | 2 +- .../cms/delegates/article_comment.ex | 2 +- .../cms/delegates/article_comment_action.ex | 2 +- .../cms/delegates/article_community.ex | 4 +- .../cms/delegates/article_curd.ex | 2 +- .../cms/delegates/article_emotion.ex | 2 +- .../cms/delegates/article_tag.ex | 2 +- .../cms/delegates/article_upvote.ex | 2 +- .../cms/delegates/comment_curd.ex | 2 +- lib/groupher_server/cms/delegates/helper.ex | 2 +- lib/groupher_server/cms/delegates/seeds.ex | 1 - lib/groupher_server/cms/helper/matcher.ex | 90 ++++++------------- .../helper/{matcher2.ex => matcher_macros.ex} | 42 +-------- lib/groupher_server/cms/helper/matcher_old.ex | 75 ++++++++++++++++ .../middleware/passport_loader.ex | 2 +- 19 files changed, 119 insertions(+), 121 deletions(-) rename lib/groupher_server/cms/helper/{matcher2.ex => matcher_macros.ex} (70%) create mode 100644 lib/groupher_server/cms/helper/matcher_old.ex diff --git a/cover/excoveralls.json b/cover/excoveralls.json index b218cfe3f..560df5dbf 100644 --- a/cover/excoveralls.json +++ b/cover/excoveralls.json @@ -1 +1 @@ -{"source_files":[{"coverage":[null,null,null,null,null,null,null,null,null,8,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null,null,null,null],"name":"lib/groupher_server/accounts/github_user.ex","source":"defmodule GroupherServer.Accounts.GithubUser do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @type t :: %GithubUser{}\n schema \"github_users\" do\n belongs_to(:user, User)\n\n field(:github_id, :string)\n field(:login, :string)\n field(:avatar_url, :string)\n field(:url, :string)\n field(:html_url, :string)\n field(:name, :string)\n field(:company, :string)\n field(:blog, :string)\n field(:location, :string)\n field(:email, :string)\n field(:bio, :string)\n field(:public_repos, :integer)\n field(:public_gists, :integer)\n field(:followers, :integer)\n field(:following, :integer)\n field(:access_token, :string)\n field(:node_id, :string)\n\n timestamps(type: :utc_datetime)\n end\n\n # @required_fields ~w(github_id login name avatar_url)a\n @required_fields ~w(github_id login avatar_url user_id access_token node_id)a\n @optional_fields ~w(blog company email bio followers following location html_url public_repos public_gists)a\n\n @doc false\n def changeset(%GithubUser{} = github_user, attrs) do\n # |> cast(attrs, [:username, :nickname, :bio, :company])\n # |> validate_required([:username])\n # |> cast(attrs, @required_fields, @optional_fields)\n github_user\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:github_id)\n |> unique_constraint(:node_id)\n |> foreign_key_constraint(:user_id)\n\n # |> validate_length(:username, max: 20)\n # |> unique_constraint(:username)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,2,null,1,1,null,null,null,null,null,null,null,1,1,1,null,null,null,null,3,1,1,null,null,null,null,null,null,null,null,null,null,4,null,null,null,null,null,null,5,null,5,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/accounts/delegates/billing.ex","source":"defmodule GroupherServer.Accounts.Delegate.Billing do\n @moduledoc \"\"\"\n user billings related\n \"\"\"\n import Ecto.Query, warn: false\n\n alias Helper.ORM\n alias GroupherServer.Accounts.{Purchase, User}\n\n # ...\n def purchase_service(%User{} = _user, map) when map_size(map) == 0 do\n {:error, \"AccountPurchase: invalid option or not purchased\"}\n end\n\n def purchase_service(%User{} = user, map) when is_map(map) do\n valid? = map |> Map.keys() |> Enum.all?(&can_purchase?(user, &1, :boolean))\n\n case valid? do\n true ->\n attrs = Map.merge(%{user_id: user.id}, map)\n Purchase |> ORM.upsert_by([user_id: user.id], attrs)\n\n false ->\n {:error, \"AccountCustomization: invalid option or not purchased\"}\n end\n end\n\n def purchase_service(%User{} = user, key, value \\\\ true) do\n with {:ok, key} <- can_purchase?(user, key) do\n attrs = Map.put(%{user_id: user.id}, key, value)\n Purchase |> ORM.upsert_by([user_id: user.id], attrs)\n end\n end\n\n def has_purchased?(%User{} = user, key) do\n with {:ok, purchase} <- Purchase |> ORM.find_by(user_id: user.id),\n value <- purchase |> Map.get(key) do\n case value do\n true -> {:ok, key}\n false -> {:error, \"AccountPurchase: not purchase\"}\n end\n else\n nil -> {:error, \"AccountPurchase: not purchase\"}\n _ -> {:error, \"AccountPurchase: not purchase\"}\n end\n end\n\n defp can_purchase?(%User{} = user, key, :boolean) do\n case can_purchase?(%User{} = user, key) do\n {:ok, _} -> true\n {:error, _} -> false\n end\n end\n\n defp can_purchase?(%User{} = _user, key) do\n valid_service_options = valid_service()\n\n case key in valid_service_options do\n true -> {:ok, key}\n false -> {:error, \"AccountPurchase: purchase invalid service\"}\n end\n end\n\n defp valid_service do\n [:brainwash_free, :community_chart]\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,29,null,null,null,null,null,null,null,null,null,null,null,null,10,null,null],"name":"lib/groupher_server/statistics/user_contribute.ex","source":"defmodule GroupherServer.Statistics.UserContribute do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n @type t :: %UserContribute{}\n schema \"user_contributes\" do\n field(:count, :integer)\n field(:date, :date)\n belongs_to(:user, Accounts.User)\n\n timestamps()\n end\n\n @doc false\n def changeset(%UserContribute{} = user_contribute, attrs) do\n user_contribute\n |> cast(attrs, [:date, :count, :user_id])\n |> validate_required([:date, :count, :user_id])\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,77,84,null,null,6,null,3,22,2,3,2,0,null,4,4,4,null,null,null],"name":"lib/helper/error_code.ex","source":"defmodule Helper.ErrorCode do\n @moduledoc \"\"\"\n error code map for all site\n \"\"\"\n @default_base 4000\n @account_base 4300\n @changeset_base 4100\n @throttle_base 4200\n\n # account error code\n def ecode(:account_login), do: @account_base + 1\n def ecode(:passport), do: @account_base + 2\n # ...\n # changeset error code\n def ecode(:changeset), do: @changeset_base + 2\n # ...\n def ecode(:custom), do: @default_base + 1\n def ecode(:pagination), do: @default_base + 2\n def ecode(:not_exsit), do: @default_base + 3\n def ecode(:already_did), do: @default_base + 4\n def ecode(:self_conflict), do: @default_base + 5\n def ecode(:react_fails), do: @default_base + 6\n # throttle\n def ecode(:throttle_inverval), do: @throttle_base + 1\n def ecode(:throttle_hour), do: @throttle_base + 2\n def ecode(:throttle_day), do: @throttle_base + 3\n def ecode, do: @default_base\n # def ecode(_), do: @default_base\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,102,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,6,null,null,null,null],"name":"lib/groupher_server/cms/thread.ex","source":"defmodule GroupherServer.CMS.Thread do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n @optional_fields ~w(logo index)a\n @required_fields ~w(title raw)a\n\n @type t :: %Thread{}\n schema \"threads\" do\n field(:title, :string)\n field(:raw, :string)\n field(:logo, :string)\n field(:index, :integer)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Thread{} = thread, attrs) do\n thread\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> validate_length(:title, min: 2, max: 20)\n |> validate_length(:raw, min: 2, max: 20)\n |> unique_constraint(:title)\n\n # |> unique_constraint(:raw)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,29,null,null,29,29,29,29,null,null,null,null,29,null,null,null,null,null,null,31,null,null],"name":"lib/groupher_server/delivery/delegates/mentions.ex","source":"defmodule GroupherServer.Delivery.Delegate.Mentions do\n @moduledoc \"\"\"\n The Delivery context.\n \"\"\"\n import Helper.Utils, only: [done: 2]\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.Delivery.Mention\n alias Helper.ORM\n\n alias GroupherServer.Delivery.Delegate.Utils\n\n def mention_someone(%User{id: from_user_id}, %User{id: to_user_id}, info) do\n attrs = %{\n from_user_id: from_user_id,\n to_user_id: to_user_id,\n source_id: info.source_id,\n source_title: info.source_title,\n source_type: info.source_type,\n source_preview: info.source_preview\n }\n\n Mention\n |> ORM.create(attrs)\n |> done(:status)\n end\n\n @doc \"\"\"\n fetch mentions from Delivery stop\n \"\"\"\n def fetch_mentions(%User{} = user, %{page: _, size: _, read: _} = filter) do\n Utils.fetch_messages(user, Mention, filter)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,0,null,0,0,null,null,null,null,null],"name":"test/support/channel_case.ex","source":"defmodule GroupherServerWeb.ChannelCase do\n @moduledoc \"\"\"\n This module defines the test case to be used by\n channel tests.\n\n Such tests rely on `Phoenix.ChannelTest` and also\n import other functionality to make it easier\n to build common datastructures and query the data layer.\n\n Finally, if the test case interacts with the database,\n it cannot be async. For this reason, every test runs\n inside a transaction which is reset at the beginning\n of the test unless the test case is marked as async.\n \"\"\"\n\n use ExUnit.CaseTemplate\n\n using do\n quote do\n # Import conveniences for testing with channels\n use Phoenix.ChannelTest\n\n # The default endpoint for testing\n @endpoint GroupherServerWeb.Endpoint\n end\n end\n\n setup tags do\n :ok = Ecto.Adapters.SQL.Sandbox.checkout(GroupherServer.Repo)\n\n unless tags[:async] do\n Ecto.Adapters.SQL.Sandbox.mode(GroupherServer.Repo, {:shared, self()})\n end\n\n :ok\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,0,null,null,361,null,null,361,null,null,null,null,null,null,null,null,null,null,361,203,203,null,158,null,null,null,null,203,203,null,203,null,null,null,null,null,null,null,null,null,null,203,null,null,null,null,null,null],"name":"lib/groupher_server_web/context.ex","source":"# a plug for router ...\n\ndefmodule GroupherServerWeb.Context do\n @behaviour Plug\n\n import Plug.Conn\n # import Ecto.Query, only: [first: 1]\n\n alias GroupherServer.{Accounts, CMS}\n alias Helper.{Guardian, ORM}\n\n def init(opts), do: opts\n\n def call(conn, _) do\n context = build_context(conn)\n # put_private(conn, :absinthe, %{context: context})\n # TODO: use https://github.com/absinthe-graphql/absinthe/pull/497/files\n Absinthe.Plug.put_options(conn, context: context)\n end\n\n @doc \"\"\"\n Return the current user context based on the authorization header.\n\n Important: Note that at the current time this is just a stub, always\n returning the first user (marked as an admin), provided any\n authorization header is sent.\n \"\"\"\n def build_context(conn) do\n with [\"Bearer \" <> token] <- get_req_header(conn, \"authorization\"),\n {:ok, cur_user} <- authorize(token) do\n %{cur_user: cur_user}\n else\n _ -> %{}\n end\n end\n\n defp authorize(token) do\n with {:ok, claims, _info} <- Guardian.jwt_decode(token) do\n case ORM.find(Accounts.User, claims.id) do\n {:ok, user} ->\n check_passport(user)\n\n {:error, _} ->\n {:error,\n \"user is not exsit, try revoke token, or if you in dev env run the seeds first.\"}\n end\n end\n end\n\n # TODO gather role info from CMS or other context\n defp check_passport(%Accounts.User{} = user) do\n with {:ok, cms_passport} <- CMS.get_passport(%Accounts.User{id: user.id}) do\n {:ok, Map.put(user, :cur_passport, %{\"cms\" => cms_passport})}\n else\n {:error, _} -> {:ok, user}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1,null,7,null,9,3,null,6,null,7,1,2,1,null,null,null,8,2,null,59,1,null,8,1,null,76,2,null,null,44,30,9,null,null,139,5,null,null,null,28,null,10,1,null,5,1,null,null,197,5,11,3,9,null,null,null,20,2,null,14,2,null,null,80,4,211,3,0,null],"name":"lib/groupher_server/cms/cms.ex","source":"defmodule GroupherServer.CMS do\n @moduledoc \"\"\"\n this module defined basic method to handle [CMS] content [CURD] ..\n [CMS]: post, job, ...\n [CURD]: create, update, delete ...\n \"\"\"\n alias GroupherServer.CMS.Delegate.{\n ArticleCURD,\n ArticleCommunity,\n ArticleReaction,\n CommentCURD,\n CommentReaction,\n CommunityCURD,\n CommunityOperation,\n PassportCURD\n }\n\n # do not pattern match in delegating func, do it on one delegating inside\n # see https://github.com/elixir-lang/elixir/issues/5306\n\n # Community CURD: editors, thread, tag\n # >> editor ..\n defdelegate update_editor(user, community, title), to: CommunityCURD\n # >> subscribers / editors\n defdelegate community_members(type, community, filters), to: CommunityCURD\n # >> category\n defdelegate create_category(category_attrs, user), to: CommunityCURD\n defdelegate update_category(category_attrs), to: CommunityCURD\n # >> thread\n defdelegate create_thread(attrs), to: CommunityCURD\n # >> tag\n defdelegate create_tag(thread, attrs, user), to: CommunityCURD\n defdelegate update_tag(attrs), to: CommunityCURD\n defdelegate get_tags(community, thread), to: CommunityCURD\n defdelegate get_tags(filter), to: CommunityCURD\n\n # CommunityOperation\n # >> category\n defdelegate set_category(community, category), to: CommunityOperation\n defdelegate unset_category(community, category), to: CommunityOperation\n # >> editor\n defdelegate set_editor(community, title, user), to: CommunityOperation\n defdelegate unset_editor(community, user), to: CommunityOperation\n # >> thread\n defdelegate set_thread(community, thread), to: CommunityOperation\n defdelegate unset_thread(community, thread), to: CommunityOperation\n # >> subscribe / unsubscribe\n defdelegate subscribe_community(community, user), to: CommunityOperation\n defdelegate unsubscribe_community(community, user), to: CommunityOperation\n\n # ArticleCURD\n defdelegate paged_contents(queryable, filter), to: ArticleCURD\n defdelegate create_article(community, thread, attrs, user), to: ArticleCURD\n defdelegate reaction_users(thread, react, id, filters), to: ArticleCURD\n\n # ArticleReaction\n defdelegate reaction(thread, react, content_id, user), to: ArticleReaction\n defdelegate undo_reaction(thread, react, content_id, user), to: ArticleReaction\n\n # ArticleCommunity\n # >> set flag on article, like: pin / unpin article\n defdelegate set_flag(queryable, id, attrs, user), to: ArticleCommunity\n # >> tag: set / unset\n defdelegate set_tag(community, thread, tag, content_id), to: ArticleCommunity\n defdelegate unset_tag(thread, tag, content_id), to: ArticleCommunity\n # >> community: set / unset\n defdelegate mirror_article(community, thread, content_id), to: ArticleCommunity\n defdelegate unmirror_article(community, thread, content_id), to: ArticleCommunity\n\n # Comment CURD\n defdelegate create_comment(thread, content_id, body, user), to: CommentCURD\n defdelegate delete_comment(thread, content_id), to: CommentCURD\n defdelegate paged_comments(thread, content_id, filters), to: CommentCURD\n defdelegate paged_replies(thread, comment, user), to: CommentCURD\n defdelegate reply_comment(thread, comment, body, user), to: CommentCURD\n\n # Comment Reaction\n # >> like / undo like\n defdelegate like_comment(thread, comment, user), to: CommentReaction\n defdelegate undo_like_comment(thread, comment, user), to: CommentReaction\n # >> dislike / undo dislike\n defdelegate dislike_comment(thread, comment, user), to: CommentReaction\n defdelegate undo_dislike_comment(thread, comment, user), to: CommentReaction\n\n # Passport CURD\n defdelegate stamp_passport(rules, user), to: PassportCURD\n defdelegate erase_passport(rules, user), to: PassportCURD\n defdelegate get_passport(user), to: PassportCURD\n defdelegate paged_passports(community, key), to: PassportCURD\n defdelegate delete_passport(user), to: PassportCURD\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,115,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,6,null,null,null,null],"name":"lib/groupher_server/cms/tag.ex","source":"defmodule GroupherServer.CMS.Tag do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, Job, Post, Video}\n\n @required_fields ~w(thread title color author_id community_id)a\n\n @type t :: %Tag{}\n schema \"tags\" do\n field(:title, :string)\n field(:color, :string)\n field(:thread, :string)\n belongs_to(:community, Community)\n belongs_to(:author, Author)\n\n many_to_many(\n :posts,\n Post,\n join_through: \"posts_tags\",\n join_keys: [post_id: :id, tag_id: :id],\n # :delete_all will only remove data from the join source\n on_delete: :delete_all\n # on_replace: :delete\n )\n\n many_to_many(\n :videos,\n Video,\n join_through: \"videos_tags\",\n join_keys: [video_id: :id, tag_id: :id]\n )\n\n many_to_many(\n :jobs,\n Job,\n join_through: \"jobs_tags\",\n join_keys: [job_id: :id, tag_id: :id]\n )\n\n timestamps(type: :utc_datetime)\n end\n\n def changeset(%Tag{} = tag, attrs) do\n tag\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> foreign_key_constraint(:community_id)\n |> unique_constraint(:tag_duplicate, name: :tags_community_id_thread_title_index)\n\n # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,107,null,null,null,null,null,null,null,null,null,null,null,null,null,97,null,null],"name":"lib/groupher_server/accounts/user_follower.ex","source":"defmodule GroupherServer.Accounts.UserFollower do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id follower_id)a\n\n @type t :: %UserFollower{}\n schema \"users_followers\" do\n belongs_to(:user, User, foreign_key: :user_id)\n belongs_to(:follower, User, foreign_key: :follower_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%UserFollower{} = user_follower, attrs) do\n user_follower\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> foreign_key_constraint(:follower_id)\n |> unique_constraint(:user_id, name: :users_followers_user_id_follower_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,108,null,null,null,223,null,null,null,null,null,null,null,253,null,253,null,null,null,null,null,null,null,null,null,1407,null,null,null,null,null,null,null,null,1232,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,103,null,null,null,null,null,null,null,0,null,null,null,null,null,null,17,17,null,null,null,null,17,null,17,null,null,null,null,17,null,null,null,null,null,null,32,null,null,5,4,null,null,null,null,19,18,null,null,null,null,252,null,null,null,null,180,null,null,null,null,null,null,null,null,null,310,310,null,null,null,null,null,28,3,null,null,31,null,31,31,null,null,null,null,null,null,null,1,null,null,1,null,null,null,null,32,null,null,1,1,null,null,31,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,977,null,null,null,null,null,null,null,null,null,209,209,null,null,null,205,null,null],"name":"lib/helper/orm.ex","source":"defmodule Helper.ORM do\n @moduledoc \"\"\"\n General CORD functions\n \"\"\"\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1, done: 3, add: 1]\n import Helper.ErrorHandler\n import ShortMaps\n\n alias Helper.{QueryBuilder, SpecType}\n alias GroupherServer.Repo\n\n @doc \"\"\"\n a wrap for paginate request\n \"\"\"\n def paginater(queryable, page: page, size: size) do\n queryable |> Repo.paginate(page: page, page_size: size)\n end\n\n def paginater(queryable, ~m(page size)a) do\n queryable |> Repo.paginate(page: page, page_size: size)\n end\n\n @doc \"\"\"\n wrap Repo.get with preload and result/errer format handle\n \"\"\"\n def find(queryable, id, preload: preload) do\n queryable\n |> preload(^preload)\n |> Repo.get(id)\n |> done(queryable, id)\n end\n\n @doc \"\"\"\n simular to Repo.get/3, with standard result/error handle\n \"\"\"\n @spec find(Ecto.Queryable.t(), SpecType.id()) :: {:ok, any()} | {:error, String.t()}\n def find(queryable, id) do\n queryable\n |> Repo.get(id)\n |> done(queryable, id)\n end\n\n @doc \"\"\"\n simular to Repo.get_by/3, with standard result/error handle\n \"\"\"\n def find_by(queryable, clauses) do\n queryable\n |> Repo.get_by(clauses)\n |> case do\n nil ->\n {:error, not_found_formater(queryable, clauses)}\n\n result ->\n {:ok, result}\n end\n end\n\n @doc \"\"\"\n return pageinated Data required by filter\n \"\"\"\n # TODO: find content not in markDelete by default\n def find_all(queryable, %{page: page, size: size} = filter) do\n queryable\n |> QueryBuilder.filter_pack(filter)\n |> paginater(page: page, size: size)\n |> done()\n end\n\n @doc \"\"\"\n return Data required by filter\n \"\"\"\n # TODO: find content not in markDelete by default\n def find_all(queryable, filter) do\n queryable |> QueryBuilder.filter_pack(filter) |> Repo.all() |> done()\n end\n\n @doc \"\"\"\n Require queryable has a views fields to count the views of the queryable Modal\n \"\"\"\n def read(queryable, id, inc: :views) do\n with {:ok, result} <- find(queryable, id) do\n result |> inc_views_count(queryable) |> done()\n end\n end\n\n defp inc_views_count(content, queryable) do\n {1, [result]} =\n Repo.update_all(\n from(p in queryable, where: p.id == ^content.id),\n [inc: [views: 1]],\n returning: [:views]\n )\n\n put_in(content.views, result.views)\n end\n\n @doc \"\"\"\n NOTICE: this should be use together with Authorize/OwnerCheck etc Middleware\n DO NOT use it directly\n \"\"\"\n def delete(content), do: Repo.delete(content)\n\n def find_delete(queryable, id) do\n with {:ok, content} <- find(queryable, id) do\n delete(content)\n end\n end\n\n def findby_delete(queryable, clauses) do\n with {:ok, content} <- find_by(queryable, clauses) do\n delete(content)\n end\n end\n\n def findby_or_insert(queryable, clauses, attrs) do\n case queryable |> find_by(clauses) do\n {:ok, content} ->\n {:ok, content}\n\n {:error, _} ->\n queryable |> create(attrs)\n end\n end\n\n @doc \"\"\"\n NOTE: this should be use together with passport_loader etc Middleware\n DO NOT use it directly\n \"\"\"\n def update(content, attrs) do\n content\n |> content.__struct__.changeset(attrs)\n |> Repo.update()\n end\n\n @doc \"\"\"\n find and update sourc\n \"\"\"\n def find_update(queryable, id, attrs), do: do_find_update(queryable, id, attrs)\n def find_update(queryable, %{id: id} = attrs), do: do_find_update(queryable, id, attrs)\n\n defp do_find_update(queryable, id, attrs) do\n with {:ok, content} <- find(queryable, id) do\n content\n |> content.__struct__.changeset(attrs)\n |> Repo.update()\n end\n end\n\n @doc \"\"\"\n find then update\n \"\"\"\n def update_by(source, clauses, attrs) do\n with {:ok, content} <- find_by(source, clauses) do\n content\n |> Ecto.Changeset.change(attrs)\n |> Repo.update()\n end\n end\n\n def upsert_by(queryable, clauses, attrs) do\n case queryable |> find_by(clauses) do\n {:ok, content} ->\n content\n |> content.__struct__.changeset(attrs)\n |> Repo.update()\n\n {:error, _} ->\n queryable |> create(attrs)\n end\n end\n\n @doc \"\"\"\n see https://elixirforum.com/t/ecto-inc-dec-update-one-helpers/5564\n \"\"\"\n # def update_one(queryable, where, changes) do\n # query |> Ecto.Query.where(^where) |> Repo.update_all(set: changes)\n # end\n\n # def inc(queryable, where, changes) do\n # query |> Ecto.Query.where(^where) |> Repo.update_all(inc: changes)\n # end\n\n def create(model, attrs) do\n model\n |> struct\n |> model.changeset(attrs)\n |> Repo.insert()\n end\n\n @doc \"\"\"\n return the total count of a Modal based on id column\n also support filters\n \"\"\"\n def count(queryable, filter \\\\ %{}) do\n queryable\n |> QueryBuilder.filter_pack(filter)\n |> select([f], count(f.id))\n |> Repo.one()\n end\n\n def next_count(queryable) do\n queryable |> count() |> add()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,20,null,20,null,null,null,628,null,628,null,null,null,null,628,null,628,null,null],"name":"lib/helper/error_handler.ex","source":"defmodule Helper.ErrorHandler do\n @moduledoc \"\"\"\n This module defines some helper function used by\n handle/format changset errors\n \"\"\"\n alias GroupherServerWeb.Gettext, as: Translator\n\n def not_found_formater(queryable, id) when is_integer(id) or is_binary(id) do\n model = queryable |> to_string |> String.split(\".\") |> List.last()\n\n Translator |> Gettext.dgettext(\"404\", \"#{model}(%{id}) not found\", id: id)\n end\n\n def not_found_formater(queryable, clauses) do\n model = queryable |> to_string |> String.split(\".\") |> List.last()\n\n detail =\n clauses\n |> Enum.into(%{})\n |> Map.values()\n |> List.first()\n |> to_string\n\n Translator |> Gettext.dgettext(\"404\", \"#{model}(%{name}) not found\", name: detail)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,0,null,null,17,null,null,null,58,null,null,0,null],"name":"lib/groupher_server_web/middleware/covert_to_int.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.ConvertToInt do\n @behaviour Absinthe.Middleware\n # google: must appear in the GROUP BY clause or be used in an aggregate function\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(%{value: [value]} = resolution, _) do\n %{resolution | value: value}\n end\n\n def call(%{value: []} = resolution, _) do\n %{resolution | value: 0}\n end\n\n def call(resolution, _), do: resolution\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,44,null,null,null,44,null,null,null,null,44,44,38,38,null,38,null,35,null,null,3,3,null,3,3,null,null,null,null,3,null,null,null,6,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,30,30,30,26,26,null,26,26,null,26,26,null,null,null,null,null,null,null,null,null,null,null,null,null,9,9,null,9,null,null,null,9,null,null,null,null,null,null,null,46,null,null,null,null,46,null,null,null,null,null,7,null,null],"name":"lib/groupher_server/cms/delegates/article_curd.ex","source":"defmodule GroupherServer.CMS.Delegate.ArticleCURD do\n import Ecto.Query, warn: false\n import GroupherServer.CMS.Helper.Matcher\n import Helper.Utils, only: [done: 1]\n import ShortMaps\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.{Repo, CMS, Statistics}\n alias GroupherServer.CMS.Delegate.ArticleCommunity\n alias Helper.{ORM, QueryBuilder}\n\n alias CMS.{Author, Community}\n\n @doc \"\"\"\n get paged post / job ...\n \"\"\"\n def paged_contents(queryable, filter) do\n normal_content_fr = filter |> Map.merge(QueryBuilder.default_article_filters())\n\n queryable\n |> ORM.find_all(normal_content_fr)\n |> add_pin_contents_ifneed(queryable, filter)\n end\n\n # only first page need pin contents\n defp add_pin_contents_ifneed(contents, queryable, filter) do\n with {:ok, normal_contents} <- contents,\n true <- 1 == Map.get(normal_contents, :page_number) do\n pin_content_fr = filter |> Map.merge(%{pin: true})\n {:ok, pined_content} = queryable |> ORM.find_all(pin_content_fr)\n\n case pined_content |> Map.get(:total_count) do\n 0 ->\n contents\n\n _ ->\n pind_entries = pined_content |> Map.get(:entries)\n normal_entries = normal_contents |> Map.get(:entries)\n\n normal_count = normal_contents |> Map.get(:total_count)\n pind_count = pined_content |> Map.get(:total_count)\n\n normal_contents\n |> Map.put(:entries, pind_entries ++ normal_entries)\n |> Map.put(:total_count, pind_count + normal_count)\n |> done\n end\n else\n _error ->\n contents\n end\n end\n\n @doc \"\"\"\n Creates a content(post/job ...), and set community.\n\n ## Examples\n\n iex> create_post(%{field: value})\n {:ok, %Post{}}\n\n iex> create_post(%{field: bad_value})\n {:error, %Ecto.Changeset{}}\n\n \"\"\"\n def create_article(%Community{id: community_id}, thread, attrs, %User{id: user_id}) do\n with {:ok, author} <- ensure_author_exists(%User{id: user_id}),\n {:ok, action} <- match_action(thread, :community),\n {:ok, community} <- ORM.find(Community, community_id),\n {:ok, content} <-\n action.target\n |> struct()\n |> action.target.changeset(attrs)\n |> Ecto.Changeset.put_change(:author_id, author.id)\n |> Repo.insert() do\n Statistics.log_publish_action(%User{id: user_id})\n ArticleCommunity.mirror_article(community, thread, content.id)\n end\n end\n\n @doc \"\"\"\n get CMS contents\n post's favorites/stars/comments ...\n ...\n jobs's favorites/stars/comments ...\n\n with or without page info\n \"\"\"\n def reaction_users(thread, react, id, %{page: page, size: size} = filters) do\n # when valid_reaction(thread, react) do\n with {:ok, action} <- match_action(thread, react),\n {:ok, where} <- dynamic_where(thread, id) do\n # common_filter(action.reactor)\n action.reactor\n |> where(^where)\n |> QueryBuilder.load_inner_users(filters)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n end\n\n def ensure_author_exists(%User{} = user) do\n # unique_constraint: avoid race conditions, make sure user_id unique\n # foreign_key_constraint: check foreign key: user_id exsit or not\n # see alos no_assoc_constraint in https://hexdocs.pm/ecto/Ecto.Changeset.html\n %Author{user_id: user.id}\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.unique_constraint(:user_id)\n |> Ecto.Changeset.foreign_key_constraint(:user_id)\n |> Repo.insert()\n |> handle_existing_author()\n end\n\n defp handle_existing_author({:ok, author}), do: {:ok, author}\n\n defp handle_existing_author({:error, changeset}) do\n ORM.find_by(Author, user_id: changeset.data.user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/accounts/user_bill.ex","source":"defmodule GroupherServer.Accounts.UserBill do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.{Bill, User}\n\n @required_fields ~w(user_id bill_id)a\n\n @type t :: %UserBill{}\n schema \"users_bills\" do\n belongs_to(:user, User, foreign_key: :user_id)\n belongs_to(:bill, Bill, foreign_key: :bill_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%UserBill{} = user_bill, attrs) do\n user_bill\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> foreign_key_constraint(:bill_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,0,null,0,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,0,0,null,null,null,null],"name":"test/support/data_case.ex","source":"defmodule GroupherServer.DataCase do\n @moduledoc \"\"\"\n This module defines the setup for tests requiring\n access to the application's data layer.\n\n You may define functions here to be used as helpers in\n your tests.\n\n Finally, if the test case interacts with the database,\n it cannot be async. For this reason, every test runs\n inside a transaction which is reset at the beginning\n of the test unless the test case is marked as async.\n \"\"\"\n\n use ExUnit.CaseTemplate\n\n using do\n quote do\n alias GroupherServer.Repo\n\n import Ecto\n import Ecto.Changeset\n import Ecto.Query\n import GroupherServer.DataCase\n end\n end\n\n setup tags do\n :ok = Ecto.Adapters.SQL.Sandbox.checkout(GroupherServer.Repo)\n\n unless tags[:async] do\n Ecto.Adapters.SQL.Sandbox.mode(GroupherServer.Repo, {:shared, self()})\n end\n\n :ok\n end\n\n @doc \"\"\"\n A helper that transform changeset errors to a map of messages.\n\n assert {:error, changeset} = Accounts.create_user(%{password: \"short\"})\n assert \"password is too short\" in errors_on(changeset).password\n assert %{password: [\"password is too short\"]} = errors_on(changeset)\n\n \"\"\"\n def errors_on(changeset) do\n Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->\n Enum.reduce(opts, message, fn {key, value}, acc ->\n String.replace(acc, \"%{#{key}}\", to_string(value))\n end)\n end)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/account/account_queries.ex","source":"defmodule GroupherServerWeb.Schema.Account.Queries do\n @moduledoc \"\"\"\n accounts GraphQL queries\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :account_queries do\n @desc \"get all users\"\n field :paged_users, non_null(:paged_users) do\n arg(:filter, non_null(:paged_users_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.users/3)\n end\n\n @desc \"get user by id\"\n field :user, :user do\n arg(:id, non_null(:id))\n\n resolve(&R.Accounts.user/3)\n end\n\n @desc \"get login-user's info\"\n field :account, :user do\n middleware(M.Authorize, :login)\n\n resolve(&R.Accounts.account/3)\n end\n\n @desc \"anyone can get anyone's subscribed communities\"\n field :subscribed_communities, :paged_communities do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.subscribed_communities/3)\n end\n\n @desc \"get user's follower\"\n field :paged_followers, :paged_users do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.paged_followers/3)\n end\n\n @desc \"get user's follower\"\n field :paged_followings, :paged_users do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.paged_followings/3)\n end\n\n @desc \"get favorited posts\"\n field :favorited_posts, :paged_posts do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.favorited_posts/3)\n end\n\n @desc \"get favorited jobs\"\n field :favorited_jobs, :paged_jobs do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.favorited_jobs/3)\n end\n\n @desc \"get all passport rules include system and community etc ...\"\n field :all_passport_rules_string, :rules do\n middleware(M.Authorize, :login)\n\n resolve(&R.Accounts.get_all_rules/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/logs/user_activity.ex","source":"defmodule GroupherServer.Logs.UserActivity do\n @moduledoc false\n # alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id source_title source_id source_type)a\n # @optional_fields ~w(source_type)a\n\n schema \"user_activity_logs\" do\n belongs_to(:user, User)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(user_activity, attrs) do\n user_activity\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,42,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/notification.mail.ex","source":"defmodule GroupherServer.Accounts.NotificationMail do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id action source_id source_type source_preview)a\n @optional_fields ~w(parent_id parent_type read)a\n\n @type t :: %NotificationMail{}\n schema \"notification_mails\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n field(:action, :string)\n\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:parent_id, :string)\n field(:parent_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%NotificationMail{} = notication_mail, attrs) do\n notication_mail\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,51,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"test/support/test_tools.ex","source":"defmodule GroupherServer.TestTools do\n @moduledoc \"\"\"\n helper for reduce import mudules in test files\n \"\"\"\n use ExUnit.CaseTemplate\n\n using do\n quote do\n use GroupherServerWeb.ConnCase, async: true\n\n import GroupherServer.Factory\n import GroupherServer.Test.ConnSimulator\n import GroupherServer.Test.AssertHelper\n import Ecto.Query, warn: false\n import Helper.ErrorCode\n\n import ShortMaps\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,8,null,null,null,null,null,null,null,2,null,2,null,null,null,null,null,null,null,8,8,null,null,null,null,null,null,null,1,null,1,null,null,null,null,null,null,null,null,null,null,null,null,null,59,59,null,null,59,null,null,null,null,null,null,1,1,1,null,null,null,null,59,null,null,null,null,null,null,null,null,null,null,null,null,76,75,null,null,null,null,2,null,1,null,null,null],"name":"lib/groupher_server/cms/delegates/community_operation.ex","source":"defmodule GroupherServer.CMS.Delegate.CommunityOperation do\n @moduledoc \"\"\"\n community operations, like: set/unset category/thread/editor...\n \"\"\"\n import ShortMaps\n\n alias Ecto.Multi\n alias Helper.{Certification, ORM}\n alias GroupherServer.Accounts.User\n alias GroupherServer.CMS.Delegate.PassportCURD\n alias GroupherServer.Repo\n\n alias GroupherServer.CMS.{\n Category,\n Community,\n CommunityCategory,\n CommunityEditor,\n CommunitySubscriber,\n CommunityThread,\n Thread\n }\n\n @doc \"\"\"\n set a category to community\n \"\"\"\n def set_category(%Community{id: community_id}, %Category{id: category_id}) do\n with {:ok, community_category} <-\n CommunityCategory |> ORM.create(~m(community_id category_id)a) do\n Community |> ORM.find(community_category.community_id)\n end\n end\n\n @doc \"\"\"\n unset a category to community\n \"\"\"\n def unset_category(%Community{id: community_id}, %Category{id: category_id}) do\n with {:ok, community_category} <-\n CommunityCategory |> ORM.findby_delete!(~m(community_id category_id)a) do\n Community |> ORM.find(community_category.community_id)\n end\n end\n\n @doc \"\"\"\n set to thread to a community\n \"\"\"\n def set_thread(%Community{id: community_id}, %Thread{id: thread_id}) do\n with {:ok, community_thread} <- CommunityThread |> ORM.create(~m(community_id thread_id)a) do\n Community |> ORM.find(community_thread.community_id)\n end\n end\n\n @doc \"\"\"\n unset to thread to a community\n \"\"\"\n def unset_thread(%Community{id: community_id}, %Thread{id: thread_id}) do\n with {:ok, community_thread} <-\n CommunityThread |> ORM.findby_delete!(~m(community_id thread_id)a) do\n Community |> ORM.find(community_thread.community_id)\n end\n end\n\n @doc \"\"\"\n set a community editor\n \"\"\"\n def set_editor(%Community{id: community_id}, title, %User{id: user_id}) do\n Multi.new()\n |> Multi.insert(\n :insert_editor,\n CommunityEditor.changeset(%CommunityEditor{}, ~m(user_id community_id title)a)\n )\n |> Multi.run(:stamp_passport, fn _ ->\n rules = Certification.passport_rules(cms: title)\n PassportCURD.stamp_passport(rules, %User{id: user_id})\n end)\n |> Repo.transaction()\n |> set_editor_result()\n end\n\n @doc \"\"\"\n unset a community editor\n \"\"\"\n def unset_editor(%Community{id: community_id}, %User{id: user_id}) do\n with {:ok, _} <- ORM.findby_delete!(CommunityEditor, ~m(user_id community_id)a),\n {:ok, _} <- PassportCURD.delete_passport(%User{id: user_id}) do\n User |> ORM.find(user_id)\n end\n end\n\n defp set_editor_result({:ok, %{insert_editor: editor}}) do\n User |> ORM.find(editor.user_id)\n end\n\n defp set_editor_result({:error, :stamp_passport, _result, _steps}),\n do: {:error, \"stamp passport error\"}\n\n defp set_editor_result({:error, :insert_editor, _result, _steps}),\n do: {:error, \"insert editor error\"}\n\n @doc \"\"\"\n subscribe a community. (ONLY community, post etc use watch )\n \"\"\"\n def subscribe_community(%Community{id: community_id}, %User{id: user_id}) do\n with {:ok, record} <- CommunitySubscriber |> ORM.create(~m(user_id community_id)a) do\n Community |> ORM.find(record.community_id)\n end\n end\n\n def unsubscribe_community(%Community{id: community_id}, %User{id: user_id}) do\n with {:ok, record} <-\n CommunitySubscriber |> ORM.findby_delete!(community_id: community_id, user_id: user_id) do\n Community |> ORM.find(record.community_id)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null],"name":"lib/groupher_server_web/schema/account/account_misc.ex","source":"defmodule GroupherServerWeb.Schema.Account.Misc do\n use Absinthe.Schema.Notation\n\n import GroupherServerWeb.Schema.Helper.Fields\n # import Helper.Utils, only: [get_config: 2]\n # @page_size get_config(:general, :page_size)\n\n @desc \"article_filter doc\"\n input_object :paged_users_filter do\n pagination_args()\n # field(:when, :when_enum)\n # field(:sort, :sort_enum)\n # field(:article_tag, :string, default_value: :all)\n # field(:community, :string)\n end\n\n input_object :github_profile_input do\n # is github_id in db table\n field(:id, non_null(:string))\n field(:login, non_null(:string))\n field(:avatar_url, non_null(:string))\n field(:url, :string)\n field(:html_url, :string)\n field(:name, :string)\n field(:company, :string)\n field(:blog, :string)\n field(:location, :string)\n field(:email, :string)\n field(:bio, :string)\n field(:public_repos, :integer)\n field(:public_gists, :integer)\n end\n\n input_object :user_profile_input do\n field(:nickname, :string)\n field(:bio, :string)\n field(:sex, :string)\n field(:education, :string)\n field(:location, :string)\n field(:company, :string)\n field(:email, :string)\n field(:qq, :string)\n field(:weibo, :string)\n field(:weichat, :string)\n end\n\n # see: https://github.com/absinthe-graphql/absinthe/issues/206\n # https://github.com/absinthe-graphql/absinthe/wiki/Scalar-Recipes\n scalar :json, name: \"Json\" do\n description(\"\"\"\n The `Json` scalar type represents arbitrary json string data, represented as UTF-8\n character sequences. The Json type is most often used to represent a free-form\n human-readable json string.\n \"\"\")\n\n serialize(&encode/1)\n parse(&decode/1)\n end\n\n @spec decode(Absinthe.Blueprint.Input.String.t()) :: {:ok, term()} | :error\n @spec decode(Absinthe.Blueprint.Input.Null.t()) :: {:ok, nil}\n defp decode(%Absinthe.Blueprint.Input.String{value: value}) do\n case Jason.decode(value) do\n {:ok, result} -> {:ok, result}\n _ -> :error\n end\n end\n\n defp decode(%Absinthe.Blueprint.Input.Null{}) do\n {:ok, nil}\n end\n\n defp decode(_) do\n :error\n end\n\n defp encode(value), do: value\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,18,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,null,null,null,null],"name":"lib/groupher_server/cms/community_category.ex","source":"defmodule GroupherServer.CMS.CommunityCategory do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Category, Community}\n\n @type t :: %CommunityCategory{}\n\n schema \"communities_categories\" do\n belongs_to(:community, Community, foreign_key: :community_id)\n belongs_to(:category, Category, foreign_key: :category_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @required_fields ~w(community_id category_id)a\n\n @doc false\n def changeset(%CommunityCategory{} = community_category, attrs) do\n community_category\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:community_id)\n |> foreign_key_constraint(:category_id)\n |> unique_constraint(\n :community_id,\n name: :communities_categories_community_id_category_id_index\n )\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,0,null,0,null,null,168,null],"name":"lib/groupher_server_web/middleware/general_error.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.GeneralError do\n @behaviour Absinthe.Middleware\n\n def call(%{errors: [List = errors]} = resolution, _) do\n message = [%{message: errors}]\n\n %{resolution | value: [], errors: message}\n end\n\n def call(resolution, _), do: resolution\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,11,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/cms/post_star.ex","source":"defmodule GroupherServer.CMS.PostStar do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Post\n\n @required_fields ~w(user_id post_id)a\n\n @type t :: %PostStar{}\n schema \"posts_stars\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:post, Post, foreign_key: :post_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostStar{} = post_star, attrs) do\n # |> unique_constraint(:user_id, name: :favorites_user_id_article_id_index)\n post_star\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:user_id, name: :posts_stars_user_id_post_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,197,197,196,196,null,196,196,null,null,null,null,196,null,196,null,null,null,153,43,null,null,null,null,null,5,5,5,null,5,5,null,null,null,null,null,null,null,null,null,null,null,null,11,11,null,11,null,null,null,11,null,null,null,null,3,3,3,null,3,null,3,null,null,null,null,9,9,9,null,9,null,null,null,null,null,null,9,9,null,null,null,null,7,null,2,null,null,null,7,7,null,7,7,null,7,null,null,null,null,null,2,2,null,2,null,2,null,null,null,null,null,196,null,null,null,196,null,null,null,null,9,null,null,null,9,null,null,162,45,null,7,2,null],"name":"lib/groupher_server/cms/delegates/comment_curd.ex","source":"defmodule GroupherServer.CMS.Delegate.CommentCURD do\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1]\n import GroupherServer.CMS.Helper.Matcher\n import ShortMaps\n\n alias GroupherServer.{Repo, Accounts}\n alias Helper.{ORM, QueryBuilder}\n alias GroupherServer.CMS.{PostCommentReply, JobCommentReply}\n\n @doc \"\"\"\n Creates a comment for psot, job ...\n \"\"\"\n def create_comment(thread, content_id, body, %Accounts.User{id: user_id}) do\n with {:ok, action} <- match_action(thread, :comment),\n {:ok, content} <- ORM.find(action.target, content_id),\n {:ok, user} <- ORM.find(Accounts.User, user_id) do\n next_floor = get_next_floor(thread, action.reactor, content.id)\n\n attrs = %{\n author_id: user.id,\n body: body,\n floor: next_floor\n }\n\n attrs = merge_comment_attrs(thread, attrs, content.id)\n\n action.reactor |> ORM.create(attrs)\n end\n end\n\n defp merge_comment_attrs(:post, attrs, id), do: attrs |> Map.merge(%{post_id: id})\n defp merge_comment_attrs(:job, attrs, id), do: attrs |> Map.merge(%{job_id: id})\n\n @doc \"\"\"\n Delete the comment and increase all the floor after this comment\n \"\"\"\n def delete_comment(thread, content_id) do\n with {:ok, action} <- match_action(thread, :comment),\n {:ok, comment} <- ORM.find(action.reactor, content_id) do\n case ORM.delete(comment) do\n {:ok, comment} ->\n Repo.update_all(\n from(p in action.reactor, where: p.id > ^comment.id),\n inc: [floor: -1]\n )\n\n {:ok, comment}\n\n {:error, error} ->\n {:error, error}\n end\n end\n end\n\n def paged_comments(thread, content_id, %{page: page, size: size} = filters) do\n with {:ok, action} <- match_action(thread, :comment) do\n dynamic = dynamic_comment_where(thread, content_id)\n\n action.reactor\n |> where(^dynamic)\n |> QueryBuilder.filter_pack(filters)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n end\n\n def paged_replies(thread, comment_id, %Accounts.User{id: user_id}) do\n with {:ok, action} <- match_action(thread, :comment) do\n action.reactor\n |> where([c], c.author_id == ^user_id)\n |> join(:inner, [c], r in assoc(c, :reply_to))\n |> where([c, r], r.id == ^comment_id)\n |> Repo.all()\n |> done()\n end\n end\n\n def reply_comment(thread, comment_id, body, %Accounts.User{id: user_id}) do\n with {:ok, action} <- match_action(thread, :comment),\n {:ok, comment} <- ORM.find(action.reactor, comment_id) do\n next_floor = get_next_floor(thread, action.reactor, comment)\n\n attrs = %{\n author_id: user_id,\n body: body,\n reply_to: comment,\n floor: next_floor\n }\n\n attrs = merge_reply_attrs(thread, attrs, comment)\n brige_reply(thread, action.reactor, comment, attrs)\n end\n end\n\n defp merge_reply_attrs(:post, attrs, comment),\n do: attrs |> Map.merge(%{post_id: comment.post_id})\n\n defp merge_reply_attrs(:job, attrs, comment), do: attrs |> Map.merge(%{job_id: comment.job_id})\n\n defp brige_reply(:post, queryable, comment, attrs) do\n # TODO: use Multi task to refactor\n with {:ok, reply} <- ORM.create(queryable, attrs) do\n ORM.update(reply, %{reply_id: comment.id})\n\n {:ok, _} =\n PostCommentReply |> ORM.create(%{post_comment_id: comment.id, reply_id: reply.id})\n\n queryable |> ORM.find(reply.id)\n end\n end\n\n defp brige_reply(:job, queryable, comment, attrs) do\n # TODO: use Multi task to refactor\n with {:ok, reply} <- ORM.create(queryable, attrs) do\n ORM.update(reply, %{reply_id: comment.id})\n\n {:ok, _} = JobCommentReply |> ORM.create(%{job_comment_id: comment.id, reply_id: reply.id})\n\n queryable |> ORM.find(reply.id)\n end\n end\n\n # for create comment\n defp get_next_floor(thread, queryable, id) when is_integer(id) do\n dynamic = dynamic_comment_where(thread, id)\n\n queryable\n |> where(^dynamic)\n |> ORM.next_count()\n end\n\n # for reply comment\n defp get_next_floor(thread, queryable, comment) do\n dynamic = dynamic_reply_where(thread, comment)\n\n queryable\n |> where(^dynamic)\n |> ORM.next_count()\n end\n\n defp dynamic_comment_where(:post, id), do: dynamic([c], c.post_id == ^id)\n defp dynamic_comment_where(:job, id), do: dynamic([c], c.job_id == ^id)\n\n defp dynamic_reply_where(:post, comment), do: dynamic([c], c.post_id == ^comment.post_id)\n defp dynamic_reply_where(:job, comment), do: dynamic([c], c.job_id == ^comment.job_id)\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,2,2,null,null,null,null,2,null,null,null,62,null,null,62,62,62,62,62,null,null,62,null,null,null,null,null,null,29,null,null,null,26,null,null],"name":"lib/groupher_server/delivery/delegates/notifications.ex","source":"defmodule GroupherServer.Delivery.Delegate.Notifications do\n @moduledoc \"\"\"\n The Delivery context.\n \"\"\"\n import Helper.Utils, only: [done: 2]\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.Delivery.{Notification, SysNotification}\n alias Helper.ORM\n\n alias GroupherServer.Delivery.Delegate.Utils\n\n # TODO: audience\n def publish_system_notification(info) do\n attrs = %{\n source_id: info.source_id,\n source_title: info.source_title,\n source_type: info |> Map.get(:source_type, \"\"),\n source_preview: info |> Map.get(:source_preview, \"\")\n }\n\n SysNotification |> ORM.create(attrs) |> done(:status)\n end\n\n def notify_someone(%User{id: from_user_id}, %User{id: to_user_id}, info) do\n attrs = %{\n from_user_id: from_user_id,\n to_user_id: to_user_id,\n action: info.action,\n source_id: info.source_id,\n source_title: info.source_title,\n source_type: info.source_type,\n source_preview: info.source_preview\n }\n\n Notification |> ORM.create(attrs)\n end\n\n @doc \"\"\"\n fetch notifications from Delivery\n \"\"\"\n def fetch_notifications(%User{} = user, %{page: _, size: _, read: _} = filter) do\n Utils.fetch_messages(user, Notification, filter)\n end\n\n def fetch_sys_notifications(%User{} = user, %{page: _, size: _} = filter) do\n Utils.fetch_messages(:sys_notification, user, filter)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,1722,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,34,null,null,null,null,null],"name":"lib/groupher_server/cms/post.ex","source":"defmodule GroupherServer.CMS.Post do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, PostComment, PostFavorite, PostStar, Tag}\n\n @required_fields ~w(title body digest length)a\n @optional_fields ~w(link_addr pin markDelete)\n\n @type t :: %Post{}\n schema \"cms_posts\" do\n field(:body, :string)\n field(:title, :string)\n field(:digest, :string)\n field(:link_addr, :string)\n field(:length, :integer)\n field(:views, :integer, default: 0)\n\n field(:pin, :boolean, default_value: false)\n field(:markDelete, :boolean, default_value: false)\n belongs_to(:author, Author)\n\n # TODO\n # 相关文章\n # has_may(:related_post, ...)\n\n has_many(:comments, {\"posts_comments\", PostComment})\n has_many(:favorites, {\"posts_favorites\", PostFavorite})\n has_many(:stars, {\"posts_stars\", PostStar})\n # The keys are inflected from the schema names!\n # see https://hexdocs.pm/ecto/Ecto.Schema.html\n many_to_many(\n :tags,\n Tag,\n join_through: \"posts_tags\",\n join_keys: [post_id: :id, tag_id: :id],\n # :delete_all will only remove data from the join source\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_posts\",\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Post{} = post, attrs) do\n post\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n\n # |> foreign_key_constraint(:posts_tags, name: :posts_tags_tag_id_fkey)\n # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,0,null,null,null,null,null,null,0,0,null,0,null,null,0,null,null,0,0,null,null,null,null,0,null,null,null,0,0,null,0,0,null,0,null,null,null,null,null,null,null,null,null,null,null,null,0,0,null,null,null,null,0,0,0,null,null,null,null],"name":"lib/helper/oauth2/github.ex","source":"defmodule Helper.OAuth2.Github do\n use Tesla, only: [:get, :post]\n import Helper.Utils, only: [get_config: 2]\n\n # see Tesla intro: https://medium.com/@teamon/introducing-tesla-the-flexible-http-client-for-elixir-95b699656d88\n @timeout_limit 5000\n @client_id get_config(:github_oauth, :client_id)\n @client_secret get_config(:github_oauth, :client_secret)\n @redirect_uri \"http://www.coderplanets.com\"\n\n # wired only this style works\n plug(Tesla.Middleware.BaseUrl, \"https://github.com/login/oauth\")\n # plug(Tesla.Middleware.BaseUrl, \"https://www.github.com/login/oauth\")\n # plug(Tesla.Middleware.BaseUrl, \"https://api.github.com/login/oauth\")\n plug(Tesla.Middleware.Headers, %{\n \"User-Agent\" => \"groupher server\"\n # \"Accept\" => \"application/json\"\n # \"Accept\" => \"application/json;application/vnd.github.jean-grey-preview+json\"\n })\n\n plug(Tesla.Middleware.Retry, delay: 200, max_retries: 2)\n plug(Tesla.Middleware.Timeout, timeout: @timeout_limit)\n plug(Tesla.Middleware.JSON)\n plug(Tesla.Middleware.FormUrlencoded)\n\n def user_profile(code) do\n # body = \"client_id=#{@client_id}&client_secret=#{@client_secret}&code=#{code}&redirect_uri=#{@redirect_uri}\"\n # post(\"access_token?#{body}\",%{})\n headers = %{\"Accept\" => \"application/json\"}\n\n query = [\n code: code,\n client_id: @client_id,\n client_secret: @client_secret,\n redirect_uri: @redirect_uri\n ]\n\n try do\n case post(\"/access_token\", %{}, query: query, headers: headers) do\n %{status: 200, body: %{\"error\" => error, \"error_description\" => description}} ->\n {:error, \"#{error}: #{description}\"}\n\n %{status: 200, body: %{\"access_token\" => access_token, \"token_type\" => \"bearer\"}} ->\n user_info(access_token)\n end\n rescue\n e ->\n e |> handle_tesla_error\n end\n end\n\n def user_info(access_token) do\n url = \"https://api.github.com/user\"\n # this special header is too get node_id\n # see: https://developer.github.com/v3/\n\n headers = %{\"Accept\" => \"application/vnd.github.jean-grey-preview+json\"}\n query = [access_token: access_token]\n\n try do\n case get(url, query: query, headers: headers) do\n %{status: 200, body: body} ->\n body = body |> Map.merge(%{\"access_token\" => access_token})\n {:ok, body}\n\n %{status: 401, body: body} ->\n {:error, \"OAuth2 Github: \" <> body[\"message\"]}\n\n %{status: 403, body: body} ->\n {:error, \"OAuth2 Github: \" <> body}\n\n _ ->\n {:error, \"OAuth2 Github: unhandle error\"}\n end\n rescue\n e ->\n e |> handle_tesla_error\n end\n end\n\n defp handle_tesla_error(error) do\n case error do\n %{reason: :timeout} -> {:error, \"OAuth2 Github: timeout in #{@timeout_limit} msec\"}\n %{reason: reason} -> {:error, \"OAuth2 Github: #{reason}\"}\n _ -> {:error, \"unhandle error #{inspect(error)}\"}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/cms_misc.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Misc do\n use Absinthe.Schema.Notation\n\n import GroupherServerWeb.Schema.Helper.Fields\n\n alias GroupherServer.CMS\n\n @default_inner_page_size 5\n\n enum :comment_replies_type do\n value(:comment_replies_type)\n end\n\n enum :post_type do\n value(:post)\n end\n\n enum :community_type do\n value(:community)\n end\n\n enum :favorite_action do\n value(:favorite)\n end\n\n enum :count_type do\n value(:count)\n end\n\n enum :viewer_did_type do\n value(:viewer_did)\n end\n\n enum :star_action do\n value(:star)\n end\n\n enum :comment_action do\n value(:comment)\n end\n\n enum :unique_type do\n value(true)\n value(false)\n end\n\n enum :cms_action do\n value(:favorite)\n value(:star)\n value(:watch)\n end\n\n enum :thread do\n value(:post)\n value(:job)\n value(:video)\n value(:repo)\n value(:wiki)\n end\n\n enum :cms_comment do\n value(:post_comment)\n end\n\n enum :order_enum do\n value(:asc)\n value(:desc)\n end\n\n enum :when_enum do\n value(:today)\n value(:this_week)\n value(:this_month)\n value(:this_year)\n end\n\n enum :comment_sort_enum do\n value(:asc_inserted)\n value(:desc_inserted)\n value(:most_likes)\n value(:most_dislikes)\n end\n\n enum :thread_sort_enum do\n value(:asc_index)\n value(:desc_index)\n value(:asc_inserted)\n value(:desc_inserted)\n end\n\n enum :sort_enum do\n value(:most_views)\n value(:most_updated)\n value(:most_favorites)\n value(:most_stars)\n value(:most_watched)\n value(:most_comments)\n value(:least_views)\n value(:least_updated)\n value(:least_favorites)\n value(:least_stars)\n value(:least_watched)\n value(:least_comments)\n value(:recent_updated)\n end\n\n enum :rainbow_color_enum do\n value(:red)\n value(:orange)\n value(:yellow)\n value(:green)\n value(:cyan)\n value(:blue)\n value(:purple)\n end\n\n @desc \"inline members-like filter for dataloader usage\"\n input_object :members_filter do\n field(:first, :integer, default_value: @default_inner_page_size)\n end\n\n input_object :comments_filter do\n pagination_args()\n field(:sort, :comment_sort_enum, default_value: :asc_inserted)\n end\n\n input_object :communities_filter do\n @desc \"limit of records (default 20), if first > 30, only return 30 at most\"\n pagination_args()\n field(:sort, :sort_enum)\n field(:category, :string)\n end\n\n input_object :threads_filter do\n pagination_args()\n field(:sort, :thread_sort_enum)\n end\n\n input_object :paged_filter do\n @desc \"limit of records (default 20), if first > 30, only return 30 at most\"\n pagination_args()\n field(:sort, :sort_enum)\n end\n\n @desc \"article_filter doc\"\n input_object :article_filter do\n @desc \"limit of records (default 20), if first > 30, only return 30 at most\"\n field(:first, :integer)\n\n @desc \"Matching a tag\"\n field(:article_tag, :string, default_value: :all)\n # field(:sort, :sort_input)\n field(:when, :when_enum)\n field(:sort, :sort_enum)\n # @desc \"Matching a tag\"\n # @desc \"Added to the menu after this date\"\n # field(:added_after, :datetime)\n end\n\n @desc \"article_filter doc\"\n input_object :paged_article_filter do\n @desc \"limit of records (default 20), if first > 30, only return 30 at most\"\n pagination_args()\n\n field(:when, :when_enum)\n field(:sort, :sort_enum)\n field(:article_tag, :string, default_value: :all)\n field(:community, :string)\n\n # @desc \"Matching a name\"\n # field(:order, :order_enum, default_value: :desc)\n\n # @desc \"Matching a tag\"\n # field(:article_tag, :string, default_value: :all)\n end\n\n @doc \"\"\"\n only used for reaction result, like: favorite/star/watch ...\n \"\"\"\n interface :article do\n field(:id, :id)\n field(:title, :string)\n\n resolve_type(fn\n %CMS.Post{}, _ -> :post\n _, _ -> nil\n end)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,3,2,0,null,null,252,null,null,99,4,3,3,null,null,12,null,null,30,null,null,28,25,null,null,2,4,6,null,null,0,4,3,null,null,6,4,null],"name":"lib/groupher_server/accounts/accounts.ex","source":"defmodule GroupherServer.Accounts do\n @moduledoc false\n\n alias GroupherServer.Accounts.Delegate.{\n Achievements,\n Billing,\n Customization,\n Fans,\n Mails,\n Profile,\n ReactedContents\n }\n\n # profile\n defdelegate update_profile(user, attrs), to: Profile\n defdelegate github_signin(github_user), to: Profile\n defdelegate default_subscribed_communities(filter), to: Profile\n defdelegate subscribed_communities(user, filter), to: Profile\n\n # achievement\n defdelegate achieve(user, operation, key), to: Achievements\n\n # fans\n defdelegate follow(user, follower), to: Fans\n defdelegate undo_follow(user, follower), to: Fans\n defdelegate fetch_followers(user, filter), to: Fans\n defdelegate fetch_followings(user, filter), to: Fans\n\n # reacted contents\n defdelegate reacted_contents(thread, react, filter, user), to: ReactedContents\n\n # mentions\n defdelegate fetch_mentions(user, filter), to: Mails\n\n # notifications\n defdelegate fetch_notifications(user, filter), to: Mails\n defdelegate fetch_sys_notifications(user, filter), to: Mails\n\n # common message\n defdelegate mailbox_status(user), to: Mails\n defdelegate mark_mail_read_all(user, opt), to: Mails\n defdelegate mark_mail_read(mail, user), to: Mails\n\n # purchase\n defdelegate purchase_service(user, key, value), to: Billing\n defdelegate purchase_service(user, key), to: Billing\n defdelegate has_purchased?(user, key), to: Billing\n\n # customization\n defdelegate add_custom_setting(user, key, value), to: Customization\n defdelegate add_custom_setting(user, key), to: Customization\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,18,null,null,null,10,null,null,null,31,31,31,null,null,31,31,null,null,null,null,null,1,1,1,null,null,1,1,null,null,null,null,null,null,null,null,10,10,10,10,null,null,null,8,8,null,null,null,null,null,null,null,null,1,1,1,null,null,1,1,null,null,null,null,null,null,10,10,null,10,null,10,null,null,10,null,null,null],"name":"lib/groupher_server/cms/delegates/article_operation.ex","source":"defmodule GroupherServer.CMS.Delegate.ArticleCommunity do\n @moduledoc \"\"\"\n set / unset operations for Article-like resource\n \"\"\"\n import GroupherServer.CMS.Helper.Matcher\n import Ecto.Query, warn: false\n import Helper.ErrorCode\n\n alias Helper.ORM\n alias GroupherServer.Accounts.User\n alias GroupherServer.CMS.{Community, Tag}\n alias GroupherServer.Repo\n\n @doc \"\"\"\n pin / unpin, markDelete / untrash articles\n \"\"\"\n def set_flag(queryable, id, %{pin: _} = attrs, %User{} = _user) do\n queryable |> ORM.find_update(id, attrs)\n end\n\n def set_flag(queryable, id, %{markDelete: _} = attrs, %User{} = _user) do\n queryable |> ORM.find_update(id, attrs)\n end\n\n def mirror_article(%Community{id: community_id}, thread, content_id) when valid_thread(thread) do\n with {:ok, action} <- match_action(thread, :community),\n {:ok, content} <- ORM.find(action.target, content_id, preload: :communities),\n {:ok, community} <- ORM.find(action.reactor, community_id) do\n content\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.put_assoc(:communities, content.communities ++ [community])\n |> Repo.update()\n end\n end\n\n def unmirror_article(%Community{id: community_id}, thread, content_id)\n when valid_thread(thread) do\n with {:ok, action} <- match_action(thread, :community),\n {:ok, content} <- ORM.find(action.target, content_id, preload: :communities),\n {:ok, community} <- ORM.find(action.reactor, community_id) do\n content\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.put_assoc(:communities, content.communities -- [community])\n |> Repo.update()\n end\n end\n\n @doc \"\"\"\n set tag for post / tuts / videos ...\n \"\"\"\n # check community first\n def set_tag(%Community{id: communitId}, thread, %Tag{id: tag_id}, content_id) do\n with {:ok, action} <- match_action(thread, :article_tag),\n {:ok, content} <- ORM.find(action.target, content_id, preload: :tags),\n {:ok, tag} <- ORM.find(action.reactor, tag_id) do\n case tag_in_community_thread?(%Community{id: communitId}, thread, tag) do\n true ->\n content\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.put_assoc(:tags, content.tags ++ [tag])\n |> Repo.update()\n\n _ ->\n {:error, message: \"Tag,Community,Thread not match\", code: ecode(:custom)}\n end\n end\n end\n\n def unset_tag(thread, %Tag{id: tag_id}, content_id) when valid_thread(thread) do\n with {:ok, action} <- match_action(thread, :article_tag),\n {:ok, content} <- ORM.find(action.target, content_id, preload: :tags),\n {:ok, tag} <- ORM.find(action.reactor, tag_id) do\n content\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.put_assoc(:tags, content.tags -- [tag])\n |> Repo.update()\n end\n end\n\n # make sure the reuest tag is in the current community thread\n # example: you can't set a other thread tag to this thread's article\n defp tag_in_community_thread?(%Community{id: communityId}, thread, tag) do\n with {:ok, community} <- ORM.find(Community, communityId) do\n matched_tags =\n Tag\n |> where([t], t.community_id == ^community.id)\n # |> where([t], t.thread == ^(to_string(thread) |> String.upcase()))\n |> where([t], t.thread == ^to_string(thread))\n |> Repo.all()\n\n tag in matched_tags\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1,0,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/endpoint.ex","source":"defmodule GroupherServerWeb.Endpoint do\n use Phoenix.Endpoint, otp_app: :groupher_server\n\n socket(\"/socket\", GroupherServerWeb.UserSocket)\n\n plug(Plug.RequestId)\n plug(Plug.Logger)\n\n plug(\n Plug.Parsers,\n parsers: [:urlencoded, :multipart, :json],\n pass: [\"*/*\"],\n json_decoder: Jason\n )\n\n plug(Plug.MethodOverride)\n plug(Plug.Head)\n\n # plug(:inspect_conn)\n\n plug(\n Corsica,\n # log: [rejected: :error],\n log: [rejected: :debug],\n origins: \"*\",\n allow_headers: [\n \"authorization\",\n \"content-type\",\n \"special\",\n \"accept\",\n \"origin\",\n \"x-requested-with\"\n ],\n allow_credentials: true\n )\n\n plug(GroupherServerWeb.Router)\n\n @doc \"\"\"\n Callback invoked for dynamically configuring the endpoint.\n\n It receives the endpoint configuration and checks if\n configuration should be loaded from the system environment.\n \"\"\"\n def init(_key, config) do\n if config[:load_from_system_env] do\n port = System.get_env(\"PORT\") || raise \"expected the PORT environment variable to be set\"\n {:ok, Keyword.put(config, :http, [:inet6, port: port])}\n else\n {:ok, config}\n end\n end\n\n # defp inspect_conn(conn, _), do: IO.inspect(conn)\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/cms/repo_builder.ex","source":"defmodule GroupherServer.CMS.RepoBuilder do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n @required_fields ~w(nickname avatar link)a\n @optional_fields ~w(bio)\n\n @type t :: %RepoBuilder{}\n schema \"cms_repo_users\" do\n field(:nickname, :string)\n field(:avatar, :string)\n field(:link, :string)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%RepoBuilder{} = repo_builder, attrs) do\n repo_builder\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/account/account_types.ex","source":"defmodule GroupherServerWeb.Schema.Account.Types do\n use Helper.GqlSchemaSuite\n\n import GroupherServerWeb.Schema.Helper.Fields\n import Absinthe.Resolution.Helpers\n\n alias GroupherServer.Accounts\n alias GroupherServerWeb.Schema\n\n import_types(Schema.Account.Misc)\n\n object :user do\n field(:id, :id)\n field(:nickname, :string)\n field(:avatar, :string)\n field(:bio, :string)\n field(:sex, :string)\n field(:email, :string)\n field(:location, :string)\n field(:education, :string)\n field(:company, :string)\n field(:qq, :string)\n field(:weibo, :string)\n field(:weichat, :string)\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n field(:from_github, :boolean)\n field(:github_profile, :github_profile, resolve: dataloader(Accounts, :github_profile))\n field(:achievement, :achievement, resolve: dataloader(Accounts, :achievement))\n\n field(:cms_passport_string, :string) do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.get_passport_string/3)\n end\n\n field(:cms_passport, :json) do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.get_passport/3)\n end\n\n field :subscribed_communities, list_of(:community) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(Accounts, :subscribed_communities))\n end\n\n field :subscribed_communities_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :subscribed_communities))\n middleware(M.ConvertToInt)\n end\n\n field :followers_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :followers))\n middleware(M.ConvertToInt)\n end\n\n field :followings_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :followings))\n middleware(M.ConvertToInt)\n end\n\n field :viewer_has_followed, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n middleware(M.PutCurrentUser)\n resolve(dataloader(Accounts, :followers))\n middleware(M.ViewerDidConvert)\n end\n\n field :favorited_posts, :paged_posts do\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.favorited_posts/3)\n end\n\n field :favorited_jobs, :paged_jobs do\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.favorited_jobs/3)\n end\n\n field :favorited_posts_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :favorited_posts))\n middleware(M.ConvertToInt)\n end\n\n field :favorited_jobs_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :favorited_jobs))\n middleware(M.ConvertToInt)\n end\n\n field :contributes, :contribute_map do\n resolve(&R.Statistics.list_contributes/3)\n end\n\n # TODO, for msg-bell UI\n # field :has_messges,\n # 1. has_mentions ?\n # 2. has_system_messages ?\n # 3. has_notifications ?\n # 4. has_watches ?\n\n field :mail_box, :mail_box_status do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.get_mail_box_status/3)\n end\n\n field :mentions, :paged_mentions do\n arg(:filter, :messages_filter)\n\n middleware(M.Authorize, :login)\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.fetch_mentions/3)\n end\n\n field :notifications, :paged_notifications do\n arg(:filter, :messages_filter)\n\n middleware(M.Authorize, :login)\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.fetch_notifications/3)\n end\n\n field :sys_notifications, :paged_sys_notifications do\n arg(:filter, :messages_filter)\n\n middleware(M.Authorize, :login)\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.fetch_sys_notifications/3)\n end\n end\n\n object :github_profile do\n field(:id, :id)\n field(:github_id, :string)\n # field(:user, :user, resolve: dataloader(Accounts, :user))\n field(:login, :string)\n field(:avatar_url, :string)\n field(:url, :string)\n field(:html_url, :string)\n field(:name, :string)\n field(:company, :string)\n field(:blog, :string)\n field(:location, :string)\n field(:email, :string)\n field(:bio, :string)\n field(:public_repos, :integer)\n field(:public_gists, :integer)\n end\n\n object :achievement do\n field(:reputation, :integer)\n field(:followers_count, :integer)\n field(:contents_stared_count, :integer)\n field(:contents_favorited_count, :integer)\n field(:contents_watched_count, :integer)\n end\n\n object :token_info do\n field(:token, :string)\n field(:user, :user)\n end\n\n object :rules do\n field(:cms, :json)\n end\n\n object :paged_users do\n field(:entries, list_of(:user))\n pagination_fields()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,39,null,39,39,null,null,null,null,null,152,null,152,152,null,null,null,null,null,10,null,10,10,null,null,null,null,null,26,26,26,null,null,null,12,12,12,null,null,null,2,null,null,null,null,null,null,null,null,null,null,null,98,null,null,null,null,null,null,null,null,null,null,9,null,null,null,null,null,null,87,null,null,null,null,null,null,87,null,87,null,null,null,null,null,null,null,null,null,null,null,null,151,null,null,null,null,null,null,null,0,null,null,null,null,null,null,3,null,null,null,null,null,null,13,null,null,null,null,13,null,13,null,null,null,null,null,null,null,6,6,null,null,null,null],"name":"test/support/assert_helper.ex","source":"defmodule GroupherServer.Test.AssertHelper do\n @moduledoc \"\"\"\n This module defines some helper function used by\n tests that require check from graphql response\n \"\"\"\n\n import Phoenix.ConnTest\n import Helper.Utils, only: [map_key_stringify: 1, get_config: 2]\n\n @endpoint GroupherServerWeb.Endpoint\n\n @page_size get_config(:general, :page_size)\n @inner_page_size get_config(:general, :inner_page_size)\n\n @doc \"\"\"\n used for non exsit id\n \"\"\"\n def non_exsit_id, do: 15_982_398_614\n def inner_page_size, do: @inner_page_size\n def page_size, do: @page_size\n\n def is_valid_kv?(obj, key, :list) when is_map(obj) do\n obj = map_key_stringify(obj)\n\n case Map.has_key?(obj, key) do\n true -> obj |> Map.get(key) |> is_list\n _ -> false\n end\n end\n\n def is_valid_kv?(obj, key, :int) when is_map(obj) do\n obj = map_key_stringify(obj)\n\n case Map.has_key?(obj, key) do\n true -> obj |> Map.get(key) |> is_integer\n _ -> false\n end\n end\n\n def is_valid_kv?(obj, key, :string) when is_map(obj) and is_binary(key) do\n obj = map_key_stringify(obj)\n\n case Map.has_key?(obj, key) do\n true -> String.length(Map.get(obj, key)) != 0\n _ -> false\n end\n end\n\n def is_valid_pagination?(obj) when is_map(obj) do\n is_valid_kv?(obj, \"entries\", :list) and is_valid_kv?(obj, \"totalPages\", :int) and\n is_valid_kv?(obj, \"totalCount\", :int) and is_valid_kv?(obj, \"pageSize\", :int) and\n is_valid_kv?(obj, \"pageNumber\", :int)\n end\n\n def is_valid_pagination?(obj, :raw) when is_map(obj) do\n is_valid_kv?(obj, \"entries\", :list) and is_valid_kv?(obj, \"total_pages\", :int) and\n is_valid_kv?(obj, \"total_count\", :int) and is_valid_kv?(obj, \"page_size\", :int) and\n is_valid_kv?(obj, \"page_number\", :int)\n end\n\n def has_boolen_value?(obj, key) do\n obj |> Map.get(key) |> is_boolean\n end\n\n @doc \"\"\"\n simulate the Graphiql murate operation\n \"\"\"\n def mutation_result(conn, query, variables, key) do\n conn\n |> post(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n # |> IO.inspect(label: \"debug\")\n |> Map.get(\"data\")\n |> Map.get(key)\n end\n\n @doc \"\"\"\n check if Graphiql murate get error\n \"\"\"\n def mutation_get_error?(conn, query, variables) do\n conn\n |> post(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n # |> IO.inspect(label: \"debug\")\n |> Map.has_key?(\"errors\")\n end\n\n @doc \"\"\"\n Graphiql murate error with code equal check\n \"\"\"\n def mutation_get_error?(conn, query, variables, code) when is_integer(code) do\n resp =\n conn\n |> post(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n\n # |> IO.inspect(label: \"debug\")\n\n case resp |> Map.has_key?(\"errors\") do\n true ->\n code == resp[\"errors\"] |> List.first() |> Map.get(\"code\")\n\n false ->\n false\n end\n end\n\n def query_result(conn, query, variables, key) do\n conn\n |> get(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n # |> IO.inspect(label: \"debug\")\n |> Map.get(\"data\")\n |> Map.get(key)\n end\n\n def query_result(conn, query, key) do\n conn\n |> get(\"/graphiql\", query: query, variables: %{})\n |> json_response(200)\n |> Map.get(\"data\")\n |> Map.get(key)\n end\n\n def query_get_error?(conn, query, variables) do\n conn\n |> get(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n |> Map.has_key?(\"errors\")\n end\n\n @doc \"\"\"\n check if Graphiql murate get error\n \"\"\"\n def query_get_error?(conn, query, variables, code) when is_integer(code) do\n resp =\n conn\n |> get(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n\n case resp |> Map.has_key?(\"errors\") do\n true ->\n code == resp[\"errors\"] |> List.first() |> Map.get(\"code\")\n\n false ->\n false\n end\n end\n\n def firstn_and_last(values, 3) do\n [value_1 | [value_2 | [value_3 | _]]] = values\n value_x = values |> List.last()\n\n [value_1, value_2, value_3, value_x]\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/utils/helper.ex","source":"defmodule GroupherServerWeb.Schema.Helper.Fields do\n import Helper.Utils, only: [get_config: 2]\n @page_size get_config(:general, :page_size)\n # @default_inner_page_size 5\n\n # see: https://github.com/absinthe-graphql/absinthe/issues/363\n defmacro pagination_args() do\n quote do\n field(:page, :integer, default_value: 1)\n field(:size, :integer, default_value: unquote(@page_size))\n end\n end\n\n defmacro pagination_fields() do\n quote do\n field(:total_count, :integer)\n field(:page_size, :integer)\n field(:total_pages, :integer)\n field(:page_number, :integer)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,14,1,null,null,24,null,null,null,1,null,null,null,0,null,null,null,5,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,2,null,null,null,2,null,null,null,2,null,null,null,2,null,null,null,null,2,null,null,null,null,20,null,null,null,1,null,null,null,1,null,null,null,null,20,null,null,null,20,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,0,null,null,null,null,1,null,null,null,null,0,null,null,null,1,null,null,null,2,null,null,null,2,null,null,null,null,null,null,null,null,null,1,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/resolvers/accounts_resolver.ex","source":"defmodule GroupherServerWeb.Resolvers.Accounts do\n @moduledoc \"\"\"\n accounts resolvers\n \"\"\"\n import ShortMaps\n\n alias Helper.{Certification, ORM}\n alias GroupherServer.{Accounts, CMS}\n\n alias Accounts.{MentionMail, NotificationMail, SysNotificationMail, User}\n\n def user(_root, %{id: id}, _info), do: User |> ORM.find(id)\n def users(_root, ~m(filter)a, _info), do: User |> ORM.find_all(filter)\n\n def account(_root, _args, %{context: %{cur_user: cur_user}}) do\n User |> ORM.find(cur_user.id)\n end\n\n def update_profile(_root, %{profile: profile}, %{context: %{cur_user: cur_user}}) do\n Accounts.update_profile(%User{id: cur_user.id}, profile)\n end\n\n def github_signin(_root, %{github_user: github_user}, _info) do\n Accounts.github_signin(github_user)\n end\n\n def follow(_root, ~m(user_id)a, %{context: %{cur_user: cur_user}}) do\n Accounts.follow(cur_user, %User{id: user_id})\n end\n\n def undo_follow(_root, ~m(user_id)a, %{context: %{cur_user: cur_user}}) do\n Accounts.undo_follow(cur_user, %User{id: user_id})\n end\n\n def paged_followers(_root, ~m(user_id filter)a, _info) do\n Accounts.fetch_followers(%User{id: user_id}, filter)\n end\n\n def paged_followers(_root, ~m(filter)a, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_followers(cur_user, filter)\n end\n\n def paged_followings(_root, ~m(user_id filter)a, _info) do\n Accounts.fetch_followings(%User{id: user_id}, filter)\n end\n\n def paged_followings(_root, ~m(filter)a, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_followings(cur_user, filter)\n end\n\n # for check other users query\n def favorited_posts(_root, ~m(user_id filter)a, _info) do\n Accounts.reacted_contents(:post, :favorite, filter, %User{id: user_id})\n end\n\n def favorited_posts(_root, ~m(filter)a, %{context: %{cur_user: cur_user}}) do\n Accounts.reacted_contents(:post, :favorite, filter, cur_user)\n end\n\n def favorited_jobs(_root, ~m(user_id filter)a, _info) do\n Accounts.reacted_contents(:job, :favorite, filter, %User{id: user_id})\n end\n\n def favorited_jobs(_root, ~m(filter)a, %{context: %{cur_user: cur_user}}) do\n Accounts.reacted_contents(:job, :favorite, filter, cur_user)\n end\n\n # TODO: refactor\n def get_mail_box_status(_root, _args, %{context: %{cur_user: cur_user}}) do\n Accounts.mailbox_status(cur_user)\n end\n\n # mentions\n def fetch_mentions(_root, %{filter: filter}, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_mentions(cur_user, filter)\n end\n\n def mark_mention_read(_root, %{id: id}, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read(%MentionMail{id: id}, cur_user)\n end\n\n def mark_mention_read_all(_root, _args, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read_all(cur_user, :mention)\n end\n\n # notification\n def fetch_notifications(_root, %{filter: filter}, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_notifications(cur_user, filter)\n end\n\n def fetch_sys_notifications(_root, %{filter: filter}, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_sys_notifications(cur_user, filter)\n end\n\n def mark_notification_read(_root, %{id: id}, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read(%NotificationMail{id: id}, cur_user)\n end\n\n def mark_notification_read_all(_root, _args, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read_all(cur_user, :notification)\n end\n\n def mark_sys_notification_read(_root, %{id: id}, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read(%SysNotificationMail{id: id}, cur_user)\n end\n\n # for user self's\n def subscribed_communities(_root, %{filter: filter}, %{cur_user: cur_user}) do\n Accounts.subscribed_communities(%User{id: cur_user.id}, filter)\n end\n\n #\n def subscribed_communities(_root, %{user_id: \"\", filter: filter}, _info) do\n Accounts.default_subscribed_communities(filter)\n end\n\n # for check other users subscribed_communities\n def subscribed_communities(_root, %{user_id: user_id, filter: filter}, _info) do\n Accounts.subscribed_communities(%User{id: user_id}, filter)\n end\n\n def subscribed_communities(_root, %{filter: filter}, _info) do\n Accounts.default_subscribed_communities(filter)\n end\n\n def get_passport(root, _args, %{context: %{cur_user: _}}) do\n CMS.get_passport(%User{id: root.id})\n end\n\n def get_passport_string(root, _args, %{context: %{cur_user: _}}) do\n case CMS.get_passport(%User{id: root.id}) do\n {:ok, passport} ->\n {:ok, Jason.encode!(passport)}\n\n {:error, _} ->\n {:ok, nil}\n end\n end\n\n def get_all_rules(_root, _args, %{context: %{cur_user: _}}) do\n cms_rules = Certification.all_rules(:cms, :stringify)\n\n {:ok,\n %{\n cms: cms_rules\n }}\n end\n\n # def create_user(_root, args, %{context: %{cur_user: %{root: true}}}) do\n # Accounts.create_user2(args)\n # end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/logs/logs.ex","source":"defmodule GroupherServer.Logs do\n @moduledoc \"\"\"\n The Logs context.\n \"\"\"\n\n # import Ecto.Query, warn: false\n # alias GroupherServer.Repo\n\n # alias GroupherServer.Logs.UserActivity\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/statistics/statistics_types.ex","source":"defmodule GroupherServerWeb.Schema.Statistics.Types do\n use Absinthe.Schema.Notation\n use Absinthe.Ecto, repo: GroupherServer.Repo\n\n # import Absinthe.Resolution.Helpers\n\n # alias GroupherServer.Accounts\n\n object :user_contribute do\n field(:count, :integer)\n field(:date, :date)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,5,5,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,null,1,null,1,null,null,null,2,null,null,null,null,null,null,null,2,2,null,null,null,null,null,null,null,0,null,0,null,null,0,null,null,null,null,null,2,null,null,2,null,null,2,null,null,2,null,null,null,null,null,null,null,null,3,null,null,null,null,null,2,null,null,null,null,null,null,null,null,null,2,null,null,null,null,2,null,2,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/delegates/profile.ex","source":"defmodule GroupherServer.Accounts.Delegate.Profile do\n @moduledoc \"\"\"\n accounts profile\n \"\"\"\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1, get_config: 2]\n import ShortMaps\n\n alias Helper.{Guardian, ORM, QueryBuilder}\n alias GroupherServer.Accounts.{GithubUser, User}\n alias GroupherServer.{CMS, Repo}\n\n alias Ecto.Multi\n\n @default_subscribed_communities get_config(:general, :default_subscribed_communities)\n\n def update_profile(%User{id: id}, attrs \\\\ %{}) do\n with {:ok, user} <- ORM.find(User, id) do\n case user.id === id do\n true -> user |> ORM.update(attrs)\n false -> {:error, \"Error: not qualified\"}\n end\n end\n end\n\n @doc \"\"\"\n github_signin steps:\n ------------------\n step 0: get access_token is enough, even profile is not need?\n step 1: check is access_token valid or not, think use a Middleware\n step 2.1: if access_token's github_id exsit, then login\n step 2.2: if access_token's github_id not exsit, then signup\n step 3: return groupher token\n \"\"\"\n def github_signin(github_user) do\n case ORM.find_by(GithubUser, github_id: to_string(github_user[\"id\"])) do\n {:ok, g_user} ->\n {:ok, user} = ORM.find(User, g_user.user_id)\n # IO.inspect label: \"send back from db\"\n token_info(user)\n\n {:error, _} ->\n # IO.inspect label: \"register then send\"\n register_github_user(github_user)\n end\n end\n\n @doc \"\"\"\n get default subscribed communities for unlogin user\n \"\"\"\n def default_subscribed_communities(%{page: _, size: _} = filter) do\n filter = Map.merge(filter, %{size: @default_subscribed_communities})\n CMS.Community |> ORM.find_all(filter)\n end\n\n @doc \"\"\"\n get users subscribed communities\n \"\"\"\n def subscribed_communities(%User{id: id}, %{page: page, size: size} = filter) do\n CMS.CommunitySubscriber\n |> where([c], c.user_id == ^id)\n |> join(:inner, [c], cc in assoc(c, :community))\n |> select([c, cc], cc)\n |> QueryBuilder.filter_pack(filter)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n defp register_github_user(github_profile) do\n Multi.new()\n |> Multi.run(:create_user, fn _ ->\n create_user(github_profile, :github)\n end)\n |> Multi.run(:create_profile, fn %{create_user: user} ->\n create_profile(user, github_profile, :github)\n end)\n |> Repo.transaction()\n |> register_github_result()\n end\n\n defp register_github_result({:ok, %{create_user: user}}), do: token_info(user)\n\n defp register_github_result({:error, :create_user, _result, _steps}),\n do: {:error, \"Accounts create_user internal error\"}\n\n defp register_github_result({:error, :create_profile, _result, _steps}),\n do: {:error, \"Accounts create_profile internal error\"}\n\n defp token_info(%User{} = user) do\n with {:ok, token, _info} <- Guardian.jwt_encode(user) do\n {:ok, %{token: token, user: user}}\n end\n end\n\n defp create_user(user, :github) do\n user = %User{\n nickname: user[\"login\"],\n avatar: user[\"avatar_url\"],\n bio: user[\"bio\"],\n location: user[\"location\"],\n email: user[\"email\"],\n company: user[\"company\"],\n from_github: true\n }\n\n Repo.insert(user)\n end\n\n defp create_profile(user, github_profile, :github) do\n # attrs = github_user |> Map.merge(%{github_id: github_user.id, user_id: 1}) |> Map.delete(:id)\n attrs =\n github_profile\n |> Map.merge(%{\"github_id\" => to_string(github_profile[\"id\"]), \"user_id\" => user.id})\n # |> Map.merge(%{\"github_id\" => github_profile[\"id\"], \"user_id\" => user.id})\n |> Map.delete(\"id\")\n\n %GithubUser{}\n |> GithubUser.changeset(attrs)\n |> Repo.insert()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,null,null,190,null,null,null,null,3,3,null,3,null,0,0,0,null,null,null,3,3,3,null,null,null,null,null,null,3,null,0,null,null,null,3,null,0,null,null,null,null,null,null,3,null,null,null,null,null],"name":"lib/groupher_server_web/middleware/changeset_errors.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.ChangesetErrors do\n @behaviour Absinthe.Middleware\n import Helper.Utils, only: [handle_absinthe_error: 3]\n import Helper.ErrorCode\n\n alias GroupherServerWeb.Gettext, as: Translator\n\n def call(%{errors: [%Ecto.Changeset{} = changeset]} = resolution, _) do\n # IO.inspect changeset, label: \"Changeset error\"\n # IO.inspect transform_errors(changeset), label: \"transform_errors\"\n resolution\n |> handle_absinthe_error(transform_errors(changeset), ecode(:changeset))\n end\n\n def call(resolution, _), do: resolution\n\n defp transform_errors(changeset) do\n changeset\n |> Ecto.Changeset.traverse_errors(&format_error/1)\n |> Enum.map(fn {key, err_msg_list} ->\n err_msg = err_msg_list |> List.first()\n\n cond do\n Map.has_key?(err_msg, :count) ->\n %{\n key: Translator |> Gettext.dgettext(\"fields\", \"#{key}\"),\n message: Translator |> Gettext.dgettext(\"errors\", err_msg.raw, count: err_msg.count)\n }\n\n true ->\n %{\n key: Translator |> Gettext.dgettext(\"fields\", \"#{key}\"),\n message: Translator |> Gettext.dgettext(\"errors\", err_msg.msg)\n }\n end\n end)\n end\n\n defp format_error({msg, opts}) do\n err_string =\n Enum.reduce(opts, msg, fn {key, value}, acc ->\n String.replace(acc, \"%{#{key}}\", to_string(value))\n end)\n\n # TODO handle: number type\n cond do\n String.contains?(msg, \"%{count}\") ->\n %{\n msg: err_string,\n count: Keyword.get(opts, :count),\n raw: msg\n }\n\n true ->\n %{\n msg: err_string\n }\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/delivery/delivery_types.ex","source":"defmodule GroupherServerWeb.Schema.Delivery.Types do\n use Absinthe.Schema.Notation\n use Absinthe.Ecto, repo: GroupherServer.Repo\n\n import GroupherServerWeb.Schema.Helper.Fields\n import Helper.Utils, only: [get_config: 2]\n\n @page_size get_config(:general, :page_size)\n\n object :mail_box_status do\n field(:has_mail, :boolean)\n field(:total_count, :integer)\n field(:mention_count, :integer)\n field(:notification_count, :integer)\n end\n\n object :mention do\n field(:id, :id)\n field(:from_user_id, :id)\n field(:to_user_id, :id)\n\n field(:source_title, :string)\n field(:read, :boolean)\n end\n\n object :sys_notification do\n field(:id, :id)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_preview, :string)\n field(:source_type, :string)\n\n field(:read, :boolean)\n end\n\n object :notification do\n field(:id, :id)\n field(:from_user_id, :id)\n field(:to_user_id, :id)\n field(:action, :string)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_preview, :string)\n field(:source_type, :string)\n field(:read, :boolean)\n end\n\n object :sys_notification do\n field(:id, :id)\n field(:user_id, :id)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_preview, :string)\n field(:source_type, :string)\n field(:read, :boolean)\n end\n\n object :paged_mentions do\n field(:entries, list_of(:mention))\n pagination_fields()\n end\n\n object :paged_notifications do\n field(:entries, list_of(:notification))\n pagination_fields()\n end\n\n object :paged_sys_notifications do\n field(:entries, list_of(:sys_notification))\n pagination_fields()\n end\n\n input_object :messages_filter do\n field(:read, :boolean, default_value: false)\n\n field(:page, :integer, default_value: 1)\n field(:size, :integer, default_value: @page_size)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,26,null,3,null,null,4,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,7,null,null,null,null,null,null,null,null,null,20,null,null,null,null,null,null,null,null,null,8,null,null,null,null,null,null,37,null,null,null,null,21,null,null,null,null,72,null,18,null,54,7,null,47,10,null,37,37,null,null,null,0,null,null,null,null,37,37,null,37,null,24,null,null,null,13,null,null,null,null,7,7,null,7,null,null,null,null,7,null,6,null,null,null,1,null,null,null,null,18,18,null,18,null,18,null,null,null,null,null,18,null,16,null,null,null,2,null,null,null,null,10,10,null,10,null,null,15,15,null,null,null,10,null,7,null,null,null,3,null,null,null],"name":"lib/groupher_server_web/middleware/passport.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\n# RBAC vs CBAC\n# https://stackoverflow.com/questions/22814023/role-based-access-control-rbac-vs-claims-based-access-control-cbac-in-asp-n\n\n# 本中间件会隐式的加载 community 的 rules 信息,并应用该 rules 信息\ndefmodule GroupherServerWeb.Middleware.Passport do\n @moduledoc \"\"\"\n c? -> community / communities\n t? -> thread, could be post / job / tut / video ...\n \"\"\"\n @behaviour Absinthe.Middleware\n\n import Helper.Utils\n import Helper.ErrorCode\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(%{arguments: %{passport_is_owner: true}} = resolution, claim: \"owner\"), do: resolution\n\n def call(%{arguments: %{passport_is_owner: true}} = resolution, claim: \"owner;\" <> _rest),\n do: resolution\n\n def call(\n %{\n context: %{cur_user: %{cur_passport: _}},\n arguments: %{community: _, thread: _}\n } = resolution,\n claim: \"cms->c?->t?.\" <> _rest = claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(\n %{\n context: %{cur_user: %{cur_passport: _}},\n arguments: %{thread: _}\n } = resolution,\n claim: \"cms->t?.\" <> _rest = claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(\n %{\n context: %{cur_user: %{cur_passport: _}},\n arguments: %{passport_communities: _}\n } = resolution,\n claim: \"cms->c?->\" <> _rest = claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(\n %{\n context: %{cur_user: %{cur_passport: _}},\n arguments: %{passport_communities: _}\n } = resolution,\n claim: \"owner;\" <> claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(\n %{context: %{cur_user: %{cur_passport: _}}} = resolution,\n claim: \"cms->\" <> _rest = claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(resolution, _) do\n resolution\n |> handle_absinthe_error(\"PassportError: your passport not qualified.\", ecode(:passport))\n end\n\n defp check_passport_stamp(resolution, claim) do\n # TODO: refactor\n cond do\n claim |> String.starts_with?(\"cms->c?->t?.\") ->\n resolution |> cp_check(claim)\n\n claim |> String.starts_with?(\"cms->t?.\") ->\n resolution |> p_check(claim)\n\n claim |> String.starts_with?(\"cms->c?->\") ->\n resolution |> c_check(claim)\n\n claim |> String.starts_with?(\"cms->\") ->\n resolution |> do_check(claim)\n\n true ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\n\n defp do_check(resolution, claim) do\n cur_passport = resolution.context.cur_user.cur_passport\n path = claim |> String.split(\"->\")\n\n case get_in(cur_passport, path) do\n true ->\n resolution\n\n nil ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\n\n defp p_check(resolution, claim) do\n cur_passport = resolution.context.cur_user.cur_passport\n thread = resolution.arguments.thread |> to_string\n\n path =\n claim\n |> String.replace(\"t?\", thread)\n |> String.split(\"->\")\n\n case get_in(cur_passport, path) do\n true ->\n resolution\n\n nil ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\n\n defp cp_check(resolution, claim) do\n cur_passport = resolution.context.cur_user.cur_passport\n community_title = resolution.arguments.passport_communities |> List.first() |> Map.get(:title)\n\n thread = resolution.arguments.thread |> to_string\n\n path =\n claim\n |> String.replace(\"c?\", community_title)\n |> String.replace(\"t?\", thread)\n |> String.split(\"->\")\n\n case get_in(cur_passport, path) do\n true ->\n resolution\n\n nil ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\n\n defp c_check(resolution, claim) do\n cur_passport = resolution.context.cur_user.cur_passport\n communities = resolution.arguments.passport_communities\n\n result =\n communities\n |> Enum.filter(fn community ->\n path = claim |> String.replace(\"c?\", community.title) |> String.split(\"->\")\n get_in(cur_passport, path) == true\n end)\n |> length\n\n case result > 0 do\n true ->\n resolution\n\n false ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,148,null,null,null,null,null,null,null,null,null,null,null,73,null,null],"name":"lib/groupher_server/cms/post_favorite.ex","source":"defmodule GroupherServer.CMS.PostFavorite do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Post\n\n @required_fields ~w(user_id post_id)a\n\n @type t :: %PostFavorite{}\n schema \"posts_favorites\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:post, Post, foreign_key: :post_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostFavorite{} = post_favorite, attrs) do\n post_favorite\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:user_id, name: :posts_favorites_user_id_post_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,2806,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/cms/author.ex","source":"defmodule GroupherServer.CMS.Author do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Post\n\n @type t :: %Author{}\n\n schema \"cms_authors\" do\n field(:role, :string)\n # field(:user_id, :id)\n has_many(:posts, Post)\n # user_id filed in own-table\n belongs_to(:user, Accounts.User)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Author{} = author, attrs) do\n # |> foreign_key_constraint(:user_id)\n author\n |> cast(attrs, [:role])\n |> validate_required([:role])\n |> unique_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,104,null,null,null,null,null,null,null,null,null,null,null,null,null,97,null,null],"name":"lib/groupher_server/accounts/user_following.ex","source":"defmodule GroupherServer.Accounts.UserFollowing do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id following_id)a\n\n @type t :: %UserFollowing{}\n schema \"users_followings\" do\n belongs_to(:user, User, foreign_key: :user_id)\n belongs_to(:following, User, foreign_key: :following_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%UserFollowing{} = user_following, attrs) do\n user_following\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> foreign_key_constraint(:following_id)\n |> unique_constraint(:user_id, name: :users_followers_user_id_following_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,361,null,null,null,null,0,0,null,null,null,null,null,null,null,0,null,null,null,null,0,null,null,null,0,null,null,null,null,null,0,null,0,null,null,null,0,null,null,null,null,null,0,0,null,null,null,null,null,0,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,0,null,null,null,0,0,null,null,0,null,null,null,null,null,null,null,12,null,null,null,3,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,0,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,2,null,null,null,1,null,null,null,6,null,null,null,2,null,null,null,null,null,null,1,null,null,null,null,2,null,null,null,null,null,0,null,null,null,null,null,4,null,null,null,2,null,null,null,null,1,null,null,null,null,null,4,null,null,null,null,0,null,null,null,null,1,null,null,null,null,null,null,1,null,null,null,null,2,null,null,null,null,null,0,null,null,null,null,null,null,null,null,26,null,null],"name":"lib/groupher_server/cms/utils/loader.ex","source":"defmodule GroupherServer.CMS.Helper.Loader do\n @moduledoc \"\"\"\n dataloader for cms context\n \"\"\"\n import Ecto.Query, warn: false\n\n alias Helper.QueryBuilder\n alias GroupherServer.Repo\n # alias GroupherServer.Accounts\n alias GroupherServer.CMS.{\n Author,\n CommunityEditor,\n CommunitySubscriber,\n CommunityThread,\n JobCommentReply,\n Post,\n PostComment,\n PostCommentDislike,\n PostCommentLike,\n PostCommentReply,\n PostFavorite,\n PostStar\n # job comment\n # JobComment,\n }\n\n def data, do: Dataloader.Ecto.new(Repo, query: &query/2, run_batch: &run_batch/5)\n\n # Big thanks: https://elixirforum.com/t/grouping-error-in-absinthe-dadaloader/13671/2\n # see also: https://github.com/absinthe-graphql/dataloader/issues/25\n def run_batch(Post, post_query, :posts_count, community_ids, repo_opts) do\n query =\n from(\n p in post_query,\n join: c in assoc(p, :communities),\n where: c.id in ^community_ids,\n group_by: c.id,\n select: {c.id, [count(p.id)]}\n )\n\n results =\n query\n |> Repo.all(repo_opts)\n |> Map.new()\n\n for id <- community_ids, do: Map.get(results, id, [0])\n end\n\n def run_batch(PostComment, comment_query, :cp_count, post_ids, repo_opts) do\n results =\n comment_query\n |> join(:inner, [c], a in assoc(c, :author))\n # |> distinct([c, a], a.id)\n |> group_by([c, a], a.id)\n |> group_by([c, a], c.post_id)\n |> select([c, a], {c.post_id, count(a.id)})\n |> Repo.all(repo_opts)\n |> Enum.group_by(fn {x, _} -> x end)\n |> Enum.map(fn {x, y} -> {x, [length(y)]} end)\n |> Map.new()\n\n for id <- post_ids, do: Map.get(results, id, [0])\n end\n\n def run_batch(PostComment, comment_query, :cp_users, post_ids, repo_opts) do\n # IO.inspect(comment_query, label: \"# run_batch # comment_query\")\n\n sq =\n from(\n pc in comment_query,\n join: a in assoc(pc, :author),\n select: %{id: a.id, row_number: fragment(\"row_number() OVER (PARTITION BY author_id)\")}\n )\n\n query =\n from(\n pc in comment_query,\n join: s in subquery(sq),\n on: s.id == pc.author_id,\n where: s.row_number == 10,\n select: {pc.post_id, s.id}\n )\n\n # query = comment_query\n # |> join(:inner, [c], a in assoc(c, :author))\n # |> distinct([c, a], c.post_id)\n # |> join(:inner_lateral, [c, a], u in fragment(\"SELECT * FROM users AS us WHERE us.id = ? LIMIT 1\", a.id))\n # |> join(:inner_lateral, [c, a], u in fragment(\"SELECT * FROM users AS us WHERE us.id > ? LIMIT 1\", 100))\n # |> select([c, a, u], {c.post_id, u.id, u.nickname})\n\n results =\n query\n # |> IO.inspect(label: \"before\")\n |> Repo.all(repo_opts)\n # |> IO.inspect(label: \"geting fuck\")\n |> bat_man()\n\n # results =\n # comment_query\n # |> join(:inner, [c], a in assoc(c, :author))\n # |> group_by([c, a], a.id)\n # |> group_by([c, a], c.post_id)\n # |> select([c, a], {c.post_id, a})\n # ---------\n # |> join(:inner, [c], s in subquery(sq), on: s.id == c.post_id)\n # |> join(:inner, [c], a in subquery(isubquery), c.post_id == 106)\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT * FROM users AS u WHERE u.id = ? LIMIT 3\", c.post_id))\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT * FROM users WHERE users.id > ? LIMIT 3\", 100))\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT * FROM posts_comments JOIN users ON users.id = ? LIMIT 2\", c.author_id))\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT * FROM posts_comments AS pc WHERE pc.author_id = ? LIMIT 2\", 185))\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT ROW_NUMBER() OVER (PARTITION BY ?) FROM posts_comments AS pc GROUP BY pc.post_id\", c.post_id))\n # |> distinct([c, a], c.post_id)\n # |> join(:inner_lateral, [c, a], x in fragment(\"SELECT * FROM posts_comments JOIN users ON users.id = posts_comments.author_id WHERE post_id = ? LIMIT 2\", c.post_id))\n # |> join(:inner_lateral, [c, a], x in fragment(\"SELECT * FROM posts_comments JOIN users ON users.id = posts_comments.author_id LIMIT 3\"))\n # |> select([c,a,x], {c.post_id, x.author_id})\n # |> select([c,a,x], {c.post_id, a.id})\n # |> where([c, a], a.row_number < 3)\n # |> join(:inner, [c], a in assoc(c, :author))\n # |> join(:inner, [c], a in subquery(isubquery))\n # |> group_by([c, a, x], x.author_id)\n # |> distinct([c, a], a.author_id)\n # |> select([c, a], {c.post_id, a.author_id})\n # |> select([c, a], {c.post_id, fragment(\"max(?) OVER (PARTITION BY ?)\", a.id, a.id)})\n # |> select([c, a], %{post_id: c.post_id, user: fragment(\"max(?) OVER (PARTITION BY ?)\", a.id, a.id)})\n # |> select([c, a], fragment(\"SELECT ROW_NUMBER() OVER (PARTITION BY ?) FROM cms_authors AS r , \", a.id))\n # |> join([c], c in subquery(sq), on: c.post_id == bq.id)\n # |> having([c, a], count(\"*\") < 10)\n # |> having([c, a], a.id < 180)\n # |> limit(3)\n # |> order_by([p, s], desc: fragment(\"count(?)\", s.id))\n # |> distinct([c, a], a.id)\n # |> Repo.all(repo_opts)\n # |> IO.inspect(label: \"get fuck\")\n # |> bat_man()\n\n for id <- post_ids, do: Map.get(results, id, [])\n end\n\n # TODO: use meta-programing to extract all query below\n # --------------------\n def bat_man(data) do\n # TODO refactor later\n data\n |> Enum.group_by(fn {x, _} -> x end)\n |> Enum.map(fn {x, y} ->\n {x,\n Enum.reduce(y, [], fn kv, acc ->\n {_, v} = kv\n acc ++ [v]\n end)}\n end)\n |> Map.new()\n end\n\n def query(Author, _args) do\n # you cannot use preload with select together\n # https://stackoverflow.com/questions/43010352/ecto-select-relations-from-preload\n # see also\n # https://github.com/elixir-ecto/ecto/issues/1145\n from(a in Author, join: u in assoc(a, :user), select: u)\n end\n\n def query({\"communities_threads\", CommunityThread}, _info) do\n from(\n ct in CommunityThread,\n join: t in assoc(ct, :thread),\n order_by: [asc: t.index],\n select: t\n )\n end\n\n @doc \"\"\"\n get unique participators join in comments\n \"\"\"\n def query({\"posts_comments\", PostComment}, %{filter: filter, unique: true}) do\n # def query({\"posts_comments\", PostComment}, %{unique: true}) do\n PostComment\n # |> QueryBuilder.members_pack(args)\n |> QueryBuilder.filter_pack(filter)\n |> join(:inner, [c], a in assoc(c, :author))\n |> distinct([c, a], a.id)\n |> select([c, a], a)\n end\n\n def query({\"posts_comments\", PostComment}, %{count: _, unique: true}) do\n # TODO: not very familar with SQL, but it has to be 2 group_by to work, check later\n # and the expect count should be the length of reault\n PostComment\n |> join(:inner, [c], a in assoc(c, :author))\n |> distinct([c, a], a.id)\n |> group_by([c, a], a.id)\n |> group_by([c, a], c.post_id)\n |> select([c, a], count(c.id))\n end\n\n def query({\"posts_comments\", PostComment}, %{count: _}) do\n PostComment\n |> group_by([c], c.post_id)\n |> select([c], count(c.id))\n end\n\n # def query({\"posts_comments\", PostComment}, %{filter: %{first: first}} = filter) do\n def query({\"posts_comments\", PostComment}, %{filter: filter}) do\n PostComment\n # |> limit(3)\n |> QueryBuilder.filter_pack(filter)\n end\n\n @doc \"\"\"\n handle query:\n 1. bacic filter of pagi,when,sort ...\n 2. count of the reactions\n 3. check is viewer reacted\n \"\"\"\n def query({\"posts_favorites\", PostFavorite}, args) do\n PostFavorite |> QueryBuilder.members_pack(args)\n end\n\n def query({\"posts_stars\", PostStar}, args) do\n PostStar |> QueryBuilder.members_pack(args)\n end\n\n def query({\"communities_subscribers\", CommunitySubscriber}, args) do\n CommunitySubscriber |> QueryBuilder.members_pack(args)\n end\n\n def query({\"communities_editors\", CommunityEditor}, args) do\n CommunityEditor |> QueryBuilder.members_pack(args)\n end\n\n # for comments replies, likes, repliesCount, likesCount...\n def query({\"posts_comments_replies\", PostCommentReply}, %{count: _}) do\n PostCommentReply\n |> group_by([c], c.post_comment_id)\n |> select([c], count(c.id))\n end\n\n def query({\"posts_comments_replies\", PostCommentReply}, %{filter: filter}) do\n PostCommentReply\n |> QueryBuilder.load_inner_replies(filter)\n end\n\n def query({\"posts_comments_replies\", PostCommentReply}, %{reply_to: _}) do\n PostCommentReply\n |> join(:inner, [c], r in assoc(c, :post_comment))\n |> select([c, r], r)\n end\n\n def query({\"posts_comments_likes\", PostCommentLike}, %{count: _}) do\n PostCommentLike\n |> group_by([f], f.post_comment_id)\n |> select([f], count(f.id))\n end\n\n def query({\"posts_comments_likes\", PostCommentLike}, %{viewer_did: _, cur_user: cur_user}) do\n PostCommentLike |> where([f], f.user_id == ^cur_user.id)\n end\n\n def query({\"posts_comments_likes\", PostCommentLike}, %{filter: _filter} = args) do\n PostCommentLike\n |> QueryBuilder.members_pack(args)\n end\n\n def query({\"posts_comments_dislikes\", PostCommentDislike}, %{count: _}) do\n PostCommentDislike\n |> group_by([f], f.post_comment_id)\n |> select([f], count(f.id))\n end\n\n # component dislikes\n def query({\"posts_comments_dislikes\", PostCommentDislike}, %{viewer_did: _, cur_user: cur_user}) do\n PostCommentDislike |> where([f], f.user_id == ^cur_user.id)\n end\n\n def query({\"posts_comments_dislikes\", PostCommentDislike}, %{filter: _filter} = args) do\n PostCommentDislike\n |> QueryBuilder.members_pack(args)\n end\n\n # ---- job comments ------\n def query({\"jobs_comments_replies\", JobCommentReply}, %{count: _}) do\n JobCommentReply\n |> group_by([c], c.job_comment_id)\n |> select([c], count(c.id))\n end\n\n def query({\"jobs_comments_replies\", JobCommentReply}, %{filter: filter}) do\n JobCommentReply\n |> QueryBuilder.load_inner_replies(filter)\n end\n\n def query({\"jobs_comments_replies\", JobCommentReply}, %{reply_to: _}) do\n JobCommentReply\n |> join(:inner, [c], r in assoc(c, :job_comment))\n |> select([c, r], r)\n end\n\n # ---- job ------\n\n # default loader\n def query(queryable, _args) do\n # IO.inspect(queryable, label: \"default loader\")\n # IO.inspect(args, label: \"default args\")\n queryable\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,153,null,null,null,null,null,null,null,null,null,null,null,null,null,76,null,null],"name":"lib/groupher_server/cms/community_subscriber.ex","source":"defmodule GroupherServer.CMS.CommunitySubscriber do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Community\n\n @required_fields ~w(user_id community_id)a\n\n @type t :: %CommunitySubscriber{}\n schema \"communities_subscribers\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:community, Community, foreign_key: :community_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%CommunitySubscriber{} = community_subscriber, attrs) do\n community_subscriber\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:community_id)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user_id, name: :communities_subscribers_user_id_community_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,20,null,null,null,2,null,null,null,14,null,null,null,2,null,null,null,null,34,34,null,34,null,1,null,null,33,null,33,null,null,null,null,null,4,4,4,4,null,null,null],"name":"lib/groupher_server/cms/delegates/comment_reaction.ex","source":"defmodule GroupherServer.CMS.Delegate.CommentReaction do\n import GroupherServer.CMS.Helper.Matcher\n\n alias GroupherServer.Accounts\n alias Helper.ORM\n\n def like_comment(thread, comment_id, %Accounts.User{id: user_id}) do\n feel_comment(thread, comment_id, user_id, :like)\n end\n\n def undo_like_comment(thread, comment_id, %Accounts.User{id: user_id}) do\n undo_feel_comment(thread, comment_id, user_id, :like)\n end\n\n def dislike_comment(thread, comment_id, %Accounts.User{id: user_id}) do\n feel_comment(thread, comment_id, user_id, :dislike)\n end\n\n def undo_dislike_comment(thread, comment_id, %Accounts.User{id: user_id}) do\n undo_feel_comment(thread, comment_id, user_id, :dislike)\n end\n\n defp feel_comment(thread, comment_id, user_id, feeling)\n when valid_feeling(feeling) do\n with {:ok, action} <- match_action(thread, feeling) do\n clause = %{post_comment_id: comment_id, user_id: user_id}\n\n case ORM.find_by(action.reactor, clause) do\n {:ok, _} ->\n {:error, \"user has #{to_string(feeling)}d this comment\"}\n\n {:error, _} ->\n action.reactor |> ORM.create(clause)\n\n ORM.find(action.target, comment_id)\n end\n end\n end\n\n defp undo_feel_comment(thread, comment_id, user_id, feeling) do\n with {:ok, action} <- match_action(thread, feeling) do\n clause = %{post_comment_id: comment_id, user_id: user_id}\n ORM.findby_delete!(action.reactor, clause)\n ORM.find(action.target, comment_id)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/helper/spec_type.ex","source":"defmodule Helper.Types do\n @moduledoc \"\"\"\n custom @types\n \"\"\"\n\n @typedoc \"\"\"\n Type GraphQL flavor the error format\n \"\"\"\n @type gq_error :: {:error, [message: String.t(), code: non_neg_integer()]}\n\n @typedoc \"\"\"\n general response conventions\n \"\"\"\n @type done :: {:ok, map} | {:error, map}\n\n @type id :: non_neg_integer() | String.t()\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/statistics/statistics_queries.ex","source":"defmodule GroupherServerWeb.Schema.Statistics.Queries do\n @moduledoc \"\"\"\n Statistics.Queries\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :statistics_queries do\n @desc \"list of user contribute in last 6 month\"\n field :user_contributes, list_of(:user_contribute) do\n arg(:id, non_null(:id))\n\n resolve(&R.Statistics.list_contributes/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,5273,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,null,null,null,null,null],"name":"lib/groupher_server/cms/community.ex","source":"defmodule GroupherServer.CMS.Community do\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.CMS.{\n Category,\n Post,\n Video,\n Repo,\n Job,\n CommunityThread,\n CommunitySubscriber,\n CommunityEditor\n }\n\n alias GroupherServer.Accounts\n\n @required_fields ~w(title desc user_id logo raw)a\n # @required_fields ~w(title desc user_id)a\n @optional_fields ~w(label)a\n\n schema \"communities\" do\n field(:title, :string)\n field(:desc, :string)\n field(:logo, :string)\n # field(:category, :string)\n field(:label, :string)\n field(:raw, :string)\n\n belongs_to(:author, Accounts.User, foreign_key: :user_id)\n\n has_many(:threads, {\"communities_threads\", CommunityThread})\n has_many(:subscribers, {\"communities_subscribers\", CommunitySubscriber})\n has_many(:editors, {\"communities_editors\", CommunityEditor})\n\n many_to_many(\n :categories,\n Category,\n join_through: \"communities_categories\",\n join_keys: [community_id: :id, category_id: :id],\n # :delete_all will only remove data from the join source\n on_delete: :delete_all\n # on_replace: :delete\n )\n\n many_to_many(\n :posts,\n Post,\n join_through: \"communities_posts\",\n join_keys: [community_id: :id, post_id: :id]\n )\n\n many_to_many(\n :videos,\n Video,\n join_through: \"communities_videos\",\n join_keys: [community_id: :id, video_id: :id]\n )\n\n many_to_many(\n :repos,\n Repo,\n join_through: \"communities_repos\",\n join_keys: [community_id: :id, repo_id: :id]\n )\n\n many_to_many(\n :jobs,\n Job,\n join_through: \"communities_jobs\",\n join_keys: [community_id: :id, job_id: :id]\n )\n\n # posts_managers\n # jobs_managers\n # tuts_managers\n # videos_managers\n #\n # posts_block_list ...\n # videos_block_list ...\n timestamps(type: :utc_datetime)\n end\n\n def changeset(%Community{} = community, attrs) do\n # |> cast_assoc(:author)\n # |> unique_constraint(:title, name: :communities_title_index)\n community\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> validate_length(:title, min: 3, max: 30)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:title, name: :communities_title_index)\n\n # |> foreign_key_constraint(:communities_author_fkey)\n # |> unique_constraint(:user_id, name: :posts_favorites_user_id_post_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,1,0,null,null,4,null,null,3,3,null,null,2,null,2,null,null,null,null,9,3,3,2,null,16,14,14,9,null,null,12,null,null,null,4,null,5,null,null,1,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,null,null,0,null,null,null,0,null,null,null,0,null,null,null,null,null,2,null,null,1,null,null,1,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,null,3,null,null,1,null,null,1,null,null,null,1,null,null,null,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,null,null,3,null,null,1,null,1,null,null,10,null,null,null,1,null,null,1,null,null,null,1,null,null,null,null,null,null,1,null,null,null,null,null,2,null,null,null,2,null,null,null,1,null,null,null,5,null,null,null,1,null,null,null,null,null,null,10,null,null,8,null,null,null,3,null,null,null,2,null,null,null,2,null,null,null,1,null,null,null,2,null,null,null,1,null,null,null,3,null,null],"name":"lib/groupher_server_web/resolvers/cms_resolver.ex","source":"defmodule GroupherServerWeb.Resolvers.CMS do\n @moduledoc false\n\n import ShortMaps\n import Ecto.Query, warn: false\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.CMS\n alias GroupherServer.CMS.{Post, Video, Repo, Job, Community, Category, Tag, Thread}\n alias Helper.ORM\n\n # #######################\n # community ..\n # #######################\n def community(_root, %{id: id}, _info), do: Community |> ORM.find(id)\n def community(_root, %{title: title}, _info), do: Community |> ORM.find_by(title: title)\n def community(_root, %{raw: raw}, _info), do: Community |> ORM.find_by(raw: raw)\n\n def community(_root, _args, _info), do: {:error, \"please provide community id or title or raw\"}\n def paged_communities(_root, ~m(filter)a, _info), do: Community |> ORM.find_all(filter)\n\n def create_community(_root, args, %{context: %{cur_user: user}}) do\n args = args |> Map.merge(%{user_id: user.id})\n Community |> ORM.create(args)\n end\n\n def update_community(_root, args, _info), do: Community |> ORM.find_update(args)\n\n def delete_community(_root, %{id: id}, _info), do: Community |> ORM.find_delete!(id)\n\n # #######################\n # community thread (post, job)\n # #######################\n def post(_root, %{id: id}, _info), do: Post |> ORM.read(id, inc: :views)\n def video(_root, %{id: id}, _info), do: Video |> ORM.read(id, inc: :views)\n def repo(_root, %{id: id}, _info), do: Repo |> ORM.read(id, inc: :views)\n def job(_root, %{id: id}, _info), do: Job |> ORM.read(id, inc: :views)\n\n def paged_posts(_root, ~m(filter)a, _info), do: Post |> CMS.paged_contents(filter)\n def paged_videos(_root, ~m(filter)a, _info), do: Video |> CMS.paged_contents(filter)\n def paged_repos(_root, ~m(filter)a, _info), do: Repo |> CMS.paged_contents(filter)\n def paged_jobs(_root, ~m(filter)a, _info), do: Job |> ORM.find_all(filter)\n\n def create_article(_root, ~m(community_id thread)a = args, %{context: %{cur_user: user}}) do\n CMS.create_article(%Community{id: community_id}, thread, args, user)\n end\n\n def update_article(_root, %{passport_source: content} = args, _info),\n do: ORM.update(content, args)\n\n def delete_content(_root, %{passport_source: content}, _info), do: ORM.delete(content)\n\n def pin_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{pin: true}, user)\n end\n\n def undo_pin_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{pin: false}, user)\n end\n\n def trash_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{markDelete: true}, user)\n end\n\n def undo_trash_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{markDelete: false}, user)\n end\n\n # #######################\n # thread reaction ..\n # #######################\n def reaction(_root, ~m(id thread action)a, %{context: %{cur_user: user}}) do\n CMS.reaction(thread, action, id, user)\n end\n\n def undo_reaction(_root, ~m(id thread action)a, %{context: %{cur_user: user}}) do\n CMS.undo_reaction(thread, action, id, user)\n end\n\n def reaction_users(_root, ~m(id action thread filter)a, _info) do\n CMS.reaction_users(thread, action, id, filter)\n end\n\n # #######################\n # category ..\n # #######################\n def paged_categories(_root, ~m(filter)a, _info), do: Category |> ORM.find_all(filter)\n\n def create_category(_root, ~m(title raw)a, %{context: %{cur_user: user}}) do\n CMS.create_category(%Category{title: title, raw: raw}, user)\n end\n\n def delete_category(_root, %{id: id}, _info), do: Category |> ORM.find_delete!(id)\n\n def update_category(_root, ~m(id title)a, %{context: %{cur_user: _}}) do\n CMS.update_category(~m(%Category id title)a)\n end\n\n def set_category(_root, ~m(community_id category_id)a, %{context: %{cur_user: _}}) do\n CMS.set_category(%Community{id: community_id}, %Category{id: category_id})\n end\n\n def unset_category(_root, ~m(community_id category_id)a, %{context: %{cur_user: _}}) do\n CMS.unset_category(%Community{id: community_id}, %Category{id: category_id})\n end\n\n # #######################\n # thread ..\n # #######################\n def paged_threads(_root, ~m(filter)a, _info), do: Thread |> ORM.find_all(filter)\n\n def create_thread(_root, ~m(title raw index)a, _info),\n do: CMS.create_thread(~m(title raw index)a)\n\n def set_thread(_root, ~m(community_id thread_id)a, _info) do\n CMS.set_thread(%Community{id: community_id}, %Thread{id: thread_id})\n end\n\n def unset_thread(_root, ~m(community_id thread_id)a, _info) do\n CMS.unset_thread(%Community{id: community_id}, %Thread{id: thread_id})\n end\n\n # #######################\n # editors ..\n # #######################\n def set_editor(_root, ~m(community_id user_id title)a, _) do\n CMS.set_editor(%Community{id: community_id}, title, %User{id: user_id})\n end\n\n def unset_editor(_root, ~m(community_id user_id)a, _) do\n CMS.unset_editor(%Community{id: community_id}, %User{id: user_id})\n end\n\n def update_editor(_root, ~m(community_id user_id title)a, _) do\n CMS.update_editor(%Community{id: community_id}, title, %User{id: user_id})\n end\n\n def community_editors(_root, ~m(id filter)a, _info) do\n CMS.community_members(:editors, %Community{id: id}, filter)\n end\n\n # #######################\n # tags ..\n # #######################\n def create_tag(_root, args, %{context: %{cur_user: user}}) do\n CMS.create_tag(args.thread, args, user)\n end\n\n def delete_tag(_root, %{id: id}, _info), do: Tag |> ORM.find_delete!(id)\n\n def update_tag(_root, args, _info), do: CMS.update_tag(args)\n\n def set_tag(_root, ~m(community_id thread id tag_id)a, _info) do\n CMS.set_tag(%Community{id: community_id}, thread, %Tag{id: tag_id}, id)\n end\n\n def unset_tag(_root, ~m(id thread tag_id)a, _info),\n do: CMS.unset_tag(thread, %Tag{id: tag_id}, id)\n\n def get_tags(_root, ~m(community_id thread)a, _info) do\n CMS.get_tags(%Community{id: community_id}, thread)\n end\n\n def get_tags(_root, ~m(community thread)a, _info) do\n CMS.get_tags(%Community{raw: community}, thread)\n end\n\n def get_tags(_root, %{thread: _thread}, _info) do\n {:error, \"community_id or community is needed\"}\n end\n\n def get_tags(_root, ~m(filter)a, _info), do: CMS.get_tags(filter)\n\n # #######################\n # community subscribe ..\n # #######################\n def subscribe_community(_root, ~m(community_id)a, %{context: %{cur_user: cur_user}}) do\n CMS.subscribe_community(%Community{id: community_id}, cur_user)\n end\n\n def unsubscribe_community(_root, ~m(community_id)a, %{context: %{cur_user: cur_user}}) do\n CMS.unsubscribe_community(%Community{id: community_id}, cur_user)\n end\n\n def community_subscribers(_root, ~m(id filter)a, _info) do\n CMS.community_members(:subscribers, %Community{id: id}, filter)\n end\n\n def mirror_article(_root, ~m(thread id community_id)a, _info) do\n CMS.mirror_article(%Community{id: community_id}, thread, id)\n end\n\n def unmirror_article(_root, ~m(thread id community_id)a, _info) do\n CMS.unmirror_article(%Community{id: community_id}, thread, id)\n end\n\n # #######################\n # comemnts ..\n # #######################\n def paged_comments(_root, ~m(id thread filter)a, _info),\n do: CMS.paged_comments(thread, id, filter)\n\n def create_comment(_root, ~m(thread id body)a, %{context: %{cur_user: user}}) do\n CMS.create_comment(thread, id, body, user)\n end\n\n def delete_comment(_root, ~m(thread id)a, _info) do\n CMS.delete_comment(thread, id)\n end\n\n def reply_comment(_root, ~m(thread id body)a, %{context: %{cur_user: user}}) do\n CMS.reply_comment(thread, id, body, user)\n end\n\n def like_comment(_root, ~m(thread id)a, %{context: %{cur_user: user}}) do\n CMS.like_comment(thread, id, user)\n end\n\n def undo_like_comment(_root, ~m(thread id)a, %{context: %{cur_user: user}}) do\n CMS.undo_like_comment(thread, id, user)\n end\n\n def dislike_comment(_root, ~m(thread id)a, %{context: %{cur_user: user}}) do\n CMS.dislike_comment(thread, id, user)\n end\n\n def undo_dislike_comment(_root, ~m(thread id)a, %{context: %{cur_user: user}}) do\n CMS.undo_dislike_comment(thread, id, user)\n end\n\n def stamp_passport(_root, ~m(user_id rules)a, %{context: %{cur_user: _user}}) do\n CMS.stamp_passport(rules, %User{id: user_id})\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,0,0,null,null,null,null,null,null,null,0,0,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,0,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/cms_types.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Types do\n @moduledoc \"\"\"\n cms types used in queries & mutations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n import GroupherServerWeb.Schema.Helper.Fields\n import Ecto.Query, warn: false\n import Absinthe.Resolution.Helpers, only: [dataloader: 2, on_load: 2]\n\n alias GroupherServer.CMS\n alias GroupherServerWeb.Schema\n\n import_types(Schema.CMS.Misc)\n\n object :idlike do\n field(:id, :id)\n end\n\n object :comment do\n field(:id, :id)\n field(:body, :string)\n field(:floor, :integer)\n field(:author, :user, resolve: dataloader(CMS, :author))\n\n field :reply_to, :comment do\n resolve(dataloader(CMS, :reply_to))\n end\n\n field :likes, list_of(:user) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :likes))\n end\n\n field :likes_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(CMS, :likes))\n middleware(M.ConvertToInt)\n end\n\n field :viewer_has_liked, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n # put current user into dataloader's args\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :likes))\n middleware(M.ViewerDidConvert)\n end\n\n field :dislikes, list_of(:user) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :dislikes))\n end\n\n field :viewer_has_disliked, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n # put current user into dataloader's args\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :dislikes))\n middleware(M.ViewerDidConvert)\n end\n\n field :dislikes_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(CMS, :dislikes))\n middleware(M.ConvertToInt)\n end\n\n field :replies, list_of(:comment) do\n arg(:filter, :members_filter)\n\n middleware(M.ForceLoader)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :replies))\n end\n\n field :replies_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(CMS, :replies))\n middleware(M.ConvertToInt)\n end\n\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :post do\n interface(:article)\n field(:id, :id)\n field(:title, :string)\n field(:digest, :string)\n field(:length, :integer)\n field(:link_addr, :string)\n field(:body, :string)\n field(:views, :integer)\n field(:pin, :boolean)\n field(:markDelete, :boolean)\n field(:tags, list_of(:article_tag), resolve: dataloader(CMS, :tags))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n\n field :comments, list_of(:comment) do\n arg(:filter, :members_filter)\n\n # middleware(M.ForceLoader)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :comments))\n end\n\n field :comments_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(CMS, :comments))\n middleware(M.ConvertToInt)\n end\n\n field :comments_participators, list_of(:user) do\n arg(:filter, :members_filter)\n arg(:unique, :unique_type, default_value: true)\n\n middleware(M.ForceLoader)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :comments))\n end\n\n field :comments_participators2, list_of(:user) do\n arg(:filter, :members_filter)\n arg(:unique, :unique_type, default_value: true)\n\n middleware(M.PageSizeProof)\n\n resolve(fn post, _args, %{context: %{loader: loader}} ->\n loader\n |> Dataloader.load(CMS, {:many, CMS.PostComment}, cp_users: post.id)\n |> on_load(fn loader ->\n {:ok, Dataloader.get(loader, CMS, {:many, CMS.PostComment}, cp_users: post.id)}\n end)\n end)\n end\n\n field :comments_participators_count, :integer do\n resolve(fn post, _args, %{context: %{loader: loader}} ->\n loader\n |> Dataloader.load(CMS, {:one, CMS.PostComment}, cp_count: post.id)\n |> on_load(fn loader ->\n {:ok, Dataloader.get(loader, CMS, {:one, CMS.PostComment}, cp_count: post.id)}\n end)\n end)\n end\n\n field :comments_participators_count_wired, :integer do\n arg(:unique, :unique_type, default_value: true)\n arg(:count, :count_type, default_value: :count)\n\n # middleware(M.ForceLoader)\n resolve(dataloader(CMS, :comments))\n # middleware(M.CountLength)\n end\n\n field :viewer_has_favorited, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n # put current user into dataloader's args\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :favorites))\n middleware(M.ViewerDidConvert)\n end\n\n field :viewer_has_starred, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :stars))\n middleware(M.ViewerDidConvert)\n end\n\n field :favorited_users, list_of(:user) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :favorites))\n end\n\n field :favorited_count, :integer do\n arg(:count, :count_type, default_value: :count)\n arg(:type, :post_type, default_value: :post)\n # middleware(M.SeeMe)\n resolve(dataloader(CMS, :favorites))\n middleware(M.ConvertToInt)\n end\n\n field :starred_count, :integer do\n arg(:count, :count_type, default_value: :count)\n arg(:type, :post_type, default_value: :post)\n\n resolve(dataloader(CMS, :stars))\n middleware(M.ConvertToInt)\n end\n\n field :starred_users, list_of(:user) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :stars))\n end\n end\n\n object :video do\n interface(:article)\n field(:id, :id)\n field(:title, :string)\n field(:desc, :string)\n field(:duration, :string)\n field(:author, :user, resolve: dataloader(CMS, :author))\n\n field(:source, :string)\n field(:link, :string)\n field(:original_author, :string)\n field(:original_author_link, :string)\n field(:views, :integer)\n\n field(:pin, :boolean)\n field(:markDelete, :boolean)\n\n # field(:tags, list_of(:article_tag), resolve: dataloader(CMS, :tags))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :repo do\n # interface(:article)\n field(:id, :id)\n field(:repo_name, :string)\n field(:desc, :string)\n field(:readme, :string)\n field(:language, :string)\n field(:author, :user, resolve: dataloader(CMS, :author))\n\n field(:repo_link, :string)\n field(:producer, :string)\n field(:producer_link, :integer)\n\n field(:repo_star_count, :integer)\n field(:repo_fork_count, :integer)\n field(:repo_watch_count, :integer)\n field(:views, :integer)\n\n field(:pin, :boolean)\n field(:markDelete, :boolean)\n\n # field(:tags, list_of(:article_tag), resolve: dataloader(CMS, :tags))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :job do\n interface(:article)\n field(:id, :id)\n field(:title, :string)\n field(:desc, :string)\n field(:company, :string)\n field(:company_logo, :string)\n field(:digest, :string)\n field(:location, :string)\n field(:length, :integer)\n field(:link_addr, :string)\n field(:body, :string)\n field(:views, :integer)\n\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:tags, list_of(:article_tag), resolve: dataloader(CMS, :tags))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :thread do\n field(:id, :id)\n field(:title, :string)\n field(:raw, :string)\n field(:index, :integer)\n end\n\n object :contribute do\n field(:date, :date)\n field(:count, :integer)\n end\n\n object :contribute_map do\n field(:start_date, :date)\n field(:end_date, :date)\n field(:total_count, :integer)\n field(:records, list_of(:contribute))\n end\n\n object :community do\n # meta(:cache, max_age: 30)\n\n field(:id, :id)\n field(:title, :string)\n field(:desc, :string)\n field(:raw, :string)\n field(:logo, :string)\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:threads, list_of(:thread), resolve: dataloader(CMS, :threads))\n field(:categories, list_of(:category), resolve: dataloader(CMS, :categories))\n\n # Big thanks: https://elixirforum.com/t/grouping-error-in-absinthe-dadaloader/13671/2\n # see also: https://github.com/absinthe-graphql/dataloader/issues/25\n field :posts_count, :integer do\n resolve(fn community, _args, %{context: %{loader: loader}} ->\n loader\n |> Dataloader.load(CMS, {:one, CMS.Post}, posts_count: community.id)\n |> on_load(fn loader ->\n {:ok, Dataloader.get(loader, CMS, {:one, CMS.Post}, posts_count: community.id)}\n end)\n end)\n end\n\n field :subscribers, list_of(:user) do\n arg(:filter, :members_filter)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :subscribers))\n end\n\n field :subscribers_count, :integer do\n arg(:count, :count_type, default_value: :count)\n arg(:type, :community_type, default_value: :community)\n resolve(dataloader(CMS, :subscribers))\n middleware(M.ConvertToInt)\n end\n\n field :viewer_has_subscribed, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :subscribers))\n middleware(M.ViewerDidConvert)\n end\n\n field :editors, list_of(:user) do\n arg(:filter, :members_filter)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :editors))\n end\n\n field :editors_count, :integer do\n arg(:count, :count_type, default_value: :count)\n arg(:type, :community_type, default_value: :community)\n resolve(dataloader(CMS, :editors))\n middleware(M.ConvertToInt)\n end\n\n field :contributes, list_of(:contribute) do\n # TODO add complex here to warning N+1 problem\n resolve(&R.Statistics.list_contributes/3)\n end\n\n field :contributes_digest, list_of(:integer) do\n # TODO add complex here to warning N+1 problem\n resolve(&R.Statistics.list_contributes_digest/3)\n end\n end\n\n object :category do\n field(:id, :id)\n field(:title, :string)\n field(:raw, :string)\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :article_tag do\n field(:id, :id)\n field(:title, :string)\n field(:color, :string)\n field(:thread, :string)\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:community, :community, resolve: dataloader(CMS, :community))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :paged_categories do\n field(:entries, list_of(:category))\n pagination_fields()\n end\n\n object :paged_posts do\n field(:entries, list_of(:post))\n pagination_fields()\n end\n\n object :paged_videos do\n field(:entries, list_of(:video))\n pagination_fields()\n end\n\n object :paged_repos do\n field(:entries, list_of(:repo))\n pagination_fields()\n end\n\n object :paged_jobs do\n field(:entries, list_of(:job))\n pagination_fields()\n end\n\n object :paged_comments do\n field(:entries, list_of(:comment))\n pagination_fields()\n end\n\n object :paged_communities do\n field(:entries, list_of(:community))\n pagination_fields()\n end\n\n object :paged_tags do\n field(:entries, list_of(:article_tag))\n pagination_fields()\n end\n\n object :paged_threads do\n field(:entries, list_of(:thread))\n pagination_fields()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/cms_queries.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Queries do\n @moduledoc \"\"\"\n CMS queries\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_queries do\n field :community, :community do\n # arg(:id, non_null(:id))\n arg(:id, :id)\n arg(:title, :string)\n arg(:raw, :string)\n resolve(&R.CMS.community/3)\n end\n\n @desc \"communities with pagination info\"\n field :paged_communities, :paged_communities do\n arg(:filter, non_null(:communities_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_communities/3)\n end\n\n @desc \"paged subscribers of a community\"\n field :community_subscribers, :paged_users do\n arg(:id, non_null(:id))\n arg(:filter, :paged_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.community_subscribers/3)\n end\n\n @desc \"paged subscribers of a community\"\n field :community_editors, :paged_users do\n arg(:id, non_null(:id))\n arg(:filter, :paged_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.community_editors/3)\n end\n\n @desc \"get all categories\"\n field :paged_categories, :paged_categories do\n arg(:filter, :paged_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_categories/3)\n end\n\n @desc \"get all the threads across all communities\"\n field :paged_threads, :paged_threads do\n arg(:filter, :threads_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_threads/3)\n end\n\n @desc \"get post by id\"\n field :post, non_null(:post) do\n arg(:id, non_null(:id))\n resolve(&R.CMS.post/3)\n end\n\n @desc \"get paged posts\"\n field :paged_posts, :paged_posts do\n arg(:filter, non_null(:paged_article_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_posts/3)\n end\n\n @desc \"get video by id\"\n field :video, non_null(:video) do\n arg(:id, non_null(:id))\n resolve(&R.CMS.video/3)\n end\n\n @desc \"get paged videos\"\n field :paged_videos, :paged_videos do\n arg(:filter, non_null(:paged_article_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_videos/3)\n end\n\n @desc \"get repo by id\"\n field :repo, non_null(:repo) do\n arg(:id, non_null(:id))\n resolve(&R.CMS.repo/3)\n end\n\n @desc \"get paged videos\"\n field :paged_repos, :paged_repos do\n arg(:filter, non_null(:paged_article_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_repos/3)\n end\n\n @desc \"get job by id\"\n field :job, non_null(:job) do\n arg(:id, non_null(:id))\n resolve(&R.CMS.job/3)\n end\n\n @desc \"get paged jobs\"\n field :paged_jobs, :paged_jobs do\n arg(:filter, non_null(:paged_article_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_jobs/3)\n end\n\n field :favorite_users, :paged_users do\n arg(:id, non_null(:id))\n arg(:type, :thread, default_value: :post)\n arg(:action, :favorite_action, default_value: :favorite)\n arg(:filter, :paged_article_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.reaction_users/3)\n end\n\n # get all tags\n field :paged_tags, :paged_tags do\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.get_tags/3)\n end\n\n # TODO: remove\n field :tags, :paged_tags do\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n # TODO: should be passport\n resolve(&R.CMS.get_tags/3)\n end\n\n # partial\n @desc \"get paged tags belongs to community_id or community\"\n field :partial_tags, list_of(:article_tag) do\n arg(:community_id, :id)\n arg(:community, :string)\n arg(:thread, :thread, default_value: :post)\n\n resolve(&R.CMS.get_tags/3)\n end\n\n @desc \"get paged comments\"\n field :paged_comments, :paged_comments do\n arg(:id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n arg(:filter, :comments_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_comments/3)\n end\n\n # comments\n field :comments, :paged_comments do\n arg(:id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n arg(:filter, :comments_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_comments/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,5,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/purchase.ex","source":"defmodule GroupherServer.Accounts.Purchase do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id)a\n @optional_fields ~w(theme community_chart brainwash_free)a\n\n @type t :: %Purchase{}\n schema \"purchases\" do\n belongs_to(:user, User)\n\n field(:theme, :boolean)\n field(:community_chart, :boolean)\n field(:brainwash_free, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Purchase{} = purchase, attrs) do\n purchase\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,1,null,null,null,0,null,null,null,0,null,null,null,0,null,null,null,3,null,null],"name":"lib/groupher_server_web/resolvers/statistics_resolver.ex","source":"defmodule GroupherServerWeb.Resolvers.Statistics do\n @moduledoc \"\"\"\n resolvers for Statistics\n \"\"\"\n alias GroupherServer.{Accounts, CMS, Statistics}\n # alias Helper.ORM\n\n # tmp for test\n def list_contributes(_root, %{id: id}, _info) do\n Statistics.list_contributes(%Accounts.User{id: id})\n end\n\n def list_contributes(%Accounts.User{id: id}, _args, _info) do\n Statistics.list_contributes(%Accounts.User{id: id})\n end\n\n def list_contributes(%CMS.Community{id: id}, _args, _info) do\n Statistics.list_contributes(%CMS.Community{id: id})\n end\n\n def list_contributes_digest(%CMS.Community{id: id}, _args, _info) do\n Statistics.list_contributes_digest(%CMS.Community{id: id})\n end\n\n def make_contrubute(_root, %{user_id: user_id}, _info) do\n Statistics.make_contribute(%Accounts.User{id: user_id})\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,99,98,null,null,null,null,null,null,null,null,null,null,95,null,null,97,null,null,null,null,null,null,null,null,null,null,null,95,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,4,4,null,null,4,null,null,4,null,null,4,null,null,4,null,null,null,null,null,null,null,null,null,null,4,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,3,3,null,null,null,null,null,null,null,null,3,3,3,null,null,null,null,null,6,null,null,6,null,null],"name":"lib/groupher_server/accounts/delegates/fans.ex","source":"defmodule GroupherServer.Accounts.Delegate.Fans do\n @moduledoc \"\"\"\n user followers / following related\n \"\"\"\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1]\n import Helper.ErrorCode\n import ShortMaps\n\n alias Helper.{ORM, QueryBuilder, SpecType}\n alias GroupherServer.{Accounts, Repo}\n\n alias GroupherServer.Accounts.{User, UserFollower, UserFollowing}\n\n alias Ecto.Multi\n\n @doc \"\"\"\n follow a user\n \"\"\"\n @spec follow(User.t(), User.t()) :: {:ok, User.t()} | SpecType.gq_error()\n def follow(%User{id: user_id}, %User{id: follower_id}) do\n with true <- to_string(user_id) !== to_string(follower_id),\n {:ok, _follow_user} <- ORM.find(User, follower_id) do\n Multi.new()\n |> Multi.insert(\n :create_follower,\n UserFollower.changeset(%UserFollower{}, ~m(user_id follower_id)a)\n )\n |> Multi.insert(\n :create_following,\n UserFollowing.changeset(%UserFollowing{}, %{user_id: user_id, following_id: follower_id})\n )\n |> Multi.run(:add_achievement, fn _ ->\n Accounts.achieve(%User{id: follower_id}, :add, :follow)\n end)\n |> Repo.transaction()\n |> follow_result()\n else\n false ->\n {:error, [message: \"can't follow yourself\", code: ecode(:self_conflict)]}\n\n {:error, error} ->\n {:error, [message: error, code: ecode(:not_exsit)]}\n end\n end\n\n @spec follow_result({:ok, map()}) :: SpecType.done()\n defp follow_result({:ok, %{create_follower: user_follower}}) do\n User |> ORM.find(user_follower.follower_id)\n end\n\n defp follow_result({:error, :create_follower, _result, _steps}) do\n {:error, [message: \"already followed\", code: ecode(:already_did)]}\n end\n\n defp follow_result({:error, :create_following, _result, _steps}) do\n {:error, [message: \"follow fails\", code: ecode(:react_fails)]}\n end\n\n defp follow_result({:error, :add_achievement, _result, _steps}) do\n {:error, [message: \"follow acieve fails\", code: ecode(:react_fails)]}\n end\n\n @doc \"\"\"\n undo a follow action to a user\n \"\"\"\n @spec undo_follow(User.t(), User.t()) :: {:ok, User.t()} | SpecType.gq_error()\n def undo_follow(%User{id: user_id}, %User{id: follower_id}) do\n with true <- to_string(user_id) !== to_string(follower_id),\n {:ok, _follow_user} <- ORM.find(User, follower_id) do\n Multi.new()\n |> Multi.run(:delete_follower, fn _ ->\n ORM.findby_delete!(UserFollower, ~m(user_id follower_id)a)\n end)\n |> Multi.run(:delete_following, fn _ ->\n ORM.findby_delete!(UserFollowing, %{user_id: user_id, following_id: follower_id})\n end)\n |> Multi.run(:minus_achievement, fn _ ->\n Accounts.achieve(%User{id: follower_id}, :minus, :follow)\n end)\n |> Repo.transaction()\n |> undo_follow_result()\n else\n false ->\n {:error, [message: \"can't undo follow yourself\", code: ecode(:self_conflict)]}\n\n {:error, error} ->\n {:error, [message: error, code: ecode(:not_exsit)]}\n end\n end\n\n defp undo_follow_result({:ok, %{delete_follower: user_follower}}) do\n User |> ORM.find(user_follower.follower_id)\n end\n\n defp undo_follow_result({:error, :delete_follower, _result, _steps}) do\n {:error, [message: \"already unfollowed\", code: ecode(:already_did)]}\n end\n\n defp undo_follow_result({:error, :delete_following, _result, _steps}) do\n {:error, [message: \"unfollow fails\", code: ecode(:react_fails)]}\n end\n\n defp undo_follow_result({:error, :minus_achievement, _result, _steps}) do\n {:error, [message: \"follow acieve fails\", code: ecode(:react_fails)]}\n end\n\n @doc \"\"\"\n get paged followers of a user\n \"\"\"\n @spec fetch_followers(User.t(), map()) :: {:ok, map()} | {:error, String.t()}\n def fetch_followers(%User{id: user_id}, filter) do\n UserFollower\n |> where([uf], uf.follower_id == ^user_id)\n |> join(:inner, [uf], u in assoc(uf, :user))\n |> load_fans(filter)\n end\n\n @doc \"\"\"\n get paged followings of a user\n \"\"\"\n @spec fetch_followings(User.t(), map()) :: {:ok, map()} | {:error, String.t()}\n def fetch_followings(%User{id: user_id}, filter) do\n UserFollowing\n |> where([uf], uf.user_id == ^user_id)\n |> join(:inner, [uf], u in assoc(uf, :following))\n |> load_fans(filter)\n end\n\n @spec load_fans(Ecto.Queryable.t(), map()) :: {:ok, map()} | {:error, String.t()}\n defp load_fans(queryable, ~m(page size)a = filter) do\n queryable\n |> select([uf, u], u)\n |> QueryBuilder.filter_pack(filter)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,18,null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,null],"name":"lib/groupher_server/cms/community_thread.ex","source":"defmodule GroupherServer.CMS.CommunityThread do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.CMS.{Community, Thread}\n\n @required_fields ~w(community_id thread_id)a\n\n @type t :: %CommunityThread{}\n schema \"communities_threads\" do\n belongs_to(:community, Community, foreign_key: :community_id)\n belongs_to(:thread, Thread, foreign_key: :thread_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%CommunityThread{} = community_thread, attrs) do\n community_thread\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:community_id)\n |> foreign_key_constraint(:thread_id)\n |> unique_constraint(:community_id, name: :communities_threads_community_id_thread_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,176,null,12,null,null,164,null,null,null,null,164,null,16,148,null,164,null,null,0,162,null,14,null,null,162,null,150,null,null,null,12,null,null,null],"name":"lib/groupher_server_web/middleware/pagesize_proof.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.PageSizeProof do\n @behaviour Absinthe.Middleware\n\n import Helper.Utils, only: [handle_absinthe_error: 3, get_config: 2]\n import Helper.ErrorCode\n\n @max_page_size get_config(:general, :page_size)\n @inner_page_size get_config(:general, :inner_page_size)\n\n # 1. if has filter:first and filter:size -> makesure it not too large\n # 2. if not has filter: marge to default first: 5\n # 3. large size should trigger error\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(resolution, _) do\n # IO.inspect resolution.arguments, label: \"resolution arguments\"\n # IO.inspect valid_size(resolution.arguments), label: \"valid_size\"\n\n case valid_size(resolution.arguments) do\n {:error, msg} ->\n resolution |> handle_absinthe_error(msg, ecode(:pagination))\n\n arguments ->\n %{resolution | arguments: sort_desc_by_default(arguments)}\n end\n end\n\n defp sort_desc_by_default(%{filter: filter} = arguments) do\n filter =\n if Map.has_key?(filter, :sort),\n do: filter,\n else: filter |> Map.merge(%{sort: :desc_inserted})\n\n arguments |> Map.merge(%{filter: filter})\n end\n\n defp valid_size(%{filter: %{first: size}} = arg), do: do_size_check(size, arg)\n defp valid_size(%{filter: %{size: size}} = arg), do: do_size_check(size, arg)\n\n defp valid_size(arg), do: arg |> Map.merge(%{filter: %{first: @inner_page_size}})\n\n defp do_size_check(size, arg) do\n case size in 1..@max_page_size do\n true ->\n arg\n\n _ ->\n {:error,\n \"SIZE_RANGE_ERROR: size shuold between 0 and #{@max_page_size}, current: #{size}\"}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/operation.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Operation do\n @moduledoc \"\"\"\n CMS mutations for cms operations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_opertion_mutations do\n @desc \"set category to a community\"\n field :set_category, :community do\n arg(:community_id, non_null(:id))\n arg(:category_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.set\")\n\n resolve(&R.CMS.set_category/3)\n end\n\n @desc \"unset category to a community\"\n field :unset_category, :community do\n arg(:community_id, non_null(:id))\n arg(:category_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.unset\")\n\n resolve(&R.CMS.unset_category/3)\n end\n\n @desc \"bind a thread to a exist community\"\n field :set_thread, :community do\n arg(:community_id, non_null(:id))\n arg(:thread_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->thread.set\")\n\n resolve(&R.CMS.set_thread/3)\n end\n\n @desc \"remove a thread from a exist community, thread content is not delete\"\n field :unset_thread, :community do\n arg(:community_id, non_null(:id))\n arg(:thread_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->thread.unset\")\n\n resolve(&R.CMS.unset_thread/3)\n end\n\n @desc \"stamp rules on user's passport\"\n field :stamp_cms_passport, :idlike do\n arg(:user_id, non_null(:id))\n arg(:rules, non_null(:json))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->community.stamp_passport\")\n\n resolve(&R.CMS.stamp_passport/3)\n end\n\n @desc \"subscribe a community so it can appear in sidebar\"\n field :subscribe_community, :community do\n arg(:community_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.subscribe_community/3)\n end\n\n @desc \"unsubscribe a community\"\n field :unsubscribe_community, :community do\n arg(:community_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.unsubscribe_community/3)\n end\n\n @desc \"set a tag within community\"\n field :set_tag, :article_tag do\n arg(:id, non_null(:id))\n arg(:tag_id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.set\")\n\n resolve(&R.CMS.set_tag/3)\n end\n\n @desc \"unset a tag within community\"\n field :unset_tag, :article_tag do\n # thread id\n arg(:id, non_null(:id))\n arg(:tag_id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.unset\")\n\n resolve(&R.CMS.unset_tag/3)\n end\n\n # TODO: use community loader\n field :mirror_article, :community do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->t?.community.mirror\")\n resolve(&R.CMS.mirror_article/3)\n end\n\n # TODO: can't not unset the oldest community\n field :unmirror_article, :community do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->t?.community.unmirror\")\n resolve(&R.CMS.unmirror_article/3)\n end\n\n field :reaction, :article do\n arg(:id, non_null(:id))\n arg(:thread, non_null(:thread))\n arg(:action, non_null(:cms_action))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.reaction/3)\n end\n\n field :undo_reaction, :article do\n arg(:id, non_null(:id))\n arg(:thread, non_null(:thread))\n arg(:action, non_null(:cms_action))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.undo_reaction/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,17,9,7,5,3,null,null,null,2,null,null,null,2,null,null,null,2,null,null,null,8,null,null,null,null,null,0,null,null,null,null,9,9,null,9,null,null,null,null,null,null,7,null,7,null,null,null,null,null,null,5,null,5,null,null,null,null,null],"name":"lib/groupher_server_web/middleware/publish_throttle.ex","source":"defmodule GroupherServerWeb.Middleware.PublishThrottle do\n @behaviour Absinthe.Middleware\n import Helper.Utils, only: [handle_absinthe_error: 3, get_config: 2]\n import Helper.ErrorCode\n\n alias GroupherServer.{Statistics, Accounts}\n\n @interval_minutes get_config(:general, :publish_throttle_interval_minutes)\n @hour_limit get_config(:general, :publish_throttle_hour_limit)\n @day_total get_config(:general, :publish_throttle_day_limit)\n\n def call(%{context: %{cur_user: cur_user}} = resolution, opt) do\n with {:ok, record} <- Statistics.load_throttle_record(%Accounts.User{id: cur_user.id}),\n {:ok, _} <- interval_check(record, opt),\n {:ok, _} <- hour_limit_check(record, opt),\n {:ok, _} <- day_limit_check(record, opt) do\n resolution\n else\n {:error, :interval_check} ->\n resolution\n |> handle_absinthe_error(\"throttle_interval\", ecode(:throttle_inverval))\n\n {:error, :hour_limit_check} ->\n resolution\n |> handle_absinthe_error(\"throttle_hour\", ecode(:throttle_hour))\n\n {:error, :day_limit_check} ->\n resolution\n |> handle_absinthe_error(\"throttle_day\", ecode(:throttle_day))\n\n {:error, _error} ->\n # publish first time ignore\n resolution\n end\n end\n\n def call(resolution, _) do\n resolution\n |> handle_absinthe_error(\"Authorize: need login\", ecode(:account_login))\n end\n\n # TODO: option: passport ..\n defp interval_check(%Statistics.PublishThrottle{last_publish_time: last_publish_time}, opt) do\n interval_opt = Keyword.get(opt, :interval) || @interval_minutes\n latest_valid_time = Timex.shift(last_publish_time, minutes: interval_opt)\n\n case Timex.before?(latest_valid_time, Timex.now()) do\n true -> {:ok, :interval_check}\n false -> {:error, :interval_check}\n end\n end\n\n defp hour_limit_check(%Statistics.PublishThrottle{hour_count: hour_count}, opt) do\n hour_count_opt = Keyword.get(opt, :hour_limit) || @hour_limit\n\n case hour_count < hour_count_opt do\n true -> {:ok, :hour_limit_check}\n false -> {:error, :hour_limit_check}\n end\n end\n\n defp day_limit_check(%Statistics.PublishThrottle{date_count: day_count}, opt) do\n day_limit_opt = Keyword.get(opt, :day_limit) || @day_total\n\n case day_count < day_limit_opt do\n true -> {:ok, :day_limit_check}\n false -> {:error, :day_limit_check}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,9771,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,null,null,null,null],"name":"lib/groupher_server/accounts/user.ex","source":"defmodule GroupherServer.Accounts.User do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.Accounts.{\n Achievement,\n Customization,\n GithubUser,\n Purchase,\n UserBill,\n UserFollower,\n UserFollowing\n }\n\n alias GroupherServer.CMS\n\n @type t :: %User{}\n schema \"users\" do\n field(:nickname, :string)\n field(:avatar, :string)\n field(:sex, :string)\n field(:bio, :string)\n field(:email, :string)\n field(:location, :string)\n field(:education, :string)\n field(:company, :string)\n field(:qq, :string)\n field(:weibo, :string)\n field(:weichat, :string)\n field(:from_github, :boolean)\n has_one(:achievement, Achievement)\n has_one(:github_profile, GithubUser)\n has_one(:cms_passport, CMS.Passport)\n\n has_many(:followers, {\"users_followers\", UserFollower})\n has_many(:followings, {\"users_followings\", UserFollowing})\n\n has_many(:subscribed_communities, {\"communities_subscribers\", CMS.CommunitySubscriber})\n has_many(:favorited_posts, {\"posts_favorites\", CMS.PostFavorite})\n has_many(:favorited_jobs, {\"jobs_favorites\", CMS.JobFavorite})\n\n field(:sponsor_member, :boolean)\n field(:paid_member, :boolean)\n field(:platinum_member, :boolean)\n\n has_many(:bills, {\"users_bills\", UserBill})\n has_one(:customization, Customization)\n has_one(:purchase, Purchase)\n\n timestamps(type: :utc_datetime)\n end\n\n @required_fields ~w(nickname avatar)a\n @optional_fields ~w(nickname bio avatar sex location email company education qq weichat weibo)a\n\n @doc false\n def changeset(%User{} = user, attrs) do\n # |> cast(attrs, [:username, :nickname, :bio, :company])\n # |> validate_required([:username])\n # |> cast(attrs, @required_fields, @optional_fields)\n user\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> validate_length(:nickname, min: 3, max: 30)\n |> validate_length(:bio, min: 3, max: 100)\n |> validate_inclusion(:sex, [\"dude\", \"girl\"])\n |> validate_format(:email, ~r/@/)\n |> validate_length(:location, min: 2, max: 30)\n |> validate_length(:company, min: 3, max: 30)\n |> validate_length(:qq, min: 8, max: 15)\n |> validate_length(:weichat, min: 3, max: 30)\n |> validate_length(:weibo, min: 3, max: 30)\n\n # |> unique_constraint(:username)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,4,null,null],"name":"lib/groupher_server/accounts/customization.ex","source":"defmodule GroupherServer.Accounts.Customization do\n @moduledoc false\n\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id)a\n @optional_fields ~w(theme sidebar_layout community_chart brainwash_free)a\n\n @type t :: %Customization{}\n schema \"customizations\" do\n belongs_to(:user, User)\n\n field(:theme, :boolean)\n field(:sidebar_layout, :map)\n field(:community_chart, :boolean)\n field(:brainwash_free, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Customization{} = customization, attrs) do\n customization\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,0,null,null,0,null,null,null,0,null,null,0,null],"name":"lib/groupher_server_web/middleware/count_length.ex","source":"# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.CountLength do\n @behaviour Absinthe.Middleware\n # google: must appear in the GROUP BY clause or be used in an aggregate function\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(%{value: []} = resolution, _) do\n %{resolution | value: 0}\n end\n\n def call(%{value: value} = resolution, _) when is_list(value) do\n %{resolution | value: length(value)}\n end\n\n def call(resolution, _), do: resolution\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,58,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/delivery/sys_notification.ex","source":"defmodule GroupherServer.Delivery.SysNotification do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n @required_fields ~w(source_title source_id source_type)a\n @optional_fields ~w(source_preview)a\n\n @type t :: %SysNotification{}\n schema \"sys_notifications\" do\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:source_preview, :string)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%SysNotification{} = sys_notification, attrs) do\n sys_notification\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,881,null,881,null,null,null,null,null,null,null,null,null,null,null,null,null,null,537,null,537,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,536,null,536,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,542,542,null,542,null,null,null,542,null,null,null,null,null,null,null,null,null,null,null,null,0,null,0,null,null,null,0,null,0,null,null,null,null,0,null,null,null,null,2628,null,null,null,0,null,null,null,59,59,null,null,null,4718,4718,null,4718,4718,null,4718,null,null,null,null,null,null,69,null,69,69,69,null,null,null,null,null,63,null,63,63,null,null,null,null,null,null,null,null,null,null,27,null,27,27,27,null,27,null,null,null,null,8433,null,8433,null,8433,null,null,null,null,null,null,1,null,1,1,1,1,1,1,null,null,null,null,null,null,null,null,null,null,null,235,209,0,27,4,3,6,5,0,0,null,null,0,null,8,1,8,1,null,null,null,null,null,null,null,null,null,854,533,533,536,0,0,2628,61,55,null,null,26,null,8224,4713,59,null,null,0,null,null,3735,null,null,null,null,null,null,null,3735,null,null,null,134,null,2617,2617,null,null,134,null,null,null,null,null,null,6,null,null,null,12,null,12,28,null,28,null,28,null,28,null,null,28,null,null,null,null,12,null,12,62,null,62,null,62,null,62,null,null,null,62,null,null,null],"name":"test/support/factory.ex","source":"defmodule GroupherServer.Factory do\n @moduledoc \"\"\"\n This module defines the mock data/func to be used by\n tests that require insert some mock data to db.\n\n for example you can db_insert(:user) to insert user into db\n \"\"\"\n import Helper.Utils, only: [done: 1]\n\n alias GroupherServer.Repo\n alias GroupherServer.{CMS, Accounts, Delivery}\n\n defp mock_meta(:post) do\n body = Faker.Lorem.sentence(%Range{first: 80, last: 120})\n\n %{\n title: Faker.Lorem.Shakespeare.king_richard_iii(),\n body: body,\n digest: String.slice(body, 1, 150),\n length: String.length(body),\n author: mock(:author),\n views: Enum.random(0..2000),\n communities: [\n mock(:community),\n mock(:community)\n ]\n }\n end\n\n defp mock_meta(:video) do\n desc = Faker.Lorem.sentence(%Range{first: 15, last: 60})\n\n %{\n title: Faker.Lorem.Shakespeare.king_richard_iii(),\n poster: Faker.Avatar.image_url(),\n desc: desc,\n duration: \"03:30\",\n duration_sec: Enum.random(300..12000),\n source: \"youtube\",\n link: \"http://www.youtube.com/video/1\",\n original_author: \"simon\",\n original_author_link: \"http://www.youtube.com/user/1\",\n author: mock(:author),\n views: Enum.random(0..2000),\n communities: [\n mock(:community),\n mock(:community)\n ]\n }\n end\n\n defp mock_meta(:repo) do\n desc = Faker.Lorem.sentence(%Range{first: 15, last: 60})\n\n %{\n repo_name: Faker.Lorem.Shakespeare.king_richard_iii(),\n desc: desc,\n readme: desc,\n language: \"javascript\",\n author: mock(:author),\n repo_link: \"http://www.github.com/mydearxym\",\n producer: \"mydearxym\",\n producer_link: \"http://www.github.com/mydearxym\",\n repo_star_count: Enum.random(0..2000),\n repo_fork_count: Enum.random(0..2000),\n repo_watch_count: Enum.random(0..2000),\n views: Enum.random(0..2000),\n communities: [\n mock(:community),\n mock(:community)\n ]\n }\n end\n\n defp mock_meta(:job) do\n body = Faker.Lorem.sentence(%Range{first: 80, last: 120})\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n title: Faker.Lorem.Shakespeare.king_richard_iii(),\n company: Faker.Company.name(),\n company_logo: Faker.Avatar.image_url(),\n location: \"location #{unique_num}\",\n body: body,\n digest: String.slice(body, 1, 150),\n length: String.length(body),\n author: mock(:author),\n views: Enum.random(0..2000),\n communities: [\n mock(:community)\n ]\n }\n end\n\n defp mock_meta(:comment) do\n body = Faker.Lorem.sentence(%Range{first: 30, last: 80})\n\n %{body: body}\n end\n\n defp mock_meta(:mention) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n from_user: mock(:user),\n to_user: mock(:user),\n source_id: \"1\",\n source_type: \"post\",\n source_preview: \"source_preview #{unique_num}.\"\n }\n end\n\n defp mock_meta(:author) do\n %{role: \"normal\", user: mock(:user)}\n end\n\n defp mock_meta(:communities_threads) do\n %{community_id: 1, thread_id: 1}\n end\n\n defp mock_meta(:thread) do\n unique_num = System.unique_integer([:positive, :monotonic])\n %{title: \"thread #{unique_num}\", raw: \"thread #{unique_num}\", index: :rand.uniform(20)}\n end\n\n defp mock_meta(:community) do\n unique_num = System.unique_integer([:positive, :monotonic])\n random_num = Enum.random(0..2000)\n\n %{\n title: \"community_#{random_num}_#{unique_num}\",\n desc: \"community desc\",\n raw: \"community_#{unique_num}\",\n logo: \"https://coderplanets.oss-cn-beijing.aliyuncs.com/icons/pl/elixir.svg\",\n author: mock(:user)\n }\n end\n\n defp mock_meta(:category) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n title: \"category#{unique_num}\",\n raw: \"category#{unique_num}\",\n author: mock(:author)\n }\n end\n\n defp mock_meta(:article_tag) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n title: \"#{Faker.Pizza.cheese()} #{unique_num}\",\n thread: \"POST\",\n color: \"YELLOW\",\n # community: Faker.Pizza.topping(),\n community: mock(:community),\n author: mock(:author)\n # user_id: 1\n }\n end\n\n defp mock_meta(:sys_notification) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n source_id: \"#{unique_num}\",\n source_title: \"#{Faker.Pizza.cheese()}\",\n source_type: \"post\",\n source_preview: \"#{Faker.Pizza.cheese()}\"\n }\n end\n\n defp mock_meta(:user) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n # username: \"#{Faker.Name.first_name()} #{unique_num}\",\n nickname: \"#{Faker.Name.first_name()} #{unique_num}\",\n bio: Faker.Lorem.Shakespeare.romeo_and_juliet(),\n avatar: Faker.Avatar.image_url()\n }\n end\n\n defp mock_meta(:github_profile) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n id: \"#{Faker.Name.first_name()} #{unique_num}\",\n login: \"#{Faker.Name.first_name()} #{unique_num}\",\n github_id: \"#{unique_num + 1000}\",\n node_id: \"#{unique_num + 2000}\",\n access_token: \"#{unique_num + 3000}\",\n bio: Faker.Lorem.Shakespeare.romeo_and_juliet(),\n company: Faker.Company.name(),\n location: \"chengdu\",\n email: Faker.Internet.email(),\n avatar_url: Faker.Avatar.image_url(),\n html_url: Faker.Avatar.image_url(),\n followers: unique_num * unique_num,\n following: unique_num * unique_num * unique_num\n }\n end\n\n def mock_attrs(_, attrs \\\\ %{})\n def mock_attrs(:user, attrs), do: mock_meta(:user) |> Map.merge(attrs)\n def mock_attrs(:author, attrs), do: mock_meta(:author) |> Map.merge(attrs)\n def mock_attrs(:post, attrs), do: mock_meta(:post) |> Map.merge(attrs)\n def mock_attrs(:video, attrs), do: mock_meta(:video) |> Map.merge(attrs)\n def mock_attrs(:repo, attrs), do: mock_meta(:repo) |> Map.merge(attrs)\n def mock_attrs(:job, attrs), do: mock_meta(:job) |> Map.merge(attrs)\n def mock_attrs(:community, attrs), do: mock_meta(:community) |> Map.merge(attrs)\n def mock_attrs(:thread, attrs), do: mock_meta(:thread) |> Map.merge(attrs)\n def mock_attrs(:mention, attrs), do: mock_meta(:mention) |> Map.merge(attrs)\n\n def mock_attrs(:communities_threads, attrs),\n do: mock_meta(:communities_threads) |> Map.merge(attrs)\n\n def mock_attrs(:article_tag, attrs), do: mock_meta(:article_tag) |> Map.merge(attrs)\n def mock_attrs(:sys_notification, attrs), do: mock_meta(:sys_notification) |> Map.merge(attrs)\n def mock_attrs(:category, attrs), do: mock_meta(:category) |> Map.merge(attrs)\n def mock_attrs(:github_profile, attrs), do: mock_meta(:github_profile) |> Map.merge(attrs)\n\n # NOTICE: avoid Recursive problem\n # bad example:\n # mismatch mismatch\n # | |\n # defp mock(:user), do: Accounts.User |> struct(mock_meta(:community))\n\n # this line of code will cause SERIOUS Recursive problem\n\n defp mock(:post), do: CMS.Post |> struct(mock_meta(:post))\n defp mock(:video), do: CMS.Video |> struct(mock_meta(:video))\n defp mock(:repo), do: CMS.Repo |> struct(mock_meta(:repo))\n defp mock(:job), do: CMS.Job |> struct(mock_meta(:job))\n defp mock(:comment), do: CMS.Comment |> struct(mock_meta(:comment))\n defp mock(:mention), do: Delivery.Mention |> struct(mock_meta(:mention))\n defp mock(:author), do: CMS.Author |> struct(mock_meta(:author))\n defp mock(:category), do: CMS.Category |> struct(mock_meta(:category))\n defp mock(:article_tag), do: CMS.Tag |> struct(mock_meta(:article_tag))\n\n defp mock(:sys_notification),\n do: Delivery.SysNotification |> struct(mock_meta(:sys_notification))\n\n defp mock(:user), do: Accounts.User |> struct(mock_meta(:user))\n defp mock(:community), do: CMS.Community |> struct(mock_meta(:community))\n defp mock(:thread), do: CMS.Thread |> struct(mock_meta(:thread))\n\n defp mock(:communities_threads),\n do: CMS.CommunityThread |> struct(mock_meta(:communities_threads))\n\n defp mock(factory_name, attributes) do\n factory_name |> mock() |> struct(attributes)\n end\n\n # \"\"\"\n # not use changeset because in test we may insert some attrs which not in schema\n # like: views, insert/update ... to test filter-sort,when ...\n # \"\"\"\n def db_insert(factory_name, attributes \\\\ []) do\n Repo.insert(mock(factory_name, attributes))\n end\n\n def db_insert_multi(factory_name, count \\\\ 2) do\n results =\n Enum.reduce(1..count, [], fn _, acc ->\n {:ok, value} = db_insert(factory_name)\n acc ++ [value]\n end)\n\n results |> done\n end\n\n alias GroupherServer.Accounts.User\n\n def mock_sys_notification(count \\\\ 3) do\n # {:ok, sys_notifications} = db_insert_multi(:sys_notification, count)\n db_insert_multi(:sys_notification, count)\n end\n\n def mock_mentions_for(%User{id: _to_user_id} = user, count \\\\ 3) do\n {:ok, users} = db_insert_multi(:user, count)\n\n Enum.map(users, fn u ->\n unique_num = System.unique_integer([:positive, :monotonic])\n\n info = %{\n source_id: \"1\",\n source_title: \"Title #{unique_num}\",\n source_type: \"post\",\n source_preview: \"preview #{unique_num}\"\n }\n\n {:ok, _} = Delivery.mention_someone(u, user, info)\n end)\n end\n\n def mock_notifications_for(%User{id: _to_user_id} = user, count \\\\ 3) do\n {:ok, users} = db_insert_multi(:user, count)\n\n Enum.map(users, fn u ->\n unique_num = System.unique_integer([:positive, :monotonic])\n\n info = %{\n source_id: \"1\",\n source_title: \"Title #{unique_num}\",\n source_type: \"post\",\n source_preview: \"preview #{unique_num}\",\n action: \"like\"\n }\n\n {:ok, _} = Delivery.notify_someone(u, user, info)\n end)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,368,null,null,null,null,null,null,null,null,null,null,null,null,140,null,null],"name":"lib/groupher_server/cms/passport.ex","source":"defmodule GroupherServer.CMS.Passport do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n @type t :: %Passport{}\n schema \"cms_passports\" do\n field(:rules, :map)\n belongs_to(:user, Accounts.User)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Passport{} = passport, attrs) do\n passport\n |> cast(attrs, [:rules, :user_id])\n |> validate_required([:rules, :user_id])\n |> unique_constraint(:user_id)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,null,3,null,null,null,null,null,null,211,79,null,null,null,null,null,null,null,null,139,null,5,5,null,null,134,134,null,null,null,null,4,4,null,3,null,null,1,null,null,null,null,null,1,null,null,null,139,null,null,null,139,null,null],"name":"lib/groupher_server/cms/delegates/passport_curd.ex","source":"defmodule GroupherServer.CMS.Delegate.PassportCURD do\n @moduledoc \"\"\"\n passport curd\n \"\"\"\n import Helper.Utils, only: [done: 1, deep_merge: 2]\n import Ecto.Query, warn: false\n import ShortMaps\n\n alias Helper.{NestedFilter, ORM}\n alias GroupherServer.CMS.Passport, as: UserPasport\n alias GroupherServer.{Accounts, Repo}\n\n # https://medium.com/front-end-hacking/use-github-oauth-as-your-sso-seamlessly-with-react-3e2e3b358fa1\n # http://www.ubazu.com/using-postgres-jsonb-columns-in-ecto\n # http://www.ubazu.com/using-postgres-jsonb-columns-in-ecto\n\n def paged_passports(community, key) do\n UserPasport\n |> where([p], fragment(\"(?->?->>?)::boolean = ?\", p.rules, ^community, ^key, true))\n |> Repo.all()\n |> done\n end\n\n @doc \"\"\"\n return a user's passport in CMS context\n \"\"\"\n def get_passport(%Accounts.User{} = user) do\n with {:ok, passport} <- ORM.find_by(UserPasport, user_id: user.id) do\n {:ok, passport.rules}\n end\n end\n\n # TODO passport should be public utils\n @doc \"\"\"\n insert or update a user's passport in CMS context\n \"\"\"\n def stamp_passport(rules, %Accounts.User{id: user_id}) do\n case ORM.find_by(UserPasport, user_id: user_id) do\n {:ok, passport} ->\n rules = passport.rules |> deep_merge(rules) |> reject_invalid_rules\n passport |> ORM.update(~m(rules)a)\n\n {:error, _} ->\n rules = rules |> reject_invalid_rules\n UserPasport |> ORM.create(~m(user_id rules)a)\n end\n end\n\n def erase_passport(rules, %Accounts.User{id: user_id}) when is_list(rules) do\n with {:ok, passport} <- ORM.find_by(UserPasport, user_id: user_id) do\n case pop_in(passport.rules, rules) do\n {nil, _} ->\n {:error, \"#{rules} not found\"}\n\n {_, lefts} ->\n passport |> ORM.update(%{rules: lefts})\n end\n end\n end\n\n def delete_passport(%Accounts.User{id: user_id}) do\n ORM.findby_delete!(UserPasport, ~m(user_id)a)\n end\n\n defp reject_invalid_rules(rules) when is_map(rules) do\n rules |> NestedFilter.drop_by_value([false]) |> reject_empty_values\n end\n\n defp reject_empty_values(map) when is_map(map) do\n for {k, v} <- map, v != %{}, into: %{}, do: {k, v}\n end\nend"},{"coverage":[null,null,null,361,null,null,null,null,null,null,null,361,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/router.ex","source":"defmodule GroupherServerWeb.Router do\n use GroupherServerWeb, :router\n\n pipeline :api do\n plug(:accepts, [\"json\"])\n plug(GroupherServerWeb.Context)\n end\n\n scope \"/graphiql\" do\n pipe_through(:api)\n\n forward(\n \"/\",\n Absinthe.Plug.GraphiQL,\n schema: GroupherServerWeb.Schema,\n pipeline: {ApolloTracing.Pipeline, :plug},\n interface: :playground,\n context: %{pubsub: GroupherServerWeb.Endpoint}\n )\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,0,null,0,null,null,0,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,9,null,4,null,null,1,null,null,4,null,null,0,null,null,null,null,null,null],"name":"lib/groupher_server/cms/utils/matcher.ex","source":"defmodule GroupherServer.CMS.Helper.Matcher do\n @moduledoc \"\"\"\n this module defined the matches and handy guard ...\n \"\"\"\n import Ecto.Query, warn: false\n\n alias GroupherServer.CMS.{\n Community,\n Post,\n Video,\n Repo,\n Job,\n PostFavorite,\n JobFavorite,\n PostStar,\n JobStar,\n PostComment,\n JobComment,\n Tag,\n Community,\n PostCommentLike,\n PostCommentDislike\n }\n\n @support_thread [:post, :video, :repo, :job]\n @support_react [:favorite, :star, :watch, :comment, :article_tag, :self]\n\n defguard valid_thread(thread) when thread in @support_thread\n defguard invalid_thread(thread) when thread not in @support_thread\n\n defguard valid_reaction(thread, react)\n when valid_thread(thread) and react in @support_react\n\n defguard invalid_reaction(thread, react)\n when invalid_thread(thread) and react not in @support_react\n\n defguard valid_feeling(feel) when feel in [:like, :dislike]\n\n # posts ...\n def match_action(:post, :self), do: {:ok, %{target: Post, reactor: Post, preload: :author}}\n\n def match_action(:post, :favorite),\n do: {:ok, %{target: Post, reactor: PostFavorite, preload: :user, preload_right: :post}}\n\n def match_action(:post, :star), do: {:ok, %{target: Post, reactor: PostStar, preload: :user}}\n def match_action(:post, :article_tag), do: {:ok, %{target: Post, reactor: Tag}}\n def match_action(:post, :community), do: {:ok, %{target: Post, reactor: Community}}\n\n def match_action(:post, :comment),\n do: {:ok, %{target: Post, reactor: PostComment, preload: :author}}\n\n def match_action(:post_comment, :like),\n do: {:ok, %{target: PostComment, reactor: PostCommentLike}}\n\n def match_action(:post_comment, :dislike),\n do: {:ok, %{target: PostComment, reactor: PostCommentDislike}}\n\n # videos ...\n def match_action(:video, :community), do: {:ok, %{target: Video, reactor: Community}}\n\n # repos ...\n def match_action(:repo, :community), do: {:ok, %{target: Repo, reactor: Community}}\n\n # jobs ...\n def match_action(:job, :self), do: {:ok, %{target: Job, reactor: Job, preload: :author}}\n def match_action(:job, :community), do: {:ok, %{target: Job, reactor: Community}}\n def match_action(:job, :star), do: {:ok, %{target: Job, reactor: JobStar, preload: :user}}\n def match_action(:job, :article_tag), do: {:ok, %{target: Job, reactor: Tag}}\n\n def match_action(:job, :comment),\n do: {:ok, %{target: Job, reactor: JobComment, preload: :author}}\n\n def match_action(:job, :favorite),\n do: {:ok, %{target: Job, reactor: JobFavorite, preload: :user}}\n\n def dynamic_where(thread, id) do\n case thread do\n :post ->\n {:ok, dynamic([p], p.post_id == ^id)}\n\n :post_comment ->\n {:ok, dynamic([p], p.post_comment_id == ^id)}\n\n :job ->\n {:ok, dynamic([p], p.job_id == ^id)}\n\n :job_comment ->\n {:ok, dynamic([p], p.job_comment_id == ^id)}\n\n _ ->\n {:error, 'where is not match'}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,84,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,29,null,null],"name":"lib/groupher_server/delivery/mention.ex","source":"defmodule GroupherServer.Delivery.Mention do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id source_title source_id source_type source_preview)a\n @optional_fields ~w(parent_id parent_type read)a\n\n @type t :: %Mention{}\n schema \"mentions\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:parent_id, :string)\n field(:parent_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Mention{} = mention, attrs) do\n mention\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,10,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/cms/job_star.ex","source":"defmodule GroupherServer.CMS.JobStar do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Job\n\n @required_fields ~w(user_id job_id)a\n\n @type t :: %JobStar{}\n schema \"jobs_stars\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:job, Job, foreign_key: :job_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%JobStar{} = job_star, attrs) do\n # |> unique_constraint(:user_id, name: :favorites_user_id_article_id_index)\n job_star\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:user_id, name: :jobs_stars_user_id_job_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server_web.ex","source":"defmodule GroupherServerWeb do\n @moduledoc \"\"\"\n The entrypoint for defining your web interface, such\n as controllers, views, channels and so on.\n\n This can be used in your application as:\n\n use GroupherServerWeb, :controller\n use GroupherServerWeb, :view\n\n The definitions below will be executed for every view,\n controller, etc, so keep them short and clean, focused\n on imports, uses and aliases.\n\n Do NOT define functions inside the quoted expressions\n below. Instead, define any helper function in modules\n and import those modules here.\n \"\"\"\n\n def controller do\n quote do\n use Phoenix.Controller, namespace: GroupherServerWeb\n import Plug.Conn\n import GroupherServerWeb.Router.Helpers\n import GroupherServerWeb.Gettext\n end\n end\n\n def view do\n quote do\n use Phoenix.View,\n root: \"lib/groupher_server_web/templates\",\n namespace: GroupherServerWeb\n\n # Import convenience functions from controllers\n import Phoenix.Controller, only: [get_flash: 2, view_module: 1]\n\n import GroupherServerWeb.Router.Helpers\n import GroupherServerWeb.ErrorHelpers\n import GroupherServerWeb.Gettext\n end\n end\n\n def router do\n quote do\n use Phoenix.Router\n import Plug.Conn\n import Phoenix.Controller\n end\n end\n\n def channel do\n quote do\n use Phoenix.Channel\n import GroupherServerWeb.Gettext\n end\n end\n\n @doc \"\"\"\n When used, dispatch to the appropriate controller/view/etc.\n \"\"\"\n defmacro __using__(which) when is_atom(which) do\n apply(__MODULE__, which, [])\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,14,null,14,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,105,null,null,null,null,null,null,0,null,null,null,null,null,0,null,null,null,60,60,null,null,null,142,null,null,null,15,null,null,null,5,4,1,null,null,0,0,null,null,null,null,6,null,39,8,null,null,null,null,null,6,null,null,null,null,null,4,null,null,null,null,4,null],"name":"lib/helper/utils.ex","source":"defmodule Helper.Utils do\n @moduledoc \"\"\"\n unitil functions\n \"\"\"\n import Ecto.Query, warn: false\n import Helper.ErrorHandler\n import Helper.ErrorCode\n\n def get_config(section, key, app \\\\ :groupher_server) do\n app\n |> Application.get_env(section)\n # |> IO.inspect(label: \"debug ci\")\n |> case do\n nil -> \"\"\n config -> Keyword.get(config, key)\n end\n end\n\n @doc \"\"\"\n handle General {:ok, ..} or {:error, ..} return\n \"\"\"\n def done(nil, :boolean), do: {:ok, false}\n def done(_, :boolean), do: {:ok, true}\n def done(nil, err_msg), do: {:error, err_msg}\n def done({:ok, _}, with: result), do: {:ok, result}\n\n def done({:ok, %{id: id}}, :status), do: {:ok, %{done: true, id: id}}\n def done({:error, _}, :status), do: {:ok, %{done: false}}\n\n def done(nil, queryable, id), do: {:error, not_found_formater(queryable, id)}\n def done(result, _, _), do: {:ok, result}\n\n def done(nil), do: {:error, \"record not found.\"}\n\n # def done({:error, error}), do: {:error, error}\n def done(result), do: {:ok, result}\n\n @doc \"\"\"\n see: https://hexdocs.pm/absinthe/errors.html#content for error format\n \"\"\"\n def handle_absinthe_error(resolution, err_msg, code) when is_integer(code) do\n resolution\n |> Absinthe.Resolution.put_result({:error, message: err_msg, code: code})\n end\n\n def handle_absinthe_error(resolution, err_msg) when is_list(err_msg) do\n # %{resolution | value: [], errors: transform_errors(changeset)}\n resolution\n # |> Absinthe.Resolution.put_result({:error, err_msg})\n |> Absinthe.Resolution.put_result({:error, message: err_msg, code: ecode()})\n end\n\n def handle_absinthe_error(resolution, err_msg) when is_binary(err_msg) do\n resolution\n # |> Absinthe.Resolution.put_result({:error, err_msg})\n |> Absinthe.Resolution.put_result({:error, message: err_msg, code: ecode()})\n end\n\n def map_key_stringify(%{__struct__: _} = map) when is_map(map) do\n map = Map.from_struct(map)\n map |> Enum.reduce(%{}, fn {key, val}, acc -> Map.put(acc, to_string(key), val) end)\n end\n\n def map_key_stringify(map) when is_map(map) do\n map |> Enum.reduce(%{}, fn {key, val}, acc -> Map.put(acc, to_string(key), val) end)\n end\n\n def deep_merge(left, right) do\n Map.merge(left, right, &deep_resolve/3)\n end\n\n def tobe_integer(val) do\n if is_integer(val),\n do: val,\n else: val |> String.to_integer()\n end\n\n def repeat(times, [x]) when is_integer(x), do: to_string(for _ <- 1..times, do: x)\n def repeat(times, x), do: for(_ <- 1..times, do: x)\n\n def add(num, offset \\\\ 1) when is_integer(num) and is_integer(offset), do: num + offset\n\n def map_atom_value(attrs, :string) do\n results =\n Enum.map(attrs, fn {k, v} ->\n if is_atom(v) do\n {k, to_string(v)}\n else\n {k, v}\n end\n end)\n\n results |> Enum.into(%{})\n end\n\n # Key exists in both maps, and both values are maps as well.\n # These can be merged recursively.\n # defp deep_resolve(_key, left = %{},right = %{}) do\n defp deep_resolve(_key, %{} = left, %{} = right), do: deep_merge(left, right)\n\n # Key exists in both maps, but at least one of the values is\n # NOT a map. We fall back to standard merge behavior, preferring\n # the value on the right.\n defp deep_resolve(_key, _left, right), do: right\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,53,null,null,null,null,null,null,null,null,null,null,null,377,null,377,364,null,null,null,null,null],"name":"test/support/conn_case.ex","source":"defmodule GroupherServerWeb.ConnCase do\n @moduledoc \"\"\"\n This module defines the test case to be used by\n tests that require setting up a connection.\n\n Such tests rely on `Phoenix.ConnTest` and also\n import other functionality to make it easier\n to build common datastructures and query the data layer.\n\n Finally, if the test case interacts with the database,\n it cannot be async. For this reason, every test runs\n inside a transaction which is reset at the beginning\n of the test unless the test case is marked as async.\n \"\"\"\n\n use ExUnit.CaseTemplate\n\n using do\n quote do\n # Import conveniences for testing with connections\n use Phoenix.ConnTest\n import GroupherServerWeb.Router.Helpers\n\n # The default endpoint for testing\n @endpoint GroupherServerWeb.Endpoint\n end\n end\n\n setup tags do\n :ok = Ecto.Adapters.SQL.Sandbox.checkout(GroupherServer.Repo)\n\n unless tags[:async] do\n Ecto.Adapters.SQL.Sandbox.mode(GroupherServer.Repo, {:shared, self()})\n end\n\n {:ok, conn: Phoenix.ConnTest.build_conn()}\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,125,null,null,null,null,null,null,null,null,null,null,null,62,null,null],"name":"lib/groupher_server/cms/job_favorite.ex","source":"defmodule GroupherServer.CMS.JobFavorite do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Job\n\n @required_fields ~w(user_id job_id)a\n\n @type t :: %JobFavorite{}\n schema \"jobs_favorites\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:job, Job, foreign_key: :job_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%JobFavorite{} = job_favorite, attrs) do\n job_favorite\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:user_id, name: :jobs_favorites_user_id_job_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,983,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,6,null,null],"name":"lib/groupher_server/cms/job.ex","source":"defmodule GroupherServer.CMS.Job do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, JobFavorite, Tag}\n\n @required_fields ~w(title company company_logo location body digest length)a\n @optional_fields ~w(link_addr link_source min_education)a\n\n @type t :: %Job{}\n schema \"cms_jobs\" do\n field(:title, :string)\n field(:company, :string)\n field(:bonus, :string)\n field(:company_logo, :string)\n field(:location, :string)\n field(:desc, :string)\n field(:body, :string)\n belongs_to(:author, Author)\n field(:views, :integer, default: 0)\n field(:link_addr, :string)\n field(:link_source, :string)\n\n field(:min_salary, :integer, default: 0)\n field(:max_salary, :integer, default: 10_000_000)\n\n field(:min_experience, :integer, default: 1)\n field(:max_experience, :integer, default: 3)\n\n # college - bachelor - master - doctor\n field(:min_education, :string)\n\n field(:digest, :string)\n field(:length, :integer)\n\n # has_many(:comments, {\"jobs_comments\", JobComment})\n has_many(:favorites, {\"jobs_favorites\", JobFavorite})\n # has_many(:stars, {\"posts_stars\", PostStar})\n\n many_to_many(\n :tags,\n Tag,\n join_through: \"jobs_tags\",\n join_keys: [job_id: :id, tag_id: :id],\n # :delete_all will only remove data from the join source\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_jobs\",\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Job{} = job, attrs) do\n job\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,26,26,null,26,26,26,null,26,null,4,4,null,4,4,null,null,22,22,22,22,null,null,null,null,null,17,9,9,null,9,null,9,9,null,9,9,null,9,9,null,null,null,null,null,null,null,null,18,null,null,null,9,9,null,9,null,9,null,null,null,null,6,6,6,null,null,null,null,2,2,null,null,null,null,1,1,1,null,null,null,null,2,2,null,null,null,null,1,1,1,null,null,null],"name":"lib/groupher_server/statistics/delegates/throttle.ex","source":"defmodule GroupherServer.Statistics.Delegate.Throttle do\n import Ecto.Query, warn: false\n import ShortMaps\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.Statistics.PublishThrottle\n alias Helper.{ORM}\n\n def log_publish_action(%User{id: user_id}) do\n cur_date = Timex.today() |> Date.to_iso8601()\n cur_datetime = DateTime.utc_now() |> DateTime.to_iso8601()\n\n last_publish_time = cur_datetime\n publish_hour = cur_datetime\n publish_date = cur_date\n\n case PublishThrottle |> ORM.find_by(~m(user_id)a) do\n {:ok, record} ->\n date_count = record.date_count + 1\n hour_count = record.hour_count + 1\n\n attrs = ~m(user_id publish_date publish_hour date_count hour_count last_publish_time)a\n record |> ORM.update(attrs)\n\n {:error, _} ->\n date_count = 1\n hour_count = 1\n attrs = ~m(user_id publish_date publish_hour date_count hour_count last_publish_time)a\n PublishThrottle |> ORM.create(attrs)\n end\n end\n\n # auto run check for same hour / day\n def load_throttle_record(%User{id: user_id}) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n date_count = if is_same_day?(record.publish_date), do: record.date_count, else: 0\n hour_count = if is_same_hour?(record.publish_hour), do: record.hour_count, else: 0\n\n case date_count !== 0 or hour_count !== 0 do\n true ->\n cur_date = Timex.today() |> Date.to_iso8601()\n cur_datetime = DateTime.utc_now() |> DateTime.to_iso8601()\n\n publish_hour = cur_datetime\n publish_date = cur_date\n\n attrs = ~m(publish_date publish_hour date_count hour_count)a\n record |> ORM.update(attrs)\n\n false ->\n {:ok, record}\n end\n end\n end\n\n defp is_same_day?(datetime) do\n datetime |> Timex.to_date() |> Timex.equal?(Timex.to_date(Timex.now()))\n end\n\n defp is_same_hour?(datetime) do\n {_date, {record_hour, _min, _sec}} = datetime |> Timex.to_erl()\n {_date, {cur_hour, _min, _sec}} = Timex.now() |> Timex.to_erl()\n\n same_hour? = record_hour == cur_hour\n\n is_same_day?(datetime) and same_hour?\n end\n\n # NOTE: the mock_xxx is only use for test\n def mock_throttle_attr(:last_publish_time, %User{id: user_id}, minutes: minutes) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n last_publish_time = Timex.shift(record.last_publish_time, minutes: minutes)\n record |> ORM.update(~m(last_publish_time)a)\n end\n end\n\n def mock_throttle_attr(:hour_count, %User{id: user_id}, count: hour_count) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n record |> ORM.update(~m(hour_count)a)\n end\n end\n\n def mock_throttle_attr(:publish_hour, %User{id: user_id}, hours: hours) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n publish_hour = Timex.shift(record.publish_hour, hours: hours)\n record |> ORM.update(~m(publish_hour)a)\n end\n end\n\n def mock_throttle_attr(:date_count, %User{id: user_id}, count: date_count) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n record |> ORM.update(~m(date_count)a)\n end\n end\n\n def mock_throttle_attr(:publish_date, %User{id: user_id}, days: days) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n publish_date = Timex.shift(record.publish_hour, days: days)\n record |> ORM.update(~m(publish_date)a)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,250,null,null,null,42,null,null],"name":"lib/groupher_server_web/middleware/authorize.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# ---\ndefmodule GroupherServerWeb.Middleware.Authorize do\n @behaviour Absinthe.Middleware\n import Helper.Utils, only: [handle_absinthe_error: 3]\n import Helper.ErrorCode\n\n def call(%{context: %{cur_user: _}} = resolution, _info), do: resolution\n\n def call(resolution, _) do\n resolution\n |> handle_absinthe_error(\"Authorize: need login\", ecode(:account_login))\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/helper/gql_schema_suite.ex","source":"defmodule Helper.GqlSchemaSuite do\n @moduledoc \"\"\"\n helper for reduce boilerplate import/use/alias in absinthe schema\n \"\"\"\n\n defmacro __using__(_opts) do\n quote do\n use Absinthe.Schema.Notation\n use Absinthe.Ecto, repo: GroupherServer.Repo\n\n alias GroupherServerWeb.Resolvers, as: R\n alias GroupherServerWeb.Middleware, as: M\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,37,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/sys_notification_mail.ex","source":"defmodule GroupherServer.Accounts.SysNotificationMail do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id source_id source_type)a\n @optional_fields ~w(source_preview read)a\n\n @type t :: %SysNotificationMail{}\n schema \"sys_notification_mails\" do\n belongs_to(:user, User)\n\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%SysNotificationMail{} = sys_notication_mail, attrs) do\n sys_notication_mail\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,0,null,null,0,0,null,null,null,0,null,null,null],"name":"lib/groupher_server_web/middleware/github_user.ex","source":"defmodule GroupherServerWeb.Middleware.GithubUser do\n @behaviour Absinthe.Middleware\n\n import Helper.Utils, only: [handle_absinthe_error: 2]\n alias Helper.OAuth2.Github\n\n def call(%{arguments: %{code: code}} = resolution, _) do\n # IO.inspect(access_token, label: \"GithubUser middleware token\")\n\n case Github.user_profile(code) do\n {:ok, user} ->\n # IO.inspect user,label: \"get ok\"\n arguments = resolution.arguments |> Map.merge(%{github_user: user})\n %{resolution | arguments: arguments}\n\n {:error, err_msg} ->\n resolution\n |> handle_absinthe_error(err_msg)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,2,null,null,null,4,null,null,null,2,null,null],"name":"lib/groupher_server_web/middleware/viewer_did_convert.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\n\ndefmodule GroupherServerWeb.Middleware.ViewerDidConvert do\n @behaviour Absinthe.Middleware\n\n def call(%{value: nil} = resolution, _) do\n %{resolution | value: false}\n end\n\n def call(%{value: []} = resolution, _) do\n %{resolution | value: false}\n end\n\n def call(%{value: [_]} = resolution, _) do\n %{resolution | value: true}\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/delivery/delivery_mutations.ex","source":"defmodule GroupherServerWeb.Schema.Delivery.Mutations do\n @moduledoc \"\"\"\n Delivery.Mutations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :delivery_mutations do\n field :mention_someone, :status do\n arg(:user_id, non_null(:id))\n\n arg(:source_id, non_null(:id))\n arg(:source_title, non_null(:string))\n arg(:source_type, non_null(:string))\n arg(:source_preview, non_null(:string))\n arg(:parent_id, :id)\n arg(:parent_type, :string)\n\n middleware(M.Authorize, :login)\n\n resolve(&R.Delivery.mention_someone/3)\n end\n\n field :publish_system_notification, :status do\n arg(:source_id, non_null(:id))\n arg(:source_title, non_null(:string))\n arg(:source_type, non_null(:string))\n arg(:source_preview, :string)\n\n middleware(M.Authorize, :login)\n # TODO: use delivery passport system instead of cms's\n middleware(M.Passport, claim: \"cms->system_notification.publish\")\n\n resolve(&R.Delivery.publish_system_notification/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null,null,5,null,null,null,null,7,null,null,7,null,null,null,1,null,1,1,null,null,null,null,null,null,null,7,7,7,5,5,null,5,null,null,null,null,1,1,null,null,null,null,null,null,1,null,null,null,null,1,null,1,null,null,null,1,null,null,null,null,1,null,1,null,null,null,null,null,null,null,null,null,1,null,null,null,9,9,null,null,null,null,3,3,null,null,null,null,null,null,null,6,6,6,null,6,null,null],"name":"lib/groupher_server/cms/delegates/community_curd.ex","source":"defmodule GroupherServer.CMS.Delegate.CommunityCURD do\n @moduledoc \"\"\"\n community curd\n \"\"\"\n import Ecto.Query, warn: false\n import GroupherServer.CMS.Helper.Matcher\n import Helper.Utils, only: [done: 1, map_atom_value: 2]\n import GroupherServer.CMS.Delegate.ArticleCURD, only: [ensure_author_exists: 1]\n import ShortMaps\n\n alias Helper.ORM\n alias Helper.QueryBuilder\n alias GroupherServer.{Accounts, Repo}\n\n alias GroupherServer.CMS.{\n Category,\n Community,\n CommunityEditor,\n CommunitySubscriber,\n Tag,\n Thread\n }\n\n @doc \"\"\"\n return paged community subscribers\n \"\"\"\n def community_members(:editors, %Community{id: id}, filters) do\n load_community_members(id, CommunityEditor, filters)\n end\n\n def community_members(:subscribers, %Community{id: id}, filters) do\n load_community_members(id, CommunitySubscriber, filters)\n end\n\n defp load_community_members(id, model, %{page: page, size: size} = filters) do\n model\n |> where([c], c.community_id == ^id)\n |> QueryBuilder.load_inner_users(filters)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n def update_editor(%Community{id: community_id}, title, %Accounts.User{id: user_id}) do\n clauses = ~m(user_id community_id)a\n\n with {:ok, _} <- CommunityEditor |> ORM.update_by(clauses, ~m(title)a) do\n Accounts.User |> ORM.find(user_id)\n end\n end\n\n @doc \"\"\"\n create a Tag base on type: post / tuts / videos ...\n \"\"\"\n def create_tag(thread, attrs, %Accounts.User{id: user_id}) when valid_thread(thread) do\n with {:ok, action} <- match_action(thread, :article_tag),\n {:ok, author} <- ensure_author_exists(%Accounts.User{id: user_id}),\n {:ok, _community} <- ORM.find(Community, attrs.community_id) do\n attrs = attrs |> Map.merge(%{author_id: author.id})\n attrs = attrs |> map_atom_value(:string)\n\n action.reactor |> ORM.create(attrs)\n end\n end\n\n def update_tag(%{id: _id} = attrs) do\n attrs = attrs |> map_atom_value(:string)\n Tag |> ORM.find_update(%{id: attrs.id, title: attrs.title, color: attrs.color})\n end\n\n @doc \"\"\"\n get tags belongs to a community / thread\n \"\"\"\n def get_tags(%Community{id: community_id}, thread) when not is_nil(community_id) do\n thread = to_string(thread)\n\n Tag\n |> join(:inner, [t], c in assoc(t, :community))\n |> where([t, c], c.id == ^community_id and t.thread == ^thread)\n |> distinct([t], t.title)\n |> Repo.all()\n |> done()\n end\n\n def get_tags(%Community{raw: community_raw}, thread) when not is_nil(community_raw) do\n thread = to_string(thread)\n\n Tag\n |> join(:inner, [t], c in assoc(t, :community))\n |> where([t, c], c.raw == ^community_raw and t.thread == ^thread)\n |> distinct([t], t.title)\n |> Repo.all()\n |> done()\n end\n\n @doc \"\"\"\n get all paged tags\n \"\"\"\n def get_tags(%{page: page, size: size} = filter) do\n Tag\n |> QueryBuilder.filter_pack(filter)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n def create_category(%Category{title: title, raw: raw}, %Accounts.User{id: user_id}) do\n with {:ok, author} <- ensure_author_exists(%Accounts.User{id: user_id}) do\n Category |> ORM.create(%{title: title, raw: raw, author_id: author.id})\n end\n end\n\n def update_category(~m(%Category id title)a) do\n with {:ok, category} <- ORM.find(Category, id) do\n category |> ORM.update(~m(title)a)\n end\n end\n\n @doc \"\"\"\n TODO: create_thread\n \"\"\"\n def create_thread(attrs) do\n raw = to_string(attrs.raw)\n title = attrs.title\n index = attrs |> Map.get(:index, 0)\n\n Thread |> ORM.create(~m(title raw index)a)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/statistics/statistics_mutations.ex","source":"defmodule GroupherServerWeb.Schema.Statistics.Mutations do\n @moduledoc \"\"\"\n Statistics mutations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :statistics_mutations do\n field :make_contrubute, :user_contribute do\n arg(:user_id, non_null(:id))\n\n resolve(&R.Statistics.make_contrubute/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,254,null,null,null,148,148,148,null,148,null,null,null,38,null,38,null,null,null,59,null,59,null,null,null,61,61,null,61,null,61,null,61,null,null,null,6,null,6,null,6,null,null,null,312,312,null,312,null,null,null],"name":"test/support/conn_simulator.ex","source":"defmodule GroupherServer.Test.ConnSimulator do\n @moduledoc \"\"\"\n mock user_conn, owner_conn, guest_conn\n \"\"\"\n import GroupherServer.Factory\n import Phoenix.ConnTest, only: [build_conn: 0]\n import Plug.Conn, only: [put_req_header: 3]\n\n alias GroupherServer.{Accounts, CMS}\n alias Helper.{Guardian, ORM}\n\n def simu_conn(:guest) do\n build_conn()\n end\n\n def simu_conn(:user) do\n user_attr = mock_attrs(:user)\n {:ok, user} = db_insert(:user, user_attr)\n token = gen_jwt_token(id: user.id)\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n def simu_conn(:owner, content) do\n token = gen_jwt_token(id: content.author.user.id)\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n def simu_conn(:user, %Accounts.User{} = user) do\n token = gen_jwt_token(id: user.id)\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n def simu_conn(:user, cms: passport_rules) do\n user_attr = mock_attrs(:user)\n {:ok, user} = db_insert(:user, user_attr)\n\n token = gen_jwt_token(id: user.id)\n\n {:ok, _passport} = CMS.stamp_passport(passport_rules, %Accounts.User{id: user.id})\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n def simu_conn(:user, %Accounts.User{} = user, cms: passport_rules) do\n token = gen_jwt_token(id: user.id)\n\n {:ok, _passport} = CMS.stamp_passport(passport_rules, %Accounts.User{id: user.id})\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n defp gen_jwt_token(clauses) do\n with {:ok, user} <- ORM.find_by(Accounts.User, clauses) do\n {:ok, token, _info} = Guardian.jwt_encode(user)\n\n \"Bearer #{token}\"\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,911,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,9,null,null,null,null,null],"name":"lib/groupher_server/cms/video.ex","source":"defmodule GroupherServer.CMS.Video do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, Tag}\n\n @required_fields ~w(title poster desc duration duration_sec source)a\n @optional_fields ~w(link original_author original_author_link publish_at pin markDelete)\n\n @type t :: %Video{}\n schema \"cms_videos\" do\n field(:title, :string)\n field(:poster, :string)\n field(:desc, :string)\n field(:duration, :string)\n field(:duration_sec, :integer)\n\n field(:source, :string)\n field(:link, :string)\n\n field(:original_author, :string)\n field(:original_author_link, :string)\n\n field(:views, :integer, default: 0)\n field(:pin, :boolean, default_value: false)\n field(:markDelete, :boolean, default_value: false)\n\n field(:publish_at, :utc_datetime)\n\n belongs_to(:author, Author)\n\n # has_many(:comments, {\"posts_comments\", PostComment})\n\n many_to_many(\n :tags,\n Tag,\n join_through: \"videos_tags\",\n join_keys: [video_id: :id, tag_id: :id],\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_videos\",\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Video{} = video, attrs) do\n video\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n\n # |> foreign_key_constraint(:posts_tags, name: :posts_tags_tag_id_fkey)\n # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,0,null,null,null,0,null,0,null],"name":"lib/groupher_server_web/middleware/put_root_source.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.PutRootSource do\n @behaviour Absinthe.Middleware\n\n # def call(%{source: %{id: id}} = resolution, _) do\n # arguments = resolution.arguments |> Map.merge(%{root_source_id: id})\n\n # %{resolution | arguments: arguments}\n # end\n\n def call(%{source: %{id: id}} = resolution, _) do\n arguments = resolution.arguments |> Map.merge(%{jj: id})\n\n %{resolution | arguments: arguments}\n # resolution\n end\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(resolution, _), do: resolution\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,911,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,9,null,null,null,null,null],"name":"lib/groupher_server/cms/repo.ex","source":"defmodule GroupherServer.CMS.Repo do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, RepoBuilder, Tag}\n\n @required_fields ~w(repo_name desc readme language producer producer_link repo_link repo_star_count repo_fork_count repo_watch_count)a\n @optional_fields ~w(views pin markDelete last_fetch_time)\n\n @type t :: %Repo{}\n schema \"cms_repos\" do\n field(:repo_name, :string)\n field(:desc, :string)\n field(:readme, :string)\n field(:language, :string)\n belongs_to(:author, Author)\n\n field(:repo_link, :string)\n field(:producer, :string)\n field(:producer_link, :string)\n\n field(:repo_star_count, :integer)\n field(:repo_fork_count, :integer)\n field(:repo_watch_count, :integer)\n\n field(:views, :integer, default: 0)\n field(:pin, :boolean, default_value: false)\n field(:markDelete, :boolean, default_value: false)\n\n field(:last_fetch_time, :utc_datetime)\n # TODO: replace RepoBuilder with paged user map\n has_many(:builders, {\"repos_builders\", RepoBuilder})\n\n many_to_many(\n :tags,\n Tag,\n join_through: \"repos_tags\",\n join_keys: [repo_id: :id, tag_id: :id],\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_repos\",\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Repo{} = repo, attrs) do\n repo\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n\n # |> foreign_key_constraint(:posts_tags, name: :posts_tags_tag_id_fkey)\n # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,693,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,432,null,null],"name":"lib/groupher_server/accounts/achievement.ex","source":"defmodule GroupherServer.Accounts.Achievement do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id)a\n @optional_fields ~w(contents_stared_count contents_favorited_count contents_watched_count followers_count reputation)a\n\n @type t :: %Achievement{}\n schema \"user_achievements\" do\n belongs_to(:user, User)\n\n field(:contents_stared_count, :integer, default: 0)\n field(:contents_favorited_count, :integer, default: 0)\n field(:contents_watched_count, :integer, default: 0)\n field(:followers_count, :integer, default: 0)\n field(:reputation, :integer, default: 0)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Achievement{} = achievement, attrs) do\n achievement\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/repo.ex","source":"defmodule GroupherServer.Repo do\n import Helper.Utils, only: [get_config: 2]\n\n use Ecto.Repo, otp_app: :groupher_server\n use Scrivener, page_size: get_config(:general, :page_size)\n\n @dialyzer {:nowarn_function, rollback: 1}\n\n @doc \"\"\"\n Dynamically loads the repository url from the\n DATABASE_URL environment variable.\n \"\"\"\n def init(_, opts) do\n {:ok, Keyword.put(opts, :url, System.get_env(\"DATABASE_URL\"))}\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,18,null,null,null,null,null,null,null,null,null,null,null,null,null,6,null,null,null,null],"name":"lib/groupher_server/statistics/community_contribute.ex","source":"defmodule GroupherServer.Statistics.CommunityContribute do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS\n\n @type t :: %CommunityContribute{}\n schema \"community_contributes\" do\n field(:count, :integer)\n field(:date, :date)\n # field(:community_id, :id)\n belongs_to(:community, CMS.Community)\n\n timestamps()\n end\n\n @doc false\n def changeset(%CommunityContribute{} = community_contribute, attrs) do\n community_contribute\n |> cast(attrs, [:date, :count, :community_id])\n |> validate_required([:date, :count, :community_id])\n |> foreign_key_constraint(:community_id)\n\n # |> unique_constraint(:community_id, name: :communities_threads_community_id_thread_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,33,null,null,null,null,null,null,null,null,null,null,null,null,null,14,null,null],"name":"lib/groupher_server/cms/post_comment_dislike.ex","source":"defmodule GroupherServer.CMS.PostCommentDislike do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.PostComment\n\n @required_fields ~w(post_comment_id user_id)a\n\n @type t :: %PostCommentDislike{}\n schema \"posts_comments_dislikes\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:post_comment, PostComment, foreign_key: :post_comment_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostCommentDislike{} = post_comment_dislike, attrs) do\n post_comment_dislike\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:post_comment_id)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user_id, name: :posts_comments_dislikes_user_id_post_comment_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,4,4,4,null,4,4,4,null,4,null,4,null,null,null,3,null,2,2,null,null,null,null,null,26,null,26,null,null,26,null,null,null,26,26,null,null,null,60,60,null,60,null,null,null,8,null,null,8,null,8,null,8,null,null,null,60,null,60,null,60,null,60,null,60,null,60,null,null,null,60,null,60,null,null,null,null,null,null,null,10,null,null,null,9,null,null,null,7,null,null,null,26,26,26,null,26,null,null,null,null,7,null,7,null,null,7,null,null,null,null,null,null,null,19,19,null,19,null,null,null,1,null,null,null,18,null,null,19,null,null,null,35,35,null,null,null,33,33,null,null,null,26,26,null,null,null,null,null,null,null,68,null,null,null,null,null,null,94,null,94,null,null,41,null,null,null,null,null,null,16,16,null,null,null,null,null,null,null,null,null,null,null,null,null,19,19,null,19,19,null,null,19,19,null,null,null,4,null,4,null,4,4,null,null,null,null,null,null,null,null,null,null,null,41,null,null],"name":"lib/groupher_server/delivery/delegates/utils.ex","source":"defmodule GroupherServer.Delivery.Delegate.Utils do\n @moduledoc \"\"\"\n The Delivery context.\n \"\"\"\n # commons\n import Ecto.Query, warn: false\n import Helper.Utils\n import ShortMaps\n\n alias GroupherServer.Repo\n\n alias GroupherServer.Delivery.{Notification, SysNotification, Mention, Record}\n alias GroupherServer.Accounts.User\n alias Helper.ORM\n\n def mailbox_status(%User{} = user) do\n filter = %{page: 1, size: 1, read: false}\n {:ok, mention_mail} = fetch_mails(user, Mention, filter)\n {:ok, notification_mail} = fetch_mails(user, Notification, filter)\n\n mention_count = mention_mail.total_count\n notification_count = notification_mail.total_count\n total_count = mention_count + notification_count\n\n has_mail = total_count > 0\n\n result = ~m(has_mail total_count mention_count notification_count)a\n {:ok, result}\n end\n\n def fetch_record(%User{id: user_id}), do: Record |> ORM.find_by(user_id: user_id)\n\n def mark_read_all(%User{} = user, :mention), do: Mention |> do_mark_read_all(user)\n def mark_read_all(%User{} = user, :notification), do: Notification |> do_mark_read_all(user)\n\n @doc \"\"\"\n fetch mentions / notifications\n \"\"\"\n def fetch_messages(:sys_notification, %User{} = user, %{page: page, size: size}) do\n {:ok, last_fetch_time} = get_last_fetch_time(SysNotification, user)\n\n mails =\n SysNotification\n |> order_by(desc: :inserted_at)\n |> where([m], m.inserted_at > ^last_fetch_time)\n |> ORM.paginater(~m(page size)a)\n |> done()\n\n record_operation(user, SysNotification, mails)\n mails\n end\n\n def fetch_messages(%User{} = user, queryable, %{page: _page, size: _size, read: read} = filter) do\n mails = fetch_mails_and_delete(user, queryable, filter)\n record_operation(queryable, read, mails)\n\n mails\n end\n\n defp fetch_mails(user, queryable, %{page: page, size: size, read: read}) do\n {:ok, last_fetch_time} = get_last_fetch_time(queryable, read, user)\n\n queryable\n |> where([m], m.to_user_id == ^user.id)\n |> where([m], m.inserted_at > ^last_fetch_time)\n |> where([m], m.read == ^read)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n defp fetch_mails_and_delete(user, queryable, %{page: page, size: size, read: read}) do\n {:ok, last_fetch_time} = get_last_fetch_time(queryable, read, user)\n\n query =\n queryable\n |> where([m], m.to_user_id == ^user.id)\n |> where([m], m.inserted_at > ^last_fetch_time)\n |> where([m], m.read == ^read)\n\n mails =\n query\n |> order_by(desc: :inserted_at)\n |> ORM.paginater(~m(page size)a)\n |> done()\n\n delete_items(query, mails)\n\n mails\n end\n\n defp record_operation(Mention, _read, {:ok, %{entries: []}}), do: {:ok, \"\"}\n defp record_operation(Notification, _read, {:ok, %{entries: []}}), do: {:ok, \"\"}\n defp record_operation(_, SysNotification, {:ok, %{entries: []}}), do: {:ok, \"\"}\n\n defp record_operation(Mention, read, {:ok, %{entries: entries}}) do\n do_record_operation(:mentions_record, read, {:ok, %{entries: entries}})\n end\n\n defp record_operation(Notification, read, {:ok, %{entries: entries}}) do\n do_record_operation(:notifications_record, read, {:ok, %{entries: entries}})\n end\n\n defp record_operation(%User{} = user, SysNotification, {:ok, %{entries: entries}}) do\n do_record_operation(user, :sys_notifications_record, {:ok, %{entries: entries}})\n end\n\n defp get_record_lasttime(entries) do\n first_insert = entries |> List.first() |> Map.get(:inserted_at)\n last_insert = entries |> List.last() |> Map.get(:inserted_at)\n newest_insert = Enum.max([first_insert, last_insert])\n\n newest_insert |> Timex.to_datetime() |> to_string\n end\n\n # sys_notification\n defp do_record_operation(%User{id: user_id}, record_name, {:ok, %{entries: entries}}) do\n record_last_fetch_time = get_record_lasttime(entries)\n\n attrs =\n %{user_id: user_id} |> Map.put(record_name, %{last_fetch_time: record_last_fetch_time})\n\n Record |> ORM.upsert_by([user_id: user_id], attrs)\n end\n\n # last_fetch_read_time\n # > the last fetch time of mails that is read\n # last_fetch_unread_time\n # > the last fetch time of mails that is read\n defp do_record_operation(record_name, read, {:ok, %{entries: entries}}) do\n record_last_fetch_time = get_record_lasttime(entries)\n user_id = entries |> List.first() |> Map.get(:to_user_id)\n\n attrs =\n case read do\n true ->\n %{user_id: user_id}\n |> Map.put(record_name, %{last_fetch_read_time: record_last_fetch_time})\n\n false ->\n %{user_id: user_id}\n |> Map.put(record_name, %{last_fetch_unread_time: record_last_fetch_time})\n end\n\n Record |> ORM.upsert_by([user_id: user_id], attrs)\n end\n\n defp get_last_fetch_time(Mention, read, user) do\n timekey = get_record_lasttime_key(read)\n do_get_last_fetch_time(:mentions_record, user, timekey)\n end\n\n defp get_last_fetch_time(Notification, read, user) do\n timekey = get_record_lasttime_key(read)\n do_get_last_fetch_time(:notifications_record, user, timekey)\n end\n\n defp get_last_fetch_time(SysNotification, user) do\n timekey = get_record_lasttime_key(:sys_notifications_record)\n do_get_last_fetch_time(:sys_notifications_record, user, timekey)\n end\n\n defp get_record_lasttime_key(:sys_notifications_record) do\n \"last_fetch_time\"\n end\n\n defp get_record_lasttime_key(read) do\n case read do\n true -> \"last_fetch_read_time\"\n false -> \"last_fetch_unread_time\"\n end\n end\n\n defp do_get_last_fetch_time(record_key, %User{id: user_id}, timekey) do\n long_long_ago = Timex.shift(Timex.now(), years: -10)\n\n with {:ok, record} <- Record |> ORM.find_by(user_id: user_id) do\n record\n |> has_valid_value(record_key)\n |> case do\n false ->\n {:ok, long_long_ago}\n\n true ->\n record\n |> Map.get(record_key)\n |> Map.get(timekey, to_string(long_long_ago))\n |> NaiveDateTime.from_iso8601()\n end\n else\n {:error, _} ->\n {:ok, long_long_ago}\n end\n end\n\n defp delete_items(_queryable, {:ok, %{entries: []}}), do: {:ok, \"\"}\n\n defp delete_items(queryable, {:ok, %{entries: entries}}) do\n # delete_all only support queryable and where syntax\n # TODO: move logic to queue job\n\n first_id = entries |> List.first() |> Map.get(:id)\n last_id = entries |> List.last() |> Map.get(:id)\n\n min_id = Enum.min([first_id, last_id])\n max_id = Enum.max([first_id, last_id])\n\n queryable\n |> where([m], m.id >= ^min_id and m.id <= ^max_id)\n |> Repo.delete_all()\n end\n\n defp do_mark_read_all(queryable, %User{} = user) do\n query =\n queryable\n |> where([m], m.to_user_id == ^user.id)\n\n try do\n Repo.update_all(\n query,\n set: [read: true]\n )\n\n {:ok, %{status: true}}\n rescue\n _ -> {:error, %{status: false}}\n end\n end\n\n defp has_valid_value(map, key) when is_map(map) do\n Map.has_key?(map, key) and not is_nil(Map.get(map, key))\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,67,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,59,null,null],"name":"lib/groupher_server/cms/community_editor.ex","source":"defmodule GroupherServer.CMS.CommunityEditor do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias Helper.Certification\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Community\n\n @required_fields ~w(user_id community_id title)a\n\n @type t :: %CommunityEditor{}\n\n schema \"communities_editors\" do\n field(:title, :string)\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:community, Community, foreign_key: :community_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%CommunityEditor{} = community_editor, attrs) do\n community_editor\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> validate_inclusion(:title, Certification.editor_titles(:cms))\n |> foreign_key_constraint(:community_id)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user_id, name: :communities_editors_user_id_community_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,2,null,1,1,null,null,null,null,null,null,null,7,3,3,null,null,null,null,3,null,null,null,null,null,null,10,null,null,null,6,3,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/accounts/delegates/customization.ex","source":"defmodule GroupherServer.Accounts.Delegate.Customization do\n import Ecto.Query, warn: false\n\n alias GroupherServer.Accounts\n alias GroupherServer.Accounts.{User, Customization}\n alias Helper.ORM\n # ...\n # TODO: Constants\n\n @doc \"\"\"\n add custom setting to user\n \"\"\"\n # for map_size\n # see https://stackoverflow.com/questions/33248816/pattern-match-function-against-empty-map\n def add_custom_setting(%User{} = _user, map) when map_size(map) == 0 do\n {:error, \"AccountCustomization: invalid option or not purchased\"}\n end\n\n def add_custom_setting(%User{} = user, map) when is_map(map) do\n valid? = map |> Map.keys() |> Enum.all?(&can_set?(user, &1, :boolean))\n\n case valid? do\n true ->\n attrs = Map.merge(%{user_id: user.id}, map)\n Customization |> ORM.upsert_by([user_id: user.id], attrs)\n\n false ->\n {:error, \"AccountCustomization: invalid option or not purchased\"}\n end\n end\n\n def add_custom_setting(%User{} = user, key, value \\\\ true) do\n with {:ok, key} <- can_set?(user, key) do\n attrs = Map.put(%{user_id: user.id}, key, value)\n Customization |> ORM.upsert_by([user_id: user.id], attrs)\n end\n end\n\n defp can_set?(%User{} = user, key, :boolean) do\n case can_set?(%User{} = user, key) do\n {:ok, _} -> true\n {:error, _} -> false\n end\n end\n\n def can_set?(%User{} = user, key) do\n cond do\n key in valid_custom_items(:free) ->\n {:ok, key}\n\n key in valid_custom_items(:advance) ->\n Accounts.has_purchased?(user, key)\n\n true ->\n {:error, \"AccountCustomization: invalid option\"}\n end\n end\n\n @doc \"\"\"\n # theme -- user can set a default theme\n # sidebar_layout -- user can arrange subscribed community index\n \"\"\"\n def valid_custom_items(:free) do\n [:theme, :sidebar_layout]\n end\n\n @doc \"\"\"\n # :brainwash_free -- ads free\n # ::community_chart -- user can access comunity charts\n \"\"\"\n def valid_custom_items(:advance) do\n # NOTE: :brainwash_free aka. \"ads_free\"\n # use brainwash to avoid brower-block-plugins\n [:brainwash_free, :community_chart]\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,7,null,0,null,null,4,null,4,4,null,null,0,0,null,null,4,null,null],"name":"lib/groupher_server_web/middleware/statistics/make_contribute.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.Statistics.MakeContribute do\n @behaviour Absinthe.Middleware\n # google: must appear in the GROUP BY clause or be used in an aggregate function\n alias GroupherServer.Statistics\n alias GroupherServer.CMS.Community\n alias GroupherServer.Accounts.User\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(%{value: nil, errors: _} = resolution, _), do: resolution\n\n def call(%{value: value, context: %{cur_user: cur_user}} = resolution, for: threads) do\n case is_list(threads) do\n true ->\n if :user in threads, do: Statistics.make_contribute(%User{id: cur_user.id})\n if :community in threads, do: Statistics.make_contribute(%Community{id: value.id})\n\n false ->\n if :user == threads, do: Statistics.make_contribute(%User{id: cur_user.id})\n if :community == threads, do: Statistics.make_contribute(%Community{id: value.id})\n end\n\n resolution\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,172,312,null,312,2,null,310,null,null,null,null,null,null,0,null,null,null,279,null,null,null,null,null,null,null,null,139,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,0,null,0,null,null,null,0,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/helper/nested_filter.ex","source":"defmodule Helper.NestedFilter do\n @moduledoc \"\"\"\n Documentation for NestedFilter.\n see: https://github.com/treble37/nested_filter\n \"\"\"\n @type key :: any\n @type val :: any\n @type keys_to_select :: list\n @type predicate :: (key, val -> boolean)\n\n # @spec drop_by(struct, predicate) :: struct\n def drop_by(%_{} = struct, _), do: struct\n\n # @spec drop_by(map, predicate) :: map\n def drop_by(map, predicate) when is_map(map) do\n map\n |> Enum.reduce(%{}, fn {key, val}, acc ->\n cleaned_val = drop_by(val, predicate)\n\n if predicate.(key, cleaned_val) do\n acc\n else\n Map.put(acc, key, cleaned_val)\n end\n end)\n end\n\n # @spec drop_by(list, predicate) :: list\n def drop_by(list, predicate) when is_list(list) do\n Enum.map(list, &drop_by(&1, predicate))\n end\n\n def drop_by(elem, _) do\n elem\n end\n\n @doc \"\"\"\n Take a (nested) map and filter out any keys with specified values in the\n values_to_reject list.\n \"\"\"\n # @spec drop_by_value(%{any => any}, [any]) :: %{any => any}\n def drop_by_value(map, values_to_reject) when is_map(map) do\n drop_by(map, fn _, val -> val in values_to_reject end)\n end\n\n @doc \"\"\"\n Take a (nested) map and filter out any values with specified keys in the\n keys_to_reject list.\n \"\"\"\n # @spec drop_by_key(%{any => any}, [any]) :: %{any => any}\n def drop_by_key(map, keys_to_reject) when is_map(map) do\n drop_by(map, fn key, _ -> key in keys_to_reject end)\n end\n\n # @spec take_by(map, keys_to_select) :: map\n def take_by(map, keys_to_select) when is_map(map) do\n map\n |> Enum.reduce(%{}, fn {_key, val}, acc ->\n Map.merge(acc, take_by(val, keys_to_select))\n end)\n |> Map.merge(Map.take(map, keys_to_select))\n end\n\n def take_by(_elem, _) do\n %{}\n end\n\n @doc \"\"\"\n Take a (nested) map and keep any values with specified keys in the\n keys_to_select list.\n \"\"\"\n # @spec take_by_key(%{any => any}, [any]) :: %{any => any}\n def take_by_key(map, keys_to_select) when is_map(map) do\n Map.merge(take_by(map, keys_to_select), Map.take(map, keys_to_select))\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,1,1,null,1,null,null,null,1,null,null],"name":"lib/groupher_server_web/resolvers/delivery_resolver.ex","source":"defmodule GroupherServerWeb.Resolvers.Delivery do\n @moduledoc false\n\n alias GroupherServer.Delivery\n alias GroupherServer.Accounts.User\n # alias Helper.ORM\n\n def mention_someone(_root, args, %{context: %{cur_user: cur_user}}) do\n from_user_id = cur_user.id\n to_user_id = args.user_id\n\n Delivery.mention_someone(%User{id: from_user_id}, %User{id: to_user_id}, args)\n end\n\n def publish_system_notification(_root, args, %{context: %{cur_user: _}}) do\n Delivery.publish_system_notification(args)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,96,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,47,null,null],"name":"lib/groupher_server/statistics/publish_throttle.ex","source":"defmodule GroupherServer.Statistics.PublishThrottle do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n @optional_fields ~w(user_id publish_hour publish_date hour_count date_count last_publish_time)a\n @required_fields ~w(user_id)a\n\n @type t :: %PublishThrottle{}\n schema \"publish_throttles\" do\n field(:publish_hour, :utc_datetime)\n field(:publish_date, :date)\n field(:hour_count, :integer)\n field(:date_count, :integer)\n belongs_to(:user, Accounts.User)\n\n field(:last_publish_time, :utc_datetime)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PublishThrottle{} = publish_throttle, attrs) do\n publish_throttle\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user, name: :publish_throttles_user_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,45,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/mention_mail.ex","source":"defmodule GroupherServer.Accounts.MentionMail do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id source_id source_type source_preview)a\n @optional_fields ~w(parent_id parent_type read)a\n\n @type t :: %MentionMail{}\n schema \"mention_mails\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:parent_id, :string)\n field(:parent_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%MentionMail{} = mention, attrs) do\n mention\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,139,139,139,null,null,139,null,null,139,139,null,null,139,null,null,null,139,null,null,null,null,null,null,null,null,139,null,139,null,139,null,null,null,null,null,null,null,null,5,5,5,null,null,5,null,null,5,5,null,null,5,null,null,null,5,null,null,null,null,null,null,null,null,5,5,null,5,null,5,null,null,null,null,5,null,null,null,null,null,3,null,null,null,2,null,null,null,0,null,null,null,0,null,null],"name":"lib/groupher_server/cms/delegates/article_reaction.ex","source":"defmodule GroupherServer.CMS.Delegate.ArticleReaction do\n @moduledoc \"\"\"\n reaction[favorite, star, watch ...] on article [post, job, video...]\n \"\"\"\n import Helper.Utils, only: [done: 1, done: 2]\n import GroupherServer.CMS.Helper.Matcher\n import Ecto.Query, warn: false\n import Helper.ErrorCode\n\n alias Helper.ORM\n alias GroupherServer.{Accounts, Repo}\n\n alias Accounts.User\n alias Ecto.Multi\n\n @doc \"\"\"\n favorite / star / watch CMS contents like post / tuts / video ...\n \"\"\"\n # when valid_reaction(thread, react) do\n def reaction(thread, react, content_id, %User{id: user_id}) do\n with {:ok, action} <- match_action(thread, react),\n {:ok, content} <- ORM.find(action.target, content_id, preload: [author: :user]),\n {:ok, user} <- ORM.find(Accounts.User, user_id) do\n Multi.new()\n |> Multi.run(:create_reaction_record, fn _ ->\n create_reaction_record(action, user, thread, content)\n end)\n |> Multi.run(:add_achievement, fn _ ->\n achiever_id = content.author.user_id\n Accounts.achieve(%User{id: achiever_id}, :add, react)\n end)\n |> Repo.transaction()\n |> reaction_result()\n end\n end\n\n defp reaction_result({:ok, %{create_reaction_record: result}}), do: result |> done()\n\n defp reaction_result({:error, :create_reaction_record, _result, _steps}),\n do: {:error, [message: \"create reaction fails\", code: ecode(:react_fails)]}\n\n defp reaction_result({:error, :add_achievement, _result, _steps}),\n do: {:error, [message: \"achieve fails\", code: ecode(:react_fails)]}\n\n defp create_reaction_record(action, %User{id: user_id}, thread, content) do\n attrs = %{} |> Map.put(\"user_id\", user_id) |> Map.put(\"#{thread}_id\", content.id)\n\n action.reactor\n |> ORM.create(attrs)\n |> done(with: content)\n end\n\n # ------\n @doc \"\"\"\n unfavorite / unstar / unwatch CMS contents like post / tuts / video ...\n \"\"\"\n # when valid_reaction(thread, react) do\n def undo_reaction(thread, react, content_id, %User{id: user_id}) do\n with {:ok, action} <- match_action(thread, react),\n {:ok, content} <- ORM.find(action.target, content_id, preload: [author: :user]),\n {:ok, user} <- ORM.find(Accounts.User, user_id) do\n Multi.new()\n |> Multi.run(:delete_reaction_record, fn _ ->\n delete_reaction_record(action, user, thread, content)\n end)\n |> Multi.run(:minus_achievement, fn _ ->\n achiever_id = content.author.user_id\n Accounts.achieve(%User{id: achiever_id}, :minus, react)\n end)\n |> Repo.transaction()\n |> undo_reaction_result()\n end\n end\n\n defp undo_reaction_result({:ok, %{delete_reaction_record: result}}), do: result |> done()\n\n defp undo_reaction_result({:error, :delete_reaction_record, _result, _steps}),\n do: {:error, [message: \"delete reaction fails\", code: ecode(:react_fails)]}\n\n defp undo_reaction_result({:error, :minus_achievement, _result, _steps}),\n do: {:error, [message: \"achieve fails\", code: ecode(:react_fails)]}\n\n defp delete_reaction_record(action, %User{id: user_id}, thread, content) do\n user_where = dynamic([u], u.user_id == ^user_id)\n reaction_where = dynamic_reaction_where(thread, content.id, user_where)\n\n query = from(f in action.reactor, where: ^reaction_where)\n\n case Repo.one(query) do\n nil ->\n {:error, \"record not found\"}\n\n record ->\n Repo.delete(record)\n {:ok, content}\n end\n end\n\n defp dynamic_reaction_where(:post, id, user_where) do\n dynamic([p], p.post_id == ^id and ^user_where)\n end\n\n defp dynamic_reaction_where(:job, id, user_where) do\n dynamic([p], p.job_id == ^id and ^user_where)\n end\n\n defp dynamic_reaction_where(:video, id, user_where) do\n dynamic([p], p.video_id == ^id and ^user_where)\n end\n\n defp dynamic_reaction_where(:repo, id, user_where) do\n dynamic([p], p.repo_id == ^id and ^user_where)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,11,null,null,null,null,null,27,null,27,27,null,null,null,0,null,null,null,null,null,null,22,22,22,22,null,null,null,20,null,null,null,2,null,null,null,null,null,0,null,null,null,20,20,null,null,null,null,20,null,null,20,null,20,20,null,null,null,22,null,22,null,7,null,null,15,null,null,null,null,20,null,null,5,null,null,15,null,null,20,null,7,7,null,null,13,null,null,null,null,null,7,null,null,null,15,null,null,null,44,0,44,null,null,null,14,30,null,null,35,null,null],"name":"lib/groupher_server_web/middleware/passport_loader.ex","source":"defmodule GroupherServerWeb.Middleware.PassportLoader do\n @behaviour Absinthe.Middleware\n import GroupherServer.CMS.Helper.Matcher\n import Helper.Utils\n import Helper.ErrorCode\n\n import ShortMaps\n\n alias GroupherServer.CMS\n alias Helper.ORM\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(\n %{context: %{cur_user: _}, arguments: ~m(community_id)a} = resolution,\n source: :community\n ) do\n case ORM.find(CMS.Community, community_id) do\n {:ok, community} ->\n arguments = resolution.arguments |> Map.merge(%{passport_communities: [community]})\n %{resolution | arguments: arguments}\n\n {:error, err_msg} ->\n resolution\n |> handle_absinthe_error(err_msg, ecode(:passport))\n end\n end\n\n # def call(%{context: %{cur_user: cur_user}, arguments: %{id: id}} = resolution, [source: .., base: ..]) do\n # Loader 应该使用 Map 作为参数,以方便模式匹配\n def call(%{context: %{cur_user: _}, arguments: %{id: id}} = resolution, args) do\n with {:ok, thread, react} <- parse_source(args, resolution),\n {:ok, action} <- match_action(thread, react),\n {:ok, preload} <- parse_preload(action, args),\n {:ok, content} <- ORM.find(action.reactor, id, preload: preload) do\n resolution\n |> load_owner_info(react, content)\n |> load_source(content)\n |> load_community_info(content, args)\n else\n {:error, err_msg} ->\n resolution\n |> handle_absinthe_error(err_msg, ecode(:passport))\n end\n end\n\n def call(resolution, _) do\n # TODO communiy in args\n resolution\n end\n\n def load_source(resolution, content) do\n arguments = resolution.arguments |> Map.merge(%{passport_source: content})\n %{resolution | arguments: arguments}\n end\n\n # 取得 content 里面的 conmunities 字段\n def load_community_info(resolution, content, args) do\n communities = content |> Map.get(parse_base(args))\n\n # check if communities is a List\n communities = if is_list(communities), do: communities, else: [communities]\n\n arguments = resolution.arguments |> Map.merge(%{passport_communities: communities})\n %{resolution | arguments: arguments}\n end\n\n defp parse_preload(action, args) do\n {:ok, _, react} = parse_source(args)\n\n case react == :comment do\n true ->\n {:ok, action.preload}\n\n false ->\n {:ok, [action.preload, parse_base(args)]}\n end\n end\n\n def load_owner_info(%{context: %{cur_user: cur_user}} = resolution, react, content) do\n content_author_id =\n cond do\n react == :comment ->\n content.author.id\n\n true ->\n content.author.user_id\n end\n\n case content_author_id == cur_user.id do\n true ->\n arguments = resolution.arguments |> Map.merge(%{passport_is_owner: true})\n %{resolution | arguments: arguments}\n\n _ ->\n resolution\n end\n end\n\n # typical usage is delete_comment, should load conent by thread\n defp parse_source([source: [:arg_thread, react]], %{arguments: %{thread: thread}}) do\n parse_source(source: [thread, react])\n end\n\n defp parse_source(args, _resolution) do\n parse_source(args)\n end\n\n defp parse_source(args) do\n case Keyword.has_key?(args, :source) do\n false -> {:error, \"Invalid.option: #{args}\"}\n true -> args |> Keyword.get(:source) |> match_source\n end\n end\n\n defp match_source([thread, react]), do: {:ok, thread, react}\n defp match_source(thread), do: {:ok, thread, :self}\n\n defp parse_base(args) do\n Keyword.get(args, :base) || :communities\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,1,null,null,null,null,null,null,null,null,null,null,1,1,null,null,null,null,null,0,null,null,null],"name":"lib/groupher_server/application.ex","source":"defmodule GroupherServer.Application do\n use Application\n\n # See https://hexdocs.pm/elixir/Application.html\n # for more information on OTP Applications\n def start(_type, _args) do\n import Supervisor.Spec\n\n # Define workers and child supervisors to be supervised\n children = [\n # Start the Ecto repository\n supervisor(GroupherServer.Repo, []),\n # Start the endpoint when the application starts\n supervisor(GroupherServerWeb.Endpoint, [])\n # Start your own worker by calling: GroupherServer.Worker.start_link(arg1, arg2, arg3)\n # worker(GroupherServer.Worker, [arg1, arg2, arg3]),\n ]\n\n # See https://hexdocs.pm/elixir/Supervisor.html\n # for other strategies and supported options\n opts = [strategy: :one_for_one, name: GroupherServer.Supervisor]\n Supervisor.start_link(children, opts)\n end\n\n # Tell Phoenix to update the endpoint configuration\n # whenever the application is updated.\n def config_change(changed, _new, removed) do\n GroupherServerWeb.Endpoint.config_change(changed, removed)\n :ok\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,119,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,47,null,null],"name":"lib/groupher_server/cms/job_comment.ex","source":"defmodule GroupherServer.CMS.JobComment do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n alias GroupherServer.CMS.{Job, JobCommentReply}\n\n @required_fields ~w(body author_id job_id floor)a\n @optional_fields ~w(reply_id)a\n\n @type t :: %JobComment{}\n schema \"jobs_comments\" do\n field(:body, :string)\n field(:floor, :integer)\n belongs_to(:author, Accounts.User, foreign_key: :author_id)\n belongs_to(:job, Job, foreign_key: :job_id)\n belongs_to(:reply_to, JobComment, foreign_key: :reply_id)\n # belongs_to(:reply_to, JobComment, foreign_key: :job_id)\n has_many(:replies, {\"jobs_comments_replies\", JobCommentReply})\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%JobComment{} = job_comment, attrs) do\n job_comment\n |> cast(attrs, @required_fields ++ @optional_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:job_id)\n |> foreign_key_constraint(:author_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,15,null,null,null,null,null,null,null,null,null,null,null,null,7,null,null],"name":"lib/groupher_server/cms/post_comment_reply.ex","source":"defmodule GroupherServer.CMS.PostCommentReply do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.PostComment\n\n @required_fields ~w(post_comment_id reply_id)a\n\n @type t :: %PostCommentReply{}\n schema \"posts_comments_replies\" do\n belongs_to(:post_comment, PostComment, foreign_key: :post_comment_id)\n belongs_to(:reply, PostComment, foreign_key: :reply_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostCommentReply{} = post_comment_reply, attrs) do\n post_comment_reply\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:post_comment_id)\n |> foreign_key_constraint(:reply_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/community.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Community do\n @moduledoc \"\"\"\n CMS mations for community\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_mutation_community do\n @desc \"create a global community\"\n field :create_community, :community do\n arg(:title, non_null(:string))\n arg(:desc, non_null(:string))\n arg(:raw, non_null(:string))\n arg(:logo, non_null(:string))\n # arg(:category, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->community.create\")\n\n resolve(&R.CMS.create_community/3)\n # middleware(M.Statistics.MakeContribute, for: :user)\n middleware(M.Statistics.MakeContribute, for: [:user, :community])\n end\n\n @desc \"update a community\"\n field :update_community, :community do\n arg(:id, non_null(:id))\n arg(:title, :string)\n arg(:desc, :string)\n arg(:raw, :string)\n arg(:logo, :string)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->community.update\")\n\n resolve(&R.CMS.update_community/3)\n middleware(M.Statistics.MakeContribute, for: [:user, :community])\n end\n\n @desc \"delete a global community\"\n field :delete_community, :community do\n arg(:id, non_null(:id))\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->community.delete\")\n\n resolve(&R.CMS.delete_community/3)\n end\n\n @desc \"create category\"\n field :create_category, :category do\n arg(:title, non_null(:string))\n arg(:raw, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.create\")\n\n resolve(&R.CMS.create_category/3)\n end\n\n @desc \"delete category\"\n field :delete_category, :category do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.delete\")\n\n resolve(&R.CMS.delete_category/3)\n end\n\n @desc \"update category\"\n field :update_category, :category do\n arg(:id, non_null(:id))\n arg(:title, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.update\")\n\n resolve(&R.CMS.update_category/3)\n end\n\n @desc \"create independent thread\"\n field :create_thread, :thread do\n arg(:title, non_null(:string))\n arg(:raw, non_null(:thread))\n arg(:index, :integer, default_value: 0)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->thread.create\")\n\n resolve(&R.CMS.create_thread/3)\n end\n\n @desc \"add a editor for a community\"\n field :set_editor, :user do\n arg(:community_id, non_null(:id))\n arg(:user_id, non_null(:id))\n arg(:title, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->editor.set\")\n\n resolve(&R.CMS.set_editor/3)\n end\n\n @desc \"unset a editor from a community, the user's passport also deleted\"\n field :unset_editor, :user do\n arg(:community_id, non_null(:id))\n arg(:user_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->editor.unset\")\n\n resolve(&R.CMS.unset_editor/3)\n end\n\n # TODO: remove, should remove both editor and cms->passport\n @desc \"update cms editor's title, passport is not effected\"\n field :update_cms_editor, :user do\n arg(:community_id, non_null(:id))\n arg(:user_id, non_null(:id))\n arg(:title, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->editor.update\")\n\n resolve(&R.CMS.update_editor/3)\n end\n\n @desc \"create a tag\"\n field :create_tag, :article_tag do\n arg(:title, non_null(:string))\n arg(:color, non_null(:rainbow_color_enum))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.create\")\n\n resolve(&R.CMS.create_tag/3)\n end\n\n @desc \"update a tag\"\n field :update_tag, :article_tag do\n arg(:id, non_null(:id))\n arg(:title, non_null(:string))\n # arg(:color, non_null(:rainbow_color_enum))\n arg(:color, non_null(:rainbow_color_enum))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.update\")\n\n resolve(&R.CMS.update_tag/3)\n end\n\n @desc \"delete a tag by thread\"\n field :delete_tag, :article_tag do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.delete\")\n\n resolve(&R.CMS.delete_tag/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,6,null,6,null,null,2,null,null,0,null,null],"name":"lib/groupher_server_web/middleware/put_current_user.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.PutCurrentUser do\n @behaviour Absinthe.Middleware\n\n def call(%{context: %{cur_user: cur_user}} = resolution, _) do\n arguments = resolution.arguments |> Map.merge(%{cur_user: cur_user})\n\n %{resolution | arguments: arguments}\n end\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(resolution, _) do\n resolution\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,7,null,7,1,null,null,6,null,null,null,null,12,null,12,2,null,null,10,null,null,null,null,null,null,null,4,null,null,4,null,4,null,null,4,null,null,null,null,null,1,null,null,null,null,null,null,0,null,null,null,1,null,null,1,null,1,null,1,null,null,null,4,4,4,null,4,null,4,null,null,null,null,5,4,null,null,null,null,null,null,null,null,null,null,null,null,0,null,0,null,null,0,0,0,null,0,null,0,0,null,0,null,null,0,null,null,null,null,4,4,null,null,null,null,1,1,null,null,null,null,2,2,null,null,null,3,null,null,null,null,null,null,3,null,null],"name":"lib/groupher_server/statistics/delegates/contribute.ex","source":"defmodule GroupherServer.Statistics.Delegate.Contribute do\n import Ecto.Query, warn: false\n import Helper.Utils\n import ShortMaps\n\n alias GroupherServer.Repo\n alias GroupherServer.Accounts.User\n alias GroupherServer.CMS.Community\n alias GroupherServer.Statistics.{UserContribute, CommunityContribute}\n alias Helper.{ORM, QueryBuilder}\n\n @community_contribute_days get_config(:general, :community_contribute_days)\n @user_contribute_months get_config(:general, :user_contribute_months)\n\n def make_contribute(%Community{id: id}) do\n today = Timex.today() |> Date.to_iso8601()\n\n with {:ok, contribute} <- ORM.find_by(CommunityContribute, community_id: id, date: today) do\n contribute |> inc_contribute_count(:community) |> done\n else\n {:error, _} ->\n CommunityContribute |> ORM.create(%{community_id: id, date: today, count: 1})\n end\n end\n\n def make_contribute(%User{id: id}) do\n today = Timex.today() |> Date.to_iso8601()\n\n with {:ok, contribute} <- ORM.find_by(UserContribute, user_id: id, date: today) do\n contribute |> inc_contribute_count(:user) |> done\n else\n {:error, _} ->\n UserContribute |> ORM.create(%{user_id: id, date: today, count: 1})\n end\n end\n\n @doc \"\"\"\n Returns the list of user_contribute by latest 6 months.\n \"\"\"\n def list_contributes(%User{id: id}) do\n user_id = tobe_integer(id)\n\n \"user_contributes\"\n |> where([c], c.user_id == ^user_id)\n |> QueryBuilder.recent_inserted(months: @user_contribute_months)\n |> select([c], %{date: c.date, count: c.count})\n |> Repo.all()\n |> to_contrubutes_map()\n |> done\n end\n\n def list_contributes(%Community{id: id}) do\n %Community{id: id}\n |> get_contributes()\n |> done\n end\n\n def list_contributes_digest(%Community{id: id}) do\n %Community{id: id}\n |> get_contributes()\n |> to_counts_digest(days: @community_contribute_days)\n |> done\n end\n\n defp get_contributes(%Community{id: id}) do\n community_id = tobe_integer(id)\n\n \"community_contributes\"\n |> where([c], c.community_id == ^community_id)\n |> QueryBuilder.recent_inserted(days: @community_contribute_days)\n |> select([c], %{date: c.date, count: c.count})\n |> Repo.all()\n |> to_contribute_records()\n end\n\n defp to_contrubutes_map(data) do\n end_date = Timex.today()\n start_date = Timex.shift(Timex.today(), months: -6)\n total_count = Enum.reduce(data, 0, &(&1.count + &2))\n\n records = to_contribute_records(data)\n\n ~m(start_date end_date total_count records)a\n end\n\n defp to_contribute_records(data) do\n data\n |> Enum.map(fn %{count: count, date: date} ->\n %{\n date: convert_date(date),\n count: count\n }\n end)\n end\n\n # 返回 count 数组,方便前端绘图\n # example:\n # from: [0,0,0,0,0,0]\n # to: [0,30,3,8,0,0]\n # 如果 7 天都有 count, 不用计算直接 map 返回\n defp to_counts_digest(record, days: count) do\n case length(record) == @community_contribute_days + 1 do\n true ->\n Enum.map(record, & &1.count)\n\n false ->\n today = Timex.today() |> Date.to_erl()\n return_count = abs(count) + 1\n enmpty_tuple = return_count |> repeat(0) |> List.to_tuple()\n\n results =\n Enum.reduce(record, enmpty_tuple, fn record, acc ->\n diff = Timex.diff(Timex.to_date(record.date), today, :days)\n index = diff + abs(count)\n\n put_elem(acc, index, record.count)\n end)\n\n results |> Tuple.to_list()\n end\n end\n\n defp convert_date(date) do\n {:ok, edate} = Date.from_erl(date)\n edate\n end\n\n defp inc_contribute_count(contribute, :community) do\n CommunityContribute\n |> where([c], c.community_id == ^contribute.community_id and c.date == ^contribute.date)\n |> do_inc_count(contribute)\n end\n\n defp inc_contribute_count(contribute, :user) do\n UserContribute\n |> where([c], c.user_id == ^contribute.user_id and c.date == ^contribute.date)\n |> do_inc_count(contribute)\n end\n\n defp do_inc_count(query, contribute, count \\\\ 1) do\n {1, [result]} =\n Repo.update_all(\n query,\n [inc: [count: count]],\n returning: [:count]\n )\n\n put_in(contribute.count, result.count)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,361,null,null,null,null,2,null,null,null,null,null,null,2,null,null,null,null,null,1,null,null,null,null,null,1,null,null,null,2,null,null,null,1,null,null,null,1,null,null,5,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/utils/loader.ex","source":"defmodule GroupherServer.Accounts.Helper.Loader do\n @moduledoc \"\"\"\n dataloader for accounts\n \"\"\"\n import Ecto.Query, warn: false\n\n alias Helper.QueryBuilder\n alias GroupherServer.{Accounts, CMS, Repo}\n\n alias Accounts.{UserFollower, UserFollowing}\n\n def data, do: Dataloader.Ecto.new(Repo, query: &query/2)\n\n def query({\"communities_subscribers\", CMS.CommunitySubscriber}, %{count: _}) do\n CMS.CommunitySubscriber\n |> group_by([f], f.user_id)\n |> select([f], count(f.id))\n end\n\n def query({\"communities_subscribers\", CMS.CommunitySubscriber}, %{filter: filter}) do\n CMS.CommunitySubscriber\n |> QueryBuilder.filter_pack(filter)\n |> join(:inner, [u], c in assoc(u, :community))\n |> select([u, c], c)\n end\n\n def query({\"users_followers\", UserFollower}, %{count: _}) do\n UserFollower\n |> group_by([f], f.user_id)\n |> select([f], count(f.id))\n end\n\n def query({\"users_followings\", UserFollowing}, %{count: _}) do\n UserFollowing\n |> group_by([f], f.user_id)\n |> select([f], count(f.id))\n end\n\n def query({\"users_followers\", UserFollower}, %{viewer_did: _, cur_user: cur_user}) do\n UserFollower |> where([f], f.follower_id == ^cur_user.id)\n end\n\n def query({\"posts_favorites\", CMS.PostFavorite}, %{count: _}) do\n CMS.PostFavorite |> count_cotents\n end\n\n def query({\"jobs_favorites\", CMS.JobFavorite}, %{count: _}) do\n CMS.JobFavorite |> count_cotents\n end\n\n def query(queryable, _args), do: queryable\n\n defp count_cotents(queryable) do\n queryable\n |> group_by([f], f.user_id)\n |> select([f], count(f.id))\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,5,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/cms/job_comment_reply.ex","source":"defmodule GroupherServer.CMS.JobCommentReply do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.JobComment\n\n @required_fields ~w(job_comment_id reply_id)a\n\n @type t :: %JobCommentReply{}\n schema \"jobs_comments_replies\" do\n belongs_to(:job_comment, JobComment, foreign_key: :job_comment_id)\n belongs_to(:reply, JobComment, foreign_key: :reply_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%JobCommentReply{} = job_comment_reply, attrs) do\n job_comment_reply\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:job_comment_id)\n |> foreign_key_constraint(:reply_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,60,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1,null,1,1,null,1,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/helper/certification.ex","source":"defmodule Helper.Certification do\n @moduledoc \"\"\"\n valid editors and passport details\n \"\"\"\n def editor_titles(:cms) do\n [\"chief editor\", \"post editor\"]\n end\n\n def passport_rules(cms: \"chief editor\") do\n %{\n \"post.tag.create\" => true,\n \"post.tag.edit\" => true,\n \"post.article.markDelete\" => true\n }\n end\n\n # a |> Enum.map(fn(x) -> {x, false} end) |> Map.new\n # %{\n # cms: %{\n # system: ..,\n # community: ...,\n # },\n # statistics: %{\n # ....\n # },\n # otherMoudle: %{\n\n # }\n # }\n\n @doc \"\"\"\n 基础权限,社区权限\n \"\"\"\n def all_rules(:cms) do\n %{\n general: [\n \"system_notification.publish\",\n \"stamp_passport\",\n # community\n \"editor.set\",\n \"editor.unset\",\n \"editor.update\",\n \"community.create\",\n \"community.update\",\n \"community.delete\",\n \"category.create\",\n \"category.delete\",\n \"category.update\",\n \"category.set\",\n \"category.unset\",\n \"thread.create\",\n \"post.community.mirror\",\n \"post.community.unmirror\",\n \"job.community.mirror\",\n \"job.community.unmirror\",\n \"post.pin\",\n \"post.undo_pin\",\n \"post.markDelete\",\n \"post.undo_trash\"\n ],\n community: [\n # thread\n \"thread.set\",\n \"thread.unset\",\n \"post.edit\",\n \"post.markDelete\",\n \"post.delete\",\n \"job.edit\",\n \"job.markDelete\",\n \"job.delete\",\n # post tag\n \"post.tag.create\",\n \"post.tag.update\",\n \"post.tag.delete\",\n \"post.tag.set\",\n \"post.tag.unset\",\n # job tag\n \"job.tag.create\",\n \"job.tag.update\",\n \"job.tag.delete\",\n \"job.tag.set\",\n \"job.tag.unset\"\n ]\n }\n end\n\n def all_rules(:cms, :stringify) do\n rules = all_rules(:cms)\n\n %{\n general: rules.general |> Enum.map(fn x -> {x, false} end) |> Map.new() |> Jason.encode!(),\n community:\n rules.community |> Enum.map(fn x -> {x, false} end) |> Map.new() |> Jason.encode!()\n }\n end\nend\n\n# 可以编辑某个社区 post 版块的文章, 支持 owner\n# middleware(M.Passport, claim: \"cms->c?->posts.article.edit\")\n# middleware(M.Passport, claim: \"owner;cms->c?->posts.article.edit\")\n\n# 可以添加某个社区 posts 版块的 tag 标签, 同时可支持 owner\n# middleware(M.Passport, claim: \"cms->c?->posts.tag.add\")\n# middleware(M.Passport, claim: \"cms->c?->posts.tag.edit\")\n# middleware(M.Passport, claim: \"cms->c?->posts.tag.delete\")\n# middleware(M.Passport, claim: \"cms->c?->posts.tag.markDelete\")\n# middleware(M.Passport, claim: \"owner;cms->c?->posts.tag.delete\")\n\n# 可以给某个社区 posts 版块的 posts 设置标签(setTag), 同时可支持 owner?\n# middleware(M.Passport, claim: \"c?->posts.tag.set\")\n\n# 可以某个社区的 posts 版块置顶\n# middleware(M.Passport, claim: \"cms->c?->posts.setTop\")\n\n# 可以编辑某个社区所有版块的文章\n# middleware(M.Passport, claim: \"cms->c?->posts.articles.edit\")\n# middleware(M.Passport, claim: \"cms->c?->job.articles.edit\")\n# ....全部显示声明....\n# middleware(M.Passport, claim: \"cms->c?->radar.articles.edit\")\n\n# 可以给某个社区的某个版块添加/删除管理员, 实际上就是在给其他成员分配上面的权限,同时该用户会被添加到相应的管理员中\n# middleware(M.Passport, claim: \"cms->c?->posts.managers.add\")\n# middleware(M.Passport, claim: \"cms->c?->jobs.managers.add\")\n# middleware(M.Passport, claim: \"cms->c?->videos.managers.add\")\n# middleware(M.Passport, claim: \"cms->c?->videos.managers.delete\")\n\n# 可以给社区的版块设置审核后发布\n# middleware(M.Passport, claim: \"cms->c?->settings.posts.needReview\")\n# middleware(M.Passport, claim: \"cms->c?->posts.reviewer\") # 审核员 (一开始没必要加)\n\n# 在某个社区的某个版块屏蔽某个用户\n# middleware(M.Passport, claim: \"cms->c?->viewer->block\")\n\n# 查看某个社区的总访问量\n# middleware(M.Passport, claim: \"statistics->c?->click\")\n# middleware(M.Passport, claim: \"logs->c?->posts ...\")\n\n# defguard the_fuck(value) when String.contains?(value, \"->?\")\n# classify the require of this gateway"},{"coverage":[null,null,null,null,null,null,null,null,null,null,2,null,null,30,30,30,null,null,null,null,28,28,28,null,null,null,null,25,null,25,25,null,25,null,null,null,25,25,null,25,null,null,null,null,null,null,null,null,null,58,null,null,null,null,58,null,58,null,null,null,2,null,null,null,2,null,null,null,2,2,null,null,null,null,2,null,null,null,2,null,null,null,4,4,null,null,null,null,4,null,4,null,4,null,4,null,null,null,null,null,9,8,null,null,25,null,null,null,null,17,null,null,null,null,null,null,17,null,null,null,null,null,null,47,null,null,null,null,null],"name":"lib/groupher_server/accounts/delegates/mails.ex","source":"defmodule GroupherServer.Accounts.Delegate.Mails do\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1, done: 2]\n import ShortMaps\n\n alias GroupherServer.Repo\n alias GroupherServer.Accounts.{User, MentionMail, NotificationMail, SysNotificationMail}\n alias GroupherServer.Delivery\n alias Helper.ORM\n\n def mailbox_status(%User{} = user), do: Delivery.mailbox_status(user)\n\n def fetch_mentions(%User{} = user, filter) do\n with {:ok, mentions} <- Delivery.fetch_mentions(user, filter),\n {:ok, washed_mentions} <- wash_data(MentionMail, mentions.entries) do\n MentionMail |> messages_handler(washed_mentions, user, filter)\n end\n end\n\n def fetch_notifications(%User{} = user, filter) do\n with {:ok, notifications} <- Delivery.fetch_notifications(user, filter),\n {:ok, washed_notifications} <- wash_data(NotificationMail, notifications.entries) do\n NotificationMail |> messages_handler(washed_notifications, user, filter)\n end\n end\n\n def fetch_sys_notifications(%User{} = user, %{page: page, size: size, read: read}) do\n with {:ok, sys_notifications} <-\n Delivery.fetch_sys_notifications(user, %{page: page, size: size}),\n {:ok, washed_notifications} <-\n wash_data(SysNotificationMail, user, sys_notifications.entries) do\n SysNotificationMail\n |> Repo.insert_all(washed_notifications)\n\n SysNotificationMail\n |> order_by(desc: :inserted_at)\n |> where([m], m.user_id == ^user.id)\n |> where([m], m.read == ^read)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n end\n\n defp messages_handler(queryable, washed_data, %User{id: user_id}, %{\n page: page,\n size: size,\n read: read\n }) do\n queryable\n |> Repo.insert_all(washed_data)\n\n queryable\n |> order_by(desc: :inserted_at)\n |> where([m], m.to_user_id == ^user_id)\n |> where([m], m.read == ^read)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n def mark_mail_read(%MentionMail{id: id}, %User{} = user) do\n do_mark_mail_read(MentionMail, id, user)\n end\n\n def mark_mail_read(%NotificationMail{id: id}, %User{} = user) do\n do_mark_mail_read(NotificationMail, id, user)\n end\n\n def mark_mail_read(%SysNotificationMail{id: id}, %User{} = user) do\n with {:ok, mail} <- SysNotificationMail |> ORM.find_by(id: id, user_id: user.id) do\n mail |> ORM.update(%{read: true}) |> done(:status)\n end\n end\n\n def mark_mail_read_all(%User{} = user, :mention) do\n user |> do_mark_mail_read_all(MentionMail, :mention)\n end\n\n def mark_mail_read_all(%User{} = user, :notification) do\n user |> do_mark_mail_read_all(NotificationMail, :notification)\n end\n\n defp do_mark_mail_read(queryable, id, %User{} = user) do\n with {:ok, mail} <- queryable |> ORM.find_by(id: id, to_user_id: user.id) do\n mail |> ORM.update(%{read: true}) |> done(:status)\n end\n end\n\n defp do_mark_mail_read_all(%User{} = user, mail, atom) do\n query =\n mail\n |> where([m], m.to_user_id == ^user.id)\n\n Repo.update_all(query, set: [read: true])\n\n Delivery.mark_read_all(user, atom)\n end\n\n defp wash_data(MentionMail, []), do: {:ok, []}\n defp wash_data(NotificationMail, []), do: {:ok, []}\n\n defp wash_data(MentionMail, list), do: do_wash_data(list)\n defp wash_data(NotificationMail, list), do: do_wash_data(list)\n\n defp wash_data(SysNotificationMail, user, list) do\n convert =\n list\n |> Enum.map(\n &(Map.from_struct(&1)\n |> Map.delete(:__meta__)\n |> Map.put(:user_id, user.id))\n )\n\n {:ok, convert}\n end\n\n defp do_wash_data(list) do\n convert =\n list\n |> Enum.map(\n &(Map.from_struct(&1)\n |> Map.delete(:__meta__)\n |> Map.delete(:id)\n |> Map.delete(:from_user)\n |> Map.delete(:to_user))\n )\n\n {:ok, convert}\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,19,5,0,null,26,17,12,null],"name":"lib/groupher_server/statistics/statistics.ex","source":"defmodule GroupherServer.Statistics do\n @moduledoc \"\"\"\n The Statistics context.\n \"\"\"\n\n alias GroupherServer.Statistics.Delegate.{\n Contribute,\n Throttle\n }\n\n defdelegate make_contribute(info), to: Contribute\n defdelegate list_contributes(info), to: Contribute\n defdelegate list_contributes_digest(community), to: Contribute\n\n defdelegate log_publish_action(user), to: Throttle\n defdelegate load_throttle_record(user), to: Throttle\n defdelegate mock_throttle_attr(scope, user, opt), to: Throttle\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,315,null,null,null,null,null,null,null,315,null,null,null,null,204,null,null],"name":"lib/helper/guardian.ex","source":"defmodule Helper.Guardian do\n @moduledoc \"\"\"\n This module defines some helper function used by\n encode/decode jwt\n \"\"\"\n use Guardian, otp_app: :groupher_server\n\n @token_expireation 24 * 14\n\n def subject_for_token(resource, _claims) do\n {:ok, to_string(resource.id)}\n end\n\n def resource_from_claims(claims) do\n {:ok, %{id: claims[\"sub\"]}}\n end\n\n def jwt_encode(source, args \\\\ %{}) do\n encode_and_sign(source, args, ttl: {@token_expireation, :hour})\n end\n\n # jwt_decode\n def jwt_decode(token) do\n resource_from_token(token)\n end\nend"},{"coverage":[null,null,null,null,null,null,4,null,null,2,26,null,null,29,31,null,null,62,29,null,3,4,null],"name":"lib/groupher_server/delivery/delivery.ex","source":"defmodule GroupherServer.Delivery do\n @moduledoc \"\"\"\n The Delivery context.\n \"\"\"\n alias GroupherServer.Delivery.Delegate.{Mentions, Notifications, Utils}\n\n defdelegate mailbox_status(user), to: Utils\n\n # system_notifications\n defdelegate publish_system_notification(info), to: Notifications\n defdelegate fetch_sys_notifications(user, filter), to: Notifications\n\n # mentions\n defdelegate mention_someone(from_user, to_user, info), to: Mentions\n defdelegate fetch_mentions(user, filter), to: Mentions\n\n # notifications\n defdelegate notify_someone(from_user, to_user, info), to: Notifications\n defdelegate fetch_notifications(user, filter), to: Notifications\n\n defdelegate fetch_record(user), to: Utils\n defdelegate mark_read_all(user, opt), to: Utils\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/comment.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Comment do\n @moduledoc \"\"\"\n CMS mutations for comments\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_comment_mutations do\n @desc \"create a comment\"\n field :create_comment, :comment do\n # TODO use thread and force community pass-in\n arg(:thread, :thread, default_value: :post)\n arg(:id, non_null(:id))\n arg(:body, non_null(:string))\n\n # TDOO: use a comment resolver\n middleware(M.Authorize, :login)\n # TODO: 文章作者可以删除评论,文章可以设置禁止评论\n resolve(&R.CMS.create_comment/3)\n end\n\n field :delete_comment, :comment do\n arg(:thread, :thread, default_value: :post)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n # middleware(M.PassportLoader, source: [:post, :comment])\n middleware(M.PassportLoader, source: [:arg_thread, :comment])\n # TODO: 文章可以设置禁止评论\n # middleware(M.Passport, claim: \"owner;cms->c?->post.comment.delete\")\n middleware(M.Passport, claim: \"owner\")\n # middleware(M.Authorize, :login)\n resolve(&R.CMS.delete_comment/3)\n end\n\n @desc \"reply a exsiting comment\"\n field :reply_comment, :comment do\n arg(:thread, non_null(:thread), default_value: :post)\n arg(:id, non_null(:id))\n arg(:body, non_null(:string))\n\n middleware(M.Authorize, :login)\n\n resolve(&R.CMS.reply_comment/3)\n end\n\n @desc \"like a comment\"\n field :like_comment, :comment do\n arg(:thread, non_null(:cms_comment), default_value: :post_comment)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.like_comment/3)\n end\n\n @desc \"undo like comment\"\n # field :undo_like_comment, :idlike do\n field :undo_like_comment, :comment do\n arg(:thread, non_null(:cms_comment), default_value: :post_comment)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.undo_like_comment/3)\n end\n\n field :dislike_comment, :comment do\n arg(:thread, non_null(:cms_comment), default_value: :post_comment)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.dislike_comment/3)\n end\n\n field :undo_dislike_comment, :comment do\n arg(:thread, non_null(:cms_comment), default_value: :post_comment)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.undo_dislike_comment/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server.ex","source":"defmodule GroupherServer do\n @moduledoc \"\"\"\n GroupherServer keeps the contexts that define your domain\n and business logic.\n\n Contexts are also responsible for managing your data, regardless\n if it comes from the database, an external API or others.\n \"\"\"\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,123,null,null,null,null,null,null,null,null,null,null,null,null,null,26,null,null],"name":"lib/groupher_server/delivery/record.ex","source":"defmodule GroupherServer.Delivery.Record do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id)a\n @optional_fields ~w(mentions_record notifications_record sys_notifications_record)a\n\n @type t :: %Record{}\n schema \"delivery_records\" do\n field(:mentions_record, :map)\n field(:notifications_record, :map)\n field(:sys_notifications_record, :map)\n belongs_to(:user, User)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Record{} = record, attrs) do\n record\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/gettext.ex","source":"defmodule GroupherServerWeb.Gettext do\n @moduledoc \"\"\"\n A module providing Internationalization with a gettext-based API.\n\n By using [Gettext](https://hexdocs.pm/gettext),\n your module gains a set of macros for translations, for example:\n\n import GroupherServerWeb.Gettext\n\n # Simple translation\n gettext \"Here is the string to translate\"\n\n # Plural translation\n ngettext \"Here is the string to translate\",\n \"Here are the strings to translate\",\n 3\n\n # Domain-based translation\n dgettext \"errors\", \"Here is the error message to translate\"\n\n See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.\n \"\"\"\n use Gettext, otp_app: :groupher_server\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server_web/middleware/see_me.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.SeeMe do\n @behaviour Absinthe.Middleware\n\n def call(res, _) do\n # IO.inspect(\"see me\")\n res\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,45,null,null,null,null,null,null,null,null,null,null,null,null,null,19,null,null],"name":"lib/groupher_server/cms/post_comment_like.ex","source":"defmodule GroupherServer.CMS.PostCommentLike do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.PostComment\n\n @required_fields ~w(post_comment_id user_id)a\n\n @type t :: %PostCommentLike{}\n schema \"posts_comments_likes\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:post_comment, PostComment, foreign_key: :post_comment_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostCommentLike{} = post_comment_like, attrs) do\n post_comment_like\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:post_comment_id)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user_id, name: :posts_comments_likes_user_id_post_comment_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/accounts/bill.ex","source":"defmodule GroupherServer.Accounts.Bill do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id source_type source_title price)a\n @optional_fields ~w(source_id)a\n\n @type t :: %Bill{}\n schema \"bills\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:price, :integer)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Bill{} = bill, attrs) do\n bill\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/delivery/delivery_queries.ex","source":"defmodule GroupherServerWeb.Schema.Delivery.Queries do\n @moduledoc \"\"\"\n Delivery.Queries\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :delivery_queries do\n @desc \"get mention list?\"\n field :xxxx_todo, :boolean do\n arg(:id, non_null(:id))\n\n resolve(&R.Delivery.mention_someone/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,null,null,2,null,null,null,null,null,0,null,null,null,null,null,3,null,null,null,null,null,24,24,null,null,null,null,null,null,null,null,null,4,null,null,null,null,null,null,4,4,null,null,null,4,null,null,null,null,null,null,1,1,null,null,null,1,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null,44,null,null,372,null,null,123,null,null,8,null,null,1,null,null,1,null,null,null,null,7,null,null,null,0,null,null,0,null,null,0,null,null,1,null,null,1,null,null,null,null,null,7,null,null,null,7,null,null,7,null,null,null,7,null,null,7,null,null,null,7,null,null,7,null,null,null,7,null,null,null,91,null,null,null,0,null,null,null,null,null,null,2,null,null,null,null,null,null,7,null,null,null,null,null,null,14,null,null,null,82,null,null,null,44,null,null,298,null,null,null],"name":"lib/helper/query_builder.ex","source":"defmodule Helper.QueryBuilder do\n # alias GroupherServer.Repo\n import Ecto.Query, warn: false\n\n @doc \"\"\"\n handle [3] situation:\n\n 1. basic query with filter\n 2. reaction_user's count\n 3. is viewer reacted?\n\n bewteen [THREAD] and [REACT]\n [THREAD]: cms thread, include: Post, Job, Video, Repo ...\n [REACT]; favorites, stars, watchs ...\n \"\"\"\n def members_pack(queryable, %{filter: filter}) do\n queryable |> load_inner_users(filter)\n end\n\n def members_pack(queryable, %{viewer_did: _, cur_user: cur_user}) do\n queryable |> where([f], f.user_id == ^cur_user.id)\n end\n\n def members_pack(queryable, %{count: _, type: :post}) do\n queryable\n |> group_by([f], f.post_id)\n |> select([f], count(f.id))\n end\n\n def members_pack(queryable, %{count: _, type: :community}) do\n queryable\n |> group_by([f], f.community_id)\n |> select([f], count(f.id))\n end\n\n def load_inner_users(queryable, filter) do\n queryable\n |> join(:inner, [f], u in assoc(f, :user))\n |> select([f, u], u)\n |> filter_pack(filter)\n end\n\n @doc \"\"\"\n load replies of the given comment\n \"\"\"\n def load_inner_replies(queryable, filter) do\n queryable\n |> filter_pack(filter)\n |> join(:inner, [c], r in assoc(c, :reply))\n |> select([c, r], r)\n end\n\n @doc \"\"\"\n inserted in latest x mounth\n \"\"\"\n def recent_inserted(queryable, months: count) do\n end_of_today = Timex.now() |> Timex.end_of_day()\n x_months_ago = Timex.today() |> Timex.shift(months: -count) |> Timex.to_datetime()\n\n queryable\n |> where([q], q.inserted_at >= ^x_months_ago)\n |> where([q], q.inserted_at <= ^end_of_today)\n end\n\n @doc \"\"\"\n inserted in latest x days\n \"\"\"\n def recent_inserted(queryable, days: count) do\n end_of_today = Timex.now() |> Timex.end_of_day()\n x_days_ago = Timex.today() |> Timex.shift(days: -count) |> Timex.to_datetime()\n\n queryable\n |> where([q], q.inserted_at >= ^x_days_ago)\n |> where([q], q.inserted_at <= ^end_of_today)\n end\n\n # this is strategy will cause\n # defp sort_strategy(:desc_inserted), do: [desc: :inserted_at, desc: :views]\n # defp sort_strategy(:most_views), do: [desc: :views, desc: :inserted_at]\n # defp sort_strategy(:least_views), do: [asc: :views, desc: :inserted_at]\n # defp strategy(:most_stars), do: [desc: :views, desc: :inserted_at]\n\n defp sort_by_count(queryable, field, direction) do\n queryable\n |> join(:left, [p], s in assoc(p, ^field))\n |> group_by([p], p.id)\n |> select([p], p)\n |> order_by([_, s], {^direction, fragment(\"count(?)\", s.id)})\n end\n\n def default_article_filters, do: %{pin: false, markDelete: false}\n\n def filter_pack(queryable, filter) when is_map(filter) do\n Enum.reduce(filter, queryable, fn\n {:sort, :desc_inserted}, queryable ->\n # queryable |> order_by(^sort_strategy(:desc_inserted))\n queryable |> order_by(desc: :inserted_at)\n\n {:sort, :asc_inserted}, queryable ->\n queryable |> order_by(asc: :inserted_at)\n\n {:sort, :desc_index}, queryable ->\n queryable |> order_by(desc: :index)\n\n {:sort, :asc_index}, queryable ->\n queryable |> order_by(asc: :index)\n\n {:sort, :most_views}, queryable ->\n # this will cause error in Dialyzer\n # queryable |> order_by(^sort_strategy(:most_views))\n queryable |> order_by(desc: :views, desc: :inserted_at)\n\n {:sort, :least_views}, queryable ->\n # queryable |> order_by(^sort_strategy(:least_views))\n queryable |> order_by(asc: :views, desc: :inserted_at)\n\n {:sort, :most_stars}, queryable ->\n queryable |> sort_by_count(:stars, :desc)\n\n {:sort, :least_stars}, queryable ->\n queryable |> sort_by_count(:stars, :asc)\n\n {:sort, :most_likes}, queryable ->\n queryable |> sort_by_count(:likes, :desc)\n\n {:sort, :most_dislikes}, queryable ->\n queryable |> sort_by_count(:dislikes, :desc)\n\n {:when, :today}, queryable ->\n # date = DateTime.utc_now() |> Timex.to_datetime()\n # use timezone info is server is not in the some timezone\n # Timex.now(\"America/Chicago\")\n date = Timex.now()\n\n queryable\n |> where([p], p.inserted_at >= ^Timex.beginning_of_day(date))\n |> where([p], p.inserted_at <= ^Timex.end_of_day(date))\n\n {:when, :this_week}, queryable ->\n date = Timex.now()\n\n queryable\n |> where([p], p.inserted_at >= ^Timex.beginning_of_week(date))\n |> where([p], p.inserted_at <= ^Timex.end_of_week(date))\n\n {:when, :this_month}, queryable ->\n date = Timex.now()\n\n queryable\n |> where([p], p.inserted_at >= ^Timex.beginning_of_month(date))\n |> where([p], p.inserted_at <= ^Timex.end_of_month(date))\n\n {:when, :this_year}, queryable ->\n date = Timex.now()\n\n queryable\n |> where([p], p.inserted_at >= ^Timex.beginning_of_year(date))\n |> where([p], p.inserted_at <= ^Timex.end_of_year(date))\n\n # TODO: remove\n {_, :all}, queryable ->\n queryable\n\n # TODO: use raw instead title\n {:article_tag, tag_name}, queryable ->\n from(\n q in queryable,\n join: t in assoc(q, :tags),\n where: t.title == ^tag_name\n )\n\n {:category, catetory_raw}, queryable ->\n from(\n q in queryable,\n join: t in assoc(q, :categories),\n where: t.raw == ^catetory_raw\n )\n\n {:community, community_raw}, queryable ->\n from(\n q in queryable,\n join: t in assoc(q, :communities),\n where: t.raw == ^community_raw\n )\n\n {:first, first}, queryable ->\n queryable |> limit(^first)\n\n {:pin, bool}, queryable ->\n queryable\n |> where([p], p.pin == ^bool)\n\n {:markDelete, bool}, queryable ->\n queryable\n |> where([p], p.markDelete == ^bool)\n\n {_, _}, queryable ->\n queryable\n end)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,4092,null,null,null,6600,null,null,null,65366,null,null,null,null,null,null,null,null,null,null,null,361,null,null,null,361,null,null],"name":"lib/groupher_server_web/schema.ex","source":"defmodule GroupherServerWeb.Schema do\n @moduledoc \"\"\"\n scham index\n \"\"\"\n use Absinthe.Schema\n\n alias GroupherServerWeb.Schema.{Account, CMS, Delivery, Statistics, Utils}\n alias GroupherServerWeb.Middleware, as: M\n\n import_types(Absinthe.Type.Custom)\n\n # utils\n import_types(Utils.CommonTypes)\n\n # account\n import_types(Account.Types)\n import_types(Account.Queries)\n import_types(Account.Mutations)\n\n # statistics\n import_types(Statistics.Types)\n import_types(Statistics.Queries)\n import_types(Statistics.Mutations)\n\n # delivery\n import_types(Delivery.Types)\n import_types(Delivery.Queries)\n import_types(Delivery.Mutations)\n\n # cms\n import_types(CMS.Types)\n import_types(CMS.Queries)\n import_types(CMS.Mutations.Community)\n import_types(CMS.Mutations.Operation)\n import_types(CMS.Mutations.Post)\n import_types(CMS.Mutations.Job)\n import_types(CMS.Mutations.Comment)\n\n query do\n import_fields(:account_queries)\n import_fields(:statistics_queries)\n import_fields(:delivery_queries)\n import_fields(:cms_queries)\n end\n\n mutation do\n # account\n import_fields(:account_mutations)\n # statistics\n import_fields(:statistics_mutations)\n # delivery\n import_fields(:delivery_mutations)\n # cms\n import_fields(:cms_mutation_community)\n import_fields(:cms_opertion_mutations)\n import_fields(:cms_post_mutations)\n import_fields(:cms_job_mutations)\n import_fields(:cms_comment_mutations)\n end\n\n def middleware(middleware, _field, %{identifier: :query}) do\n middleware ++ [M.GeneralError]\n end\n\n def middleware(middleware, _field, %{identifier: :mutation}) do\n middleware ++ [M.ChangesetErrors]\n end\n\n def middleware(middleware, _field, _object) do\n [ApolloTracing.Middleware.Tracing, ApolloTracing.Middleware.Caching] ++ middleware\n end\n\n def plugins do\n [Absinthe.Middleware.Dataloader | Absinthe.Plugin.defaults()]\n end\n\n def dataloader do\n alias GroupherServer.{Accounts, CMS}\n\n Dataloader.new()\n |> Dataloader.add_source(Accounts, Accounts.Helper.Loader.data())\n |> Dataloader.add_source(CMS, CMS.Helper.Loader.data())\n end\n\n def context(ctx) do\n ctx |> Map.put(:loader, dataloader())\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/post.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Post do\n @moduledoc \"\"\"\n CMS mutations for post\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_post_mutations do\n @desc \"create a user\"\n field :create_post, :post do\n arg(:title, non_null(:string))\n arg(:body, non_null(:string))\n arg(:digest, non_null(:string))\n arg(:length, non_null(:integer))\n arg(:link_addr, :string)\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PublishThrottle)\n # middleware(M.PublishThrottle, interval: 3, hour_limit: 15, day_limit: 30)\n resolve(&R.CMS.create_article/3)\n end\n\n @desc \"pin a post\"\n field :pin_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->post.pin\")\n resolve(&R.CMS.pin_post/3)\n end\n\n @desc \"unpin a post\"\n field :undo_pin_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->post.undo_pin\")\n resolve(&R.CMS.undo_pin_post/3)\n end\n\n @desc \"markDelete a post, not delete\"\n field :trash_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->post.markDelete\")\n\n resolve(&R.CMS.trash_post/3)\n end\n\n @desc \"markDelete a post, not delete\"\n field :undo_trash_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->post.undo_trash\")\n\n resolve(&R.CMS.undo_trash_post/3)\n end\n\n @desc \"delete a cms/post\"\n # TODO: if post belongs to multi communities, unset instead delete\n field :delete_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :post)\n middleware(M.Passport, claim: \"owner;cms->c?->post.delete\")\n\n resolve(&R.CMS.delete_content/3)\n end\n\n @desc \"update a cms/post\"\n field :update_post, :post do\n arg(:id, non_null(:id))\n arg(:title, :string)\n arg(:body, :string)\n arg(:digest, :string)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :post)\n middleware(M.Passport, claim: \"owner;cms->c?->post.edit\")\n\n resolve(&R.CMS.update_article/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,106,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,12,null,null],"name":"lib/groupher_server/cms/category.ex","source":"defmodule GroupherServer.CMS.Category do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community}\n # alias GroupherServer.Accounts\n # alias Helper.Certification\n\n @required_fields ~w(title raw author_id)a\n\n @type t :: %Category{}\n\n schema \"categories\" do\n field(:title, :string)\n field(:raw, :string)\n belongs_to(:author, Author)\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_categories\",\n join_keys: [category_id: :id, community_id: :id],\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Category{} = category, attrs) do\n category\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n # |> validate_inclusion(:title, Certification.editor_titles(:cms))\n # |> foreign_key_constraint(:community_id)\n # |> foreign_key_constraint(:author_id)\n |> unique_constraint(:title, name: :categories_title_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/job.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Job do\n @moduledoc \"\"\"\n CMS mutations for job\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_job_mutations do\n @desc \"create a user\"\n field :create_job, :job do\n arg(:title, non_null(:string))\n arg(:company, non_null(:string))\n arg(:company_logo, non_null(:string))\n arg(:location, non_null(:string))\n arg(:body, non_null(:string))\n arg(:digest, non_null(:string))\n arg(:length, non_null(:integer))\n arg(:community_id, non_null(:id))\n arg(:link_addr, :string)\n arg(:link_source, :string)\n\n arg(:thread, :thread, default_value: :job)\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.create_article/3)\n end\n\n @desc \"delete a job\"\n field :delete_job, :job do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :job)\n middleware(M.Passport, claim: \"owner;cms->c?->job.delete\")\n\n resolve(&R.CMS.delete_content/3)\n end\n\n @desc \"update a cms/job\"\n field :update_job, :job do\n arg(:id, non_null(:id))\n arg(:title, :string)\n arg(:body, :string)\n arg(:digest, :string)\n # ...\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :job)\n middleware(M.Passport, claim: \"owner;cms->c?->job.edit\")\n\n resolve(&R.CMS.update_article/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/account/account_mutations.ex","source":"defmodule GroupherServerWeb.Schema.Account.Mutations do\n @moduledoc \"\"\"\n accounts mutations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :account_mutations do\n # @desc \"hehehef: create a user\"\n # field :create_user, :user do\n # arg(:username, non_null(:string))\n # arg(:nickname, non_null(:string))\n # arg(:bio, non_null(:string))\n # arg(:company, non_null(:string))\n\n # resolve(&R.Accounts.create_user/3)\n # end\n\n @desc \"update user's profile\"\n field :update_profile, :user do\n arg(:profile, non_null(:user_profile_input))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.update_profile/3)\n end\n\n field :github_signin, :token_info do\n arg(:code, non_null(:string))\n # arg(:profile, non_null(:github_profile_input))\n\n middleware(M.GithubUser)\n resolve(&R.Accounts.github_signin/3)\n end\n\n @doc \"follow a user\"\n field :follow, :user do\n arg(:user_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.follow/3)\n end\n\n @doc \"undo follow to a user\"\n field :undo_follow, :user do\n arg(:user_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.undo_follow/3)\n end\n\n @desc \"mark a mention as read\"\n field :mark_mention_read, :status do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_mention_read/3)\n end\n\n @desc \"mark a all unread mention as read\"\n field :mark_mention_read_all, :status do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_mention_read_all/3)\n end\n\n @desc \"mark a notification as read\"\n field :mark_notification_read, :status do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_notification_read/3)\n end\n\n @desc \"mark a all unread notifications as read\"\n field :mark_notification_read_all, :status do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_notification_read_all/3)\n end\n\n @desc \"mark a system notification as read\"\n field :mark_sys_notification_read, :status do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_sys_notification_read/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/utils/common_types.ex","source":"defmodule GroupherServerWeb.Schema.Helper.Metrics do\n use Absinthe.Schema.Notation\n use Absinthe.Ecto, repo: GroupherServer.Repo\n\n object :status do\n field(:done, :boolean)\n field(:id, :id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,151,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,62,null,null],"name":"lib/groupher_server/delivery/notification.ex","source":"defmodule GroupherServer.Delivery.Notification do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id action source_title source_id source_preview source_type)a\n @optional_fields ~w(parent_id parent_type read)a\n\n @type t :: %Notification{}\n schema \"notifications\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n field(:action, :string)\n\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:parent_id, :string)\n field(:parent_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Notification{} = notification, attrs) do\n notification\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,12,12,12,null,12,null,null,12,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/accounts/delegates/reacted_contents.ex","source":"defmodule GroupherServer.Accounts.Delegate.ReactedContents do\n @moduledoc \"\"\"\n get contents(posts, jobs, videos ...) that user reacted (star, favorite ..)\n \"\"\"\n import GroupherServer.CMS.Helper.Matcher\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1]\n import ShortMaps\n\n alias Helper.{ORM, QueryBuilder}\n alias GroupherServer.Accounts.User\n\n def reacted_contents(thread, react, ~m(page size)a = filter, %User{id: user_id}) do\n with {:ok, action} <- match_action(thread, react) do\n action.reactor\n |> where([f], f.user_id == ^user_id)\n |> join(:inner, [f], p in assoc(f, ^thread))\n |> select([f, p], p)\n |> QueryBuilder.filter_pack(filter)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n end\n\n # def reacted_count(thread, react, %User{id: user_id}) do\n # with {:ok, action} <- match_action(thread, react) do\n # action.reactor\n # |> where([f], f.user_id == ^user_id)\n # |> group_by([f], f.post_id)\n # |> select([f], count(f.id))\n # end\n # end\nend"},{"coverage":[null,null,null,null,null,null,null,null,4,null,4,null,null,null,0,null,null,0,null,null],"name":"lib/groupher_server_web/middleware/force_loader.ex","source":"# this is a tmp solution for load related-users like situations\n# it turn dataloader into nomal N+1 resolver\n# NOTE: it should be replaced using \"Select-Top-N-By-Group\" solution\n\ndefmodule GroupherServerWeb.Middleware.ForceLoader do\n @behaviour Absinthe.Middleware\n\n def call(%{source: %{id: id}} = resolution, _) do\n arguments = resolution.arguments |> Map.merge(%{what_ever: id})\n\n %{resolution | arguments: arguments}\n # resolution\n end\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(resolution, _) do\n resolution\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,475,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,167,null,null],"name":"lib/groupher_server/cms/post_comment.ex","source":"defmodule GroupherServer.CMS.PostComment do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n alias GroupherServer.CMS.{\n Post,\n PostCommentDislike,\n PostCommentLike,\n PostCommentReply\n }\n\n @required_fields ~w(body author_id post_id floor)a\n @optional_fields ~w(reply_id)a\n\n @type t :: %PostComment{}\n schema \"posts_comments\" do\n field(:body, :string)\n field(:floor, :integer)\n belongs_to(:author, Accounts.User, foreign_key: :author_id)\n belongs_to(:post, Post, foreign_key: :post_id)\n belongs_to(:reply_to, PostComment, foreign_key: :reply_id)\n\n has_many(:replies, {\"posts_comments_replies\", PostCommentReply})\n has_many(:likes, {\"posts_comments_likes\", PostCommentLike})\n has_many(:dislikes, {\"posts_comments_dislikes\", PostCommentDislike})\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostComment{} = post_comment, attrs) do\n post_comment\n |> cast(attrs, @required_fields ++ @optional_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:post_id)\n |> foreign_key_constraint(:author_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,96,96,96,null,null,96,null,null,null,null,null,null,null,6,6,6,null,null,6,null,null,null,null,null,null,null,5,5,5,null,null,5,null,null,null,null,null,null,null,4,4,4,null,null,4,null,null,null,null,null,null,null,136,136,136,null,null,136,null,null,null,null,null,null,null,5,5,5,null,5,null,null,5,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,30,null,null,null,null,24,null,null,null],"name":"lib/groupher_server/accounts/delegates/achievements.ex","source":"defmodule GroupherServer.Accounts.Delegate.Achievements do\n @moduledoc \"\"\"\n user achievements related\n acheiveements formula:\n 1. create content been stared by other user + 1\n 2. create content been watched by other user + 1\n 3. create content been favorited by other user + 2\n 4. followed by other user + 3\n \"\"\"\n import Helper.Utils, only: [get_config: 2]\n import ShortMaps\n\n alias Helper.{ORM, SpecType}\n alias GroupherServer.Accounts.{Achievement, User}\n\n @favorite_weight get_config(:general, :user_achieve_favorite_weight)\n @star_weight get_config(:general, :user_achieve_star_weight)\n # @watch_weight get_config(:general, :user_achieve_watch_weight)\n @follow_weight get_config(:general, :user_achieve_follow_weight)\n\n @doc \"\"\"\n add user's achievement by add followers_count of favorite_weight\n \"\"\"\n @spec achieve(User.t(), atom, atom) :: SpecType.done()\n def achieve(%User{id: user_id}, :add, :follow) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n followers_count = achievement.followers_count + @follow_weight\n reputation = achievement.reputation + @follow_weight\n\n achievement\n |> ORM.update(~m(followers_count reputation)a)\n end\n end\n\n @doc \"\"\"\n minus user's achievement by add followers_count of favorite_weight\n \"\"\"\n def achieve(%User{id: user_id}, :minus, :follow) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n followers_count = achievement.followers_count |> safe_minus(@follow_weight)\n reputation = achievement.reputation |> safe_minus(@follow_weight)\n\n achievement\n |> ORM.update(~m(followers_count reputation)a)\n end\n end\n\n @doc \"\"\"\n add user's achievement by contents_stared_count of star_weight\n \"\"\"\n def achieve(%User{id: user_id} = _user, :add, :star) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n contents_stared_count = achievement.contents_stared_count + @star_weight\n reputation = achievement.reputation + @star_weight\n\n achievement\n |> ORM.update(~m(contents_stared_count reputation)a)\n end\n end\n\n @doc \"\"\"\n minus user's achievement by contents_stared_count of star_weight\n \"\"\"\n def achieve(%User{id: user_id} = _user, :minus, :star) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n contents_stared_count = achievement.contents_stared_count |> safe_minus(@star_weight)\n reputation = achievement.reputation |> safe_minus(@star_weight)\n\n achievement\n |> ORM.update(~m(contents_stared_count reputation)a)\n end\n end\n\n @doc \"\"\"\n minus user's achievement by contents_favorited_count of favorite_weight\n \"\"\"\n def achieve(%User{id: user_id} = _user, :add, :favorite) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n contents_favorited_count = achievement.contents_favorited_count + @favorite_weight\n reputation = achievement.reputation + @favorite_weight\n\n achievement\n |> ORM.update(~m(contents_favorited_count reputation)a)\n end\n end\n\n @doc \"\"\"\n add user's achievement by contents_favorited_count of favorite_weight\n \"\"\"\n def achieve(%User{id: user_id} = _user, :minus, :favorite) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n contents_favorited_count =\n achievement.contents_favorited_count |> safe_minus(@favorite_weight)\n\n reputation = achievement.reputation |> safe_minus(@favorite_weight)\n\n achievement\n |> ORM.update(~m(contents_favorited_count reputation)a)\n end\n end\n\n # def achieve(%User{} = _user, :+, :watch) do\n # IO.inspect(\"acheiveements add :conent_watched\")\n # end\n\n # def achieve(%User{} = _user, :+, key) do\n # IO.inspect(\"acheiveements add #{key}\")\n # end\n\n # def achieve(%User{} = _user, :-, _key) do\n # IO.inspect(\"acheiveements plus\")\n # end\n\n @spec safe_minus(non_neg_integer(), non_neg_integer()) :: non_neg_integer()\n defp safe_minus(count, unit) when is_integer(count) and is_integer(unit) and unit > 0 do\n case count <= 0 do\n true ->\n 0\n\n false ->\n count - unit\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/channels/user_socket.ex","source":"defmodule GroupherServerWeb.UserSocket do\n use Phoenix.Socket\n\n ## Channels\n # channel \"room:*\", GroupherServerWeb.RoomChannel\n\n ## Transports\n transport(:websocket, Phoenix.Transports.WebSocket)\n # transport :longpoll, Phoenix.Transports.LongPoll\n\n # Socket params are passed from the client and can\n # be used to verify and authenticate a user. After\n # verification, you can put default assigns into\n # the socket that will be set for all channels, ie\n #\n # {:ok, assign(socket, :user_id, verified_user_id)}\n #\n # To deny connection, return `:error`.\n #\n # See `Phoenix.Token` documentation for examples in\n # performing token verification on connect.\n def connect(_params, socket) do\n {:ok, socket}\n end\n\n # Socket id's are topics that allow you to identify all sockets for a given user:\n #\n # def id(socket), do: \"user_socket:#{socket.assigns.user_id}\"\n #\n # Would allow you to broadcast a \"disconnect\" event and terminate\n # all active sockets and channels for a given user:\n #\n # GroupherServerWeb.Endpoint.broadcast(\"user_socket:#{user.id}\", \"disconnect\", %{})\n #\n # Returning `nil` makes this socket anonymous.\n def id(_socket), do: nil\nend"}]} \ No newline at end of file +{"source_files":[{"coverage":[null,null,null,null,null,null,null,null,null,8,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null,null,null,null],"name":"lib/groupher_server/accounts/github_user.ex","source":"defmodule GroupherServer.Accounts.GithubUser do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @type t :: %GithubUser{}\n schema \"github_users\" do\n belongs_to(:user, User)\n\n field(:github_id, :string)\n field(:login, :string)\n field(:avatar_url, :string)\n field(:url, :string)\n field(:html_url, :string)\n field(:name, :string)\n field(:company, :string)\n field(:blog, :string)\n field(:location, :string)\n field(:email, :string)\n field(:bio, :string)\n field(:public_repos, :integer)\n field(:public_gists, :integer)\n field(:followers, :integer)\n field(:following, :integer)\n field(:access_token, :string)\n field(:node_id, :string)\n\n timestamps(type: :utc_datetime)\n end\n\n # @required_fields ~w(github_id login name avatar_url)a\n @required_fields ~w(github_id login avatar_url user_id access_token node_id)a\n @optional_fields ~w(blog company email bio followers following location html_url public_repos public_gists)a\n\n @doc false\n def changeset(%GithubUser{} = github_user, attrs) do\n # |> cast(attrs, [:username, :nickname, :bio, :company])\n # |> validate_required([:username])\n # |> cast(attrs, @required_fields, @optional_fields)\n github_user\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:github_id)\n |> unique_constraint(:node_id)\n |> foreign_key_constraint(:user_id)\n\n # |> validate_length(:username, max: 20)\n # |> unique_constraint(:username)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,2,null,1,1,null,null,null,null,null,null,null,1,1,1,null,null,null,null,3,1,1,null,null,null,null,null,null,null,null,null,null,4,null,null,null,null,null,null,5,null,5,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/accounts/delegates/billing.ex","source":"defmodule GroupherServer.Accounts.Delegate.Billing do\n @moduledoc \"\"\"\n user billings related\n \"\"\"\n import Ecto.Query, warn: false\n\n alias Helper.ORM\n alias GroupherServer.Accounts.{Purchase, User}\n\n # ...\n def purchase_service(%User{} = _user, map) when map_size(map) == 0 do\n {:error, \"AccountPurchase: invalid option or not purchased\"}\n end\n\n def purchase_service(%User{} = user, map) when is_map(map) do\n valid? = map |> Map.keys() |> Enum.all?(&can_purchase?(user, &1, :boolean))\n\n case valid? do\n true ->\n attrs = Map.merge(%{user_id: user.id}, map)\n Purchase |> ORM.upsert_by([user_id: user.id], attrs)\n\n false ->\n {:error, \"AccountCustomization: invalid option or not purchased\"}\n end\n end\n\n def purchase_service(%User{} = user, key, value \\\\ true) do\n with {:ok, key} <- can_purchase?(user, key) do\n attrs = Map.put(%{user_id: user.id}, key, value)\n Purchase |> ORM.upsert_by([user_id: user.id], attrs)\n end\n end\n\n def has_purchased?(%User{} = user, key) do\n with {:ok, purchase} <- Purchase |> ORM.find_by(user_id: user.id),\n value <- purchase |> Map.get(key) do\n case value do\n true -> {:ok, key}\n false -> {:error, \"AccountPurchase: not purchase\"}\n end\n else\n nil -> {:error, \"AccountPurchase: not purchase\"}\n _ -> {:error, \"AccountPurchase: not purchase\"}\n end\n end\n\n defp can_purchase?(%User{} = user, key, :boolean) do\n case can_purchase?(%User{} = user, key) do\n {:ok, _} -> true\n {:error, _} -> false\n end\n end\n\n defp can_purchase?(%User{} = _user, key) do\n valid_service_options = valid_service()\n\n case key in valid_service_options do\n true -> {:ok, key}\n false -> {:error, \"AccountPurchase: purchase invalid service\"}\n end\n end\n\n defp valid_service do\n [:brainwash_free, :community_chart]\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,29,null,null,null,null,null,null,null,null,null,null,null,null,10,null,null],"name":"lib/groupher_server/statistics/user_contribute.ex","source":"defmodule GroupherServer.Statistics.UserContribute do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n @type t :: %UserContribute{}\n schema \"user_contributes\" do\n field(:count, :integer)\n field(:date, :date)\n belongs_to(:user, Accounts.User)\n\n timestamps()\n end\n\n @doc false\n def changeset(%UserContribute{} = user_contribute, attrs) do\n user_contribute\n |> cast(attrs, [:date, :count, :user_id])\n |> validate_required([:date, :count, :user_id])\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,77,84,null,null,6,null,3,22,2,3,2,0,null,4,4,4,null,null,null],"name":"lib/helper/error_code.ex","source":"defmodule Helper.ErrorCode do\n @moduledoc \"\"\"\n error code map for all site\n \"\"\"\n @default_base 4000\n @account_base 4300\n @changeset_base 4100\n @throttle_base 4200\n\n # account error code\n def ecode(:account_login), do: @account_base + 1\n def ecode(:passport), do: @account_base + 2\n # ...\n # changeset error code\n def ecode(:changeset), do: @changeset_base + 2\n # ...\n def ecode(:custom), do: @default_base + 1\n def ecode(:pagination), do: @default_base + 2\n def ecode(:not_exsit), do: @default_base + 3\n def ecode(:already_did), do: @default_base + 4\n def ecode(:self_conflict), do: @default_base + 5\n def ecode(:react_fails), do: @default_base + 6\n # throttle\n def ecode(:throttle_inverval), do: @throttle_base + 1\n def ecode(:throttle_hour), do: @throttle_base + 2\n def ecode(:throttle_day), do: @throttle_base + 3\n def ecode, do: @default_base\n # def ecode(_), do: @default_base\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,102,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,6,null,null,null,null],"name":"lib/groupher_server/cms/thread.ex","source":"defmodule GroupherServer.CMS.Thread do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n @optional_fields ~w(logo index)a\n @required_fields ~w(title raw)a\n\n @type t :: %Thread{}\n schema \"threads\" do\n field(:title, :string)\n field(:raw, :string)\n field(:logo, :string)\n field(:index, :integer)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Thread{} = thread, attrs) do\n thread\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> validate_length(:title, min: 2, max: 20)\n |> validate_length(:raw, min: 2, max: 20)\n |> unique_constraint(:title)\n\n # |> unique_constraint(:raw)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,29,null,null,29,29,29,29,null,null,null,null,29,null,null,null,null,null,null,31,null,null],"name":"lib/groupher_server/delivery/delegates/mentions.ex","source":"defmodule GroupherServer.Delivery.Delegate.Mentions do\n @moduledoc \"\"\"\n The Delivery context.\n \"\"\"\n import Helper.Utils, only: [done: 2]\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.Delivery.Mention\n alias Helper.ORM\n\n alias GroupherServer.Delivery.Delegate.Utils\n\n def mention_someone(%User{id: from_user_id}, %User{id: to_user_id}, info) do\n attrs = %{\n from_user_id: from_user_id,\n to_user_id: to_user_id,\n source_id: info.source_id,\n source_title: info.source_title,\n source_type: info.source_type,\n source_preview: info.source_preview\n }\n\n Mention\n |> ORM.create(attrs)\n |> done(:status)\n end\n\n @doc \"\"\"\n fetch mentions from Delivery stop\n \"\"\"\n def fetch_mentions(%User{} = user, %{page: _, size: _, read: _} = filter) do\n Utils.fetch_messages(user, Mention, filter)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,0,null,0,0,null,null,null,null,null],"name":"test/support/channel_case.ex","source":"defmodule GroupherServerWeb.ChannelCase do\n @moduledoc \"\"\"\n This module defines the test case to be used by\n channel tests.\n\n Such tests rely on `Phoenix.ChannelTest` and also\n import other functionality to make it easier\n to build common datastructures and query the data layer.\n\n Finally, if the test case interacts with the database,\n it cannot be async. For this reason, every test runs\n inside a transaction which is reset at the beginning\n of the test unless the test case is marked as async.\n \"\"\"\n\n use ExUnit.CaseTemplate\n\n using do\n quote do\n # Import conveniences for testing with channels\n use Phoenix.ChannelTest\n\n # The default endpoint for testing\n @endpoint GroupherServerWeb.Endpoint\n end\n end\n\n setup tags do\n :ok = Ecto.Adapters.SQL.Sandbox.checkout(GroupherServer.Repo)\n\n unless tags[:async] do\n Ecto.Adapters.SQL.Sandbox.mode(GroupherServer.Repo, {:shared, self()})\n end\n\n :ok\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,0,null,null,361,null,null,361,null,null,null,null,null,null,null,null,null,null,361,203,203,null,158,null,null,null,null,203,203,null,203,null,null,null,null,null,null,null,null,null,null,203,null,null,null,null,null,null],"name":"lib/groupher_server_web/context.ex","source":"# a plug for router ...\n\ndefmodule GroupherServerWeb.Context do\n @behaviour Plug\n\n import Plug.Conn\n # import Ecto.Query, only: [first: 1]\n\n alias GroupherServer.{Accounts, CMS}\n alias Helper.{Guardian, ORM}\n\n def init(opts), do: opts\n\n def call(conn, _) do\n context = build_context(conn)\n # put_private(conn, :absinthe, %{context: context})\n # TODO: use https://github.com/absinthe-graphql/absinthe/pull/497/files\n Absinthe.Plug.put_options(conn, context: context)\n end\n\n @doc \"\"\"\n Return the current user context based on the authorization header.\n\n Important: Note that at the current time this is just a stub, always\n returning the first user (marked as an admin), provided any\n authorization header is sent.\n \"\"\"\n def build_context(conn) do\n with [\"Bearer \" <> token] <- get_req_header(conn, \"authorization\"),\n {:ok, cur_user} <- authorize(token) do\n %{cur_user: cur_user}\n else\n _ -> %{}\n end\n end\n\n defp authorize(token) do\n with {:ok, claims, _info} <- Guardian.jwt_decode(token) do\n case ORM.find(Accounts.User, claims.id) do\n {:ok, user} ->\n check_passport(user)\n\n {:error, _} ->\n {:error,\n \"user is not exsit, try revoke token, or if you in dev env run the seeds first.\"}\n end\n end\n end\n\n # TODO gather role info from CMS or other context\n defp check_passport(%Accounts.User{} = user) do\n with {:ok, cms_passport} <- CMS.get_passport(%Accounts.User{id: user.id}) do\n {:ok, Map.put(user, :cur_passport, %{\"cms\" => cms_passport})}\n else\n {:error, _} -> {:ok, user}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1,null,7,null,9,3,null,6,null,7,1,2,1,null,null,null,8,2,null,59,1,null,8,1,null,76,2,null,null,44,30,9,null,null,139,5,null,null,null,28,null,10,1,null,5,1,null,null,197,5,11,3,9,null,null,null,20,2,null,14,2,null,null,80,4,211,3,0,null],"name":"lib/groupher_server/cms/cms.ex","source":"defmodule GroupherServer.CMS do\n @moduledoc \"\"\"\n this module defined basic method to handle [CMS] content [CURD] ..\n [CMS]: post, job, ...\n [CURD]: create, update, delete ...\n \"\"\"\n alias GroupherServer.CMS.Delegate.{\n ArticleCURD,\n ArticleCommunity,\n ArticleReaction,\n CommentCURD,\n CommentReaction,\n CommunityCURD,\n CommunityOperation,\n PassportCURD\n }\n\n # do not pattern match in delegating func, do it on one delegating inside\n # see https://github.com/elixir-lang/elixir/issues/5306\n\n # Community CURD: editors, thread, tag\n # >> editor ..\n defdelegate update_editor(user, community, title), to: CommunityCURD\n # >> subscribers / editors\n defdelegate community_members(type, community, filters), to: CommunityCURD\n # >> category\n defdelegate create_category(category_attrs, user), to: CommunityCURD\n defdelegate update_category(category_attrs), to: CommunityCURD\n # >> thread\n defdelegate create_thread(attrs), to: CommunityCURD\n # >> tag\n defdelegate create_tag(thread, attrs, user), to: CommunityCURD\n defdelegate update_tag(attrs), to: CommunityCURD\n defdelegate get_tags(community, thread), to: CommunityCURD\n defdelegate get_tags(filter), to: CommunityCURD\n\n # CommunityOperation\n # >> category\n defdelegate set_category(community, category), to: CommunityOperation\n defdelegate unset_category(community, category), to: CommunityOperation\n # >> editor\n defdelegate set_editor(community, title, user), to: CommunityOperation\n defdelegate unset_editor(community, user), to: CommunityOperation\n # >> thread\n defdelegate set_thread(community, thread), to: CommunityOperation\n defdelegate unset_thread(community, thread), to: CommunityOperation\n # >> subscribe / unsubscribe\n defdelegate subscribe_community(community, user), to: CommunityOperation\n defdelegate unsubscribe_community(community, user), to: CommunityOperation\n\n # ArticleCURD\n defdelegate paged_contents(queryable, filter), to: ArticleCURD\n defdelegate create_article(community, thread, attrs, user), to: ArticleCURD\n defdelegate reaction_users(thread, react, id, filters), to: ArticleCURD\n\n # ArticleReaction\n defdelegate reaction(thread, react, content_id, user), to: ArticleReaction\n defdelegate undo_reaction(thread, react, content_id, user), to: ArticleReaction\n\n # ArticleCommunity\n # >> set flag on article, like: pin / unpin article\n defdelegate set_flag(queryable, id, attrs, user), to: ArticleCommunity\n # >> tag: set / unset\n defdelegate set_tag(community, thread, tag, content_id), to: ArticleCommunity\n defdelegate unset_tag(thread, tag, content_id), to: ArticleCommunity\n # >> community: set / unset\n defdelegate mirror_article(community, thread, content_id), to: ArticleCommunity\n defdelegate unmirror_article(community, thread, content_id), to: ArticleCommunity\n\n # Comment CURD\n defdelegate create_comment(thread, content_id, body, user), to: CommentCURD\n defdelegate delete_comment(thread, content_id), to: CommentCURD\n defdelegate paged_comments(thread, content_id, filters), to: CommentCURD\n defdelegate paged_replies(thread, comment, user), to: CommentCURD\n defdelegate reply_comment(thread, comment, body, user), to: CommentCURD\n\n # Comment Reaction\n # >> like / undo like\n defdelegate like_comment(thread, comment, user), to: CommentReaction\n defdelegate undo_like_comment(thread, comment, user), to: CommentReaction\n # >> dislike / undo dislike\n defdelegate dislike_comment(thread, comment, user), to: CommentReaction\n defdelegate undo_dislike_comment(thread, comment, user), to: CommentReaction\n\n # Passport CURD\n defdelegate stamp_passport(rules, user), to: PassportCURD\n defdelegate erase_passport(rules, user), to: PassportCURD\n defdelegate get_passport(user), to: PassportCURD\n defdelegate paged_passports(community, key), to: PassportCURD\n defdelegate delete_passport(user), to: PassportCURD\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,115,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,6,null,null,null,null],"name":"lib/groupher_server/cms/tag.ex","source":"defmodule GroupherServer.CMS.Tag do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, Job, Post, Video}\n\n @required_fields ~w(thread title color author_id community_id)a\n\n @type t :: %Tag{}\n schema \"tags\" do\n field(:title, :string)\n field(:color, :string)\n field(:thread, :string)\n belongs_to(:community, Community)\n belongs_to(:author, Author)\n\n many_to_many(\n :posts,\n Post,\n join_through: \"posts_tags\",\n join_keys: [post_id: :id, tag_id: :id],\n # :delete_all will only remove data from the join source\n on_delete: :delete_all\n # on_replace: :delete\n )\n\n many_to_many(\n :videos,\n Video,\n join_through: \"videos_tags\",\n join_keys: [video_id: :id, tag_id: :id]\n )\n\n many_to_many(\n :jobs,\n Job,\n join_through: \"jobs_tags\",\n join_keys: [job_id: :id, tag_id: :id]\n )\n\n timestamps(type: :utc_datetime)\n end\n\n def changeset(%Tag{} = tag, attrs) do\n tag\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> foreign_key_constraint(:community_id)\n |> unique_constraint(:tag_duplicate, name: :tags_community_id_thread_title_index)\n\n # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,107,null,null,null,null,null,null,null,null,null,null,null,null,null,97,null,null],"name":"lib/groupher_server/accounts/user_follower.ex","source":"defmodule GroupherServer.Accounts.UserFollower do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id follower_id)a\n\n @type t :: %UserFollower{}\n schema \"users_followers\" do\n belongs_to(:user, User, foreign_key: :user_id)\n belongs_to(:follower, User, foreign_key: :follower_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%UserFollower{} = user_follower, attrs) do\n user_follower\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> foreign_key_constraint(:follower_id)\n |> unique_constraint(:user_id, name: :users_followers_user_id_follower_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,108,null,null,null,223,null,null,null,null,null,null,null,253,null,253,null,null,null,null,null,null,null,null,null,1407,null,null,null,null,null,null,null,null,1232,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,103,null,null,null,null,null,null,null,0,null,null,null,null,null,null,17,17,null,null,null,null,17,null,17,null,null,null,null,17,null,null,null,null,null,null,32,null,null,5,4,null,null,null,null,19,18,null,null,null,null,252,null,null,null,null,180,null,null,null,null,null,null,null,null,null,310,310,null,null,null,null,null,28,3,null,null,31,null,31,31,null,null,null,null,null,null,null,1,null,null,1,null,null,null,null,32,null,null,1,1,null,null,31,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,977,null,null,null,null,null,null,null,null,null,209,209,null,null,null,205,null,null],"name":"lib/helper/orm.ex","source":"defmodule Helper.ORM do\n @moduledoc \"\"\"\n General CORD functions\n \"\"\"\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1, done: 3, add: 1]\n import Helper.ErrorHandler\n import ShortMaps\n\n alias Helper.{QueryBuilder, SpecType}\n alias GroupherServer.Repo\n\n @doc \"\"\"\n a wrap for paginate request\n \"\"\"\n def paginater(queryable, page: page, size: size) do\n queryable |> Repo.paginate(page: page, page_size: size)\n end\n\n def paginater(queryable, ~m(page size)a) do\n queryable |> Repo.paginate(page: page, page_size: size)\n end\n\n @doc \"\"\"\n wrap Repo.get with preload and result/errer format handle\n \"\"\"\n def find(queryable, id, preload: preload) do\n queryable\n |> preload(^preload)\n |> Repo.get(id)\n |> done(queryable, id)\n end\n\n @doc \"\"\"\n simular to Repo.get/3, with standard result/error handle\n \"\"\"\n @spec find(Ecto.Queryable.t(), SpecType.id()) :: {:ok, any()} | {:error, String.t()}\n def find(queryable, id) do\n queryable\n |> Repo.get(id)\n |> done(queryable, id)\n end\n\n @doc \"\"\"\n simular to Repo.get_by/3, with standard result/error handle\n \"\"\"\n def find_by(queryable, clauses) do\n queryable\n |> Repo.get_by(clauses)\n |> case do\n nil ->\n {:error, not_found_formater(queryable, clauses)}\n\n result ->\n {:ok, result}\n end\n end\n\n @doc \"\"\"\n return pageinated Data required by filter\n \"\"\"\n # TODO: find content not in markDelete by default\n def find_all(queryable, %{page: page, size: size} = filter) do\n queryable\n |> QueryBuilder.filter_pack(filter)\n |> paginater(page: page, size: size)\n |> done()\n end\n\n @doc \"\"\"\n return Data required by filter\n \"\"\"\n # TODO: find content not in markDelete by default\n def find_all(queryable, filter) do\n queryable |> QueryBuilder.filter_pack(filter) |> Repo.all() |> done()\n end\n\n @doc \"\"\"\n Require queryable has a views fields to count the views of the queryable Modal\n \"\"\"\n def read(queryable, id, inc: :views) do\n with {:ok, result} <- find(queryable, id) do\n result |> inc_views_count(queryable) |> done()\n end\n end\n\n defp inc_views_count(content, queryable) do\n {1, [result]} =\n Repo.update_all(\n from(p in queryable, where: p.id == ^content.id),\n [inc: [views: 1]],\n returning: [:views]\n )\n\n put_in(content.views, result.views)\n end\n\n @doc \"\"\"\n NOTICE: this should be use together with Authorize/OwnerCheck etc Middleware\n DO NOT use it directly\n \"\"\"\n def delete(content), do: Repo.delete(content)\n\n def find_delete(queryable, id) do\n with {:ok, content} <- find(queryable, id) do\n delete(content)\n end\n end\n\n def findby_delete(queryable, clauses) do\n with {:ok, content} <- find_by(queryable, clauses) do\n delete(content)\n end\n end\n\n def findby_or_insert(queryable, clauses, attrs) do\n case queryable |> find_by(clauses) do\n {:ok, content} ->\n {:ok, content}\n\n {:error, _} ->\n queryable |> create(attrs)\n end\n end\n\n @doc \"\"\"\n NOTE: this should be use together with passport_loader etc Middleware\n DO NOT use it directly\n \"\"\"\n def update(content, attrs) do\n content\n |> content.__struct__.changeset(attrs)\n |> Repo.update()\n end\n\n @doc \"\"\"\n find and update sourc\n \"\"\"\n def find_update(queryable, id, attrs), do: do_find_update(queryable, id, attrs)\n def find_update(queryable, %{id: id} = attrs), do: do_find_update(queryable, id, attrs)\n\n defp do_find_update(queryable, id, attrs) do\n with {:ok, content} <- find(queryable, id) do\n content\n |> content.__struct__.changeset(attrs)\n |> Repo.update()\n end\n end\n\n @doc \"\"\"\n find then update\n \"\"\"\n def update_by(source, clauses, attrs) do\n with {:ok, content} <- find_by(source, clauses) do\n content\n |> Ecto.Changeset.change(attrs)\n |> Repo.update()\n end\n end\n\n def upsert_by(queryable, clauses, attrs) do\n case queryable |> find_by(clauses) do\n {:ok, content} ->\n content\n |> content.__struct__.changeset(attrs)\n |> Repo.update()\n\n {:error, _} ->\n queryable |> create(attrs)\n end\n end\n\n @doc \"\"\"\n see https://elixirforum.com/t/ecto-inc-dec-update-one-helpers/5564\n \"\"\"\n # def update_one(queryable, where, changes) do\n # query |> Ecto.Query.where(^where) |> Repo.update_all(set: changes)\n # end\n\n # def inc(queryable, where, changes) do\n # query |> Ecto.Query.where(^where) |> Repo.update_all(inc: changes)\n # end\n\n def create(model, attrs) do\n model\n |> struct\n |> model.changeset(attrs)\n |> Repo.insert()\n end\n\n @doc \"\"\"\n return the total count of a Modal based on id column\n also support filters\n \"\"\"\n def count(queryable, filter \\\\ %{}) do\n queryable\n |> QueryBuilder.filter_pack(filter)\n |> select([f], count(f.id))\n |> Repo.one()\n end\n\n def next_count(queryable) do\n queryable |> count() |> add()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,20,null,20,null,null,null,628,null,628,null,null,null,null,628,null,628,null,null],"name":"lib/helper/error_handler.ex","source":"defmodule Helper.ErrorHandler do\n @moduledoc \"\"\"\n This module defines some helper function used by\n handle/format changset errors\n \"\"\"\n alias GroupherServerWeb.Gettext, as: Translator\n\n def not_found_formater(queryable, id) when is_integer(id) or is_binary(id) do\n model = queryable |> to_string |> String.split(\".\") |> List.last()\n\n Translator |> Gettext.dgettext(\"404\", \"#{model}(%{id}) not found\", id: id)\n end\n\n def not_found_formater(queryable, clauses) do\n model = queryable |> to_string |> String.split(\".\") |> List.last()\n\n detail =\n clauses\n |> Enum.into(%{})\n |> Map.values()\n |> List.first()\n |> to_string\n\n Translator |> Gettext.dgettext(\"404\", \"#{model}(%{name}) not found\", name: detail)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,0,null,null,17,null,null,null,58,null,null,0,null],"name":"lib/groupher_server_web/middleware/covert_to_int.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.ConvertToInt do\n @behaviour Absinthe.Middleware\n # google: must appear in the GROUP BY clause or be used in an aggregate function\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(%{value: [value]} = resolution, _) do\n %{resolution | value: value}\n end\n\n def call(%{value: []} = resolution, _) do\n %{resolution | value: 0}\n end\n\n def call(resolution, _), do: resolution\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,44,null,null,null,44,null,null,null,null,44,44,38,38,null,38,null,35,null,null,3,3,null,3,3,null,null,null,null,3,null,null,null,6,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,30,30,30,26,26,null,26,26,null,26,26,null,null,null,null,null,null,null,null,null,null,null,null,null,9,9,null,9,null,null,null,9,null,null,null,null,null,null,null,46,null,null,null,null,46,null,null,null,null,null,7,null,null],"name":"lib/groupher_server/cms/delegates/article_curd.ex","source":"defmodule GroupherServer.CMS.Delegate.ArticleCURD do\n import Ecto.Query, warn: false\n import GroupherServer.CMS.Helper.MatcherOld\n import Helper.Utils, only: [done: 1]\n import ShortMaps\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.{Repo, CMS, Statistics}\n alias GroupherServer.CMS.Delegate.ArticleCommunity\n alias Helper.{ORM, QueryBuilder}\n\n alias CMS.{Author, Community}\n\n @doc \"\"\"\n get paged post / job ...\n \"\"\"\n def paged_contents(queryable, filter) do\n normal_content_fr = filter |> Map.merge(QueryBuilder.default_article_filters())\n\n queryable\n |> ORM.find_all(normal_content_fr)\n |> add_pin_contents_ifneed(queryable, filter)\n end\n\n # only first page need pin contents\n defp add_pin_contents_ifneed(contents, queryable, filter) do\n with {:ok, normal_contents} <- contents,\n true <- 1 == Map.get(normal_contents, :page_number) do\n pin_content_fr = filter |> Map.merge(%{pin: true})\n {:ok, pined_content} = queryable |> ORM.find_all(pin_content_fr)\n\n case pined_content |> Map.get(:total_count) do\n 0 ->\n contents\n\n _ ->\n pind_entries = pined_content |> Map.get(:entries)\n normal_entries = normal_contents |> Map.get(:entries)\n\n normal_count = normal_contents |> Map.get(:total_count)\n pind_count = pined_content |> Map.get(:total_count)\n\n normal_contents\n |> Map.put(:entries, pind_entries ++ normal_entries)\n |> Map.put(:total_count, pind_count + normal_count)\n |> done\n end\n else\n _error ->\n contents\n end\n end\n\n @doc \"\"\"\n Creates a content(post/job ...), and set community.\n\n ## Examples\n\n iex> create_post(%{field: value})\n {:ok, %Post{}}\n\n iex> create_post(%{field: bad_value})\n {:error, %Ecto.Changeset{}}\n\n \"\"\"\n def create_article(%Community{id: community_id}, thread, attrs, %User{id: user_id}) do\n with {:ok, author} <- ensure_author_exists(%User{id: user_id}),\n {:ok, action} <- match_action(thread, :community),\n {:ok, community} <- ORM.find(Community, community_id),\n {:ok, content} <-\n action.target\n |> struct()\n |> action.target.changeset(attrs)\n |> Ecto.Changeset.put_change(:author_id, author.id)\n |> Repo.insert() do\n Statistics.log_publish_action(%User{id: user_id})\n ArticleCommunity.mirror_article(community, thread, content.id)\n end\n end\n\n @doc \"\"\"\n get CMS contents\n post's favorites/stars/comments ...\n ...\n jobs's favorites/stars/comments ...\n\n with or without page info\n \"\"\"\n def reaction_users(thread, react, id, %{page: page, size: size} = filters) do\n # when valid_reaction(thread, react) do\n with {:ok, action} <- match_action(thread, react),\n {:ok, where} <- dynamic_where(thread, id) do\n # common_filter(action.reactor)\n action.reactor\n |> where(^where)\n |> QueryBuilder.load_inner_users(filters)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n end\n\n def ensure_author_exists(%User{} = user) do\n # unique_constraint: avoid race conditions, make sure user_id unique\n # foreign_key_constraint: check foreign key: user_id exsit or not\n # see alos no_assoc_constraint in https://hexdocs.pm/ecto/Ecto.Changeset.html\n %Author{user_id: user.id}\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.unique_constraint(:user_id)\n |> Ecto.Changeset.foreign_key_constraint(:user_id)\n |> Repo.insert()\n |> handle_existing_author()\n end\n\n defp handle_existing_author({:ok, author}), do: {:ok, author}\n\n defp handle_existing_author({:error, changeset}) do\n ORM.find_by(Author, user_id: changeset.data.user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/accounts/user_bill.ex","source":"defmodule GroupherServer.Accounts.UserBill do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.{Bill, User}\n\n @required_fields ~w(user_id bill_id)a\n\n @type t :: %UserBill{}\n schema \"users_bills\" do\n belongs_to(:user, User, foreign_key: :user_id)\n belongs_to(:bill, Bill, foreign_key: :bill_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%UserBill{} = user_bill, attrs) do\n user_bill\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> foreign_key_constraint(:bill_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,0,null,0,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,0,0,null,null,null,null],"name":"test/support/data_case.ex","source":"defmodule GroupherServer.DataCase do\n @moduledoc \"\"\"\n This module defines the setup for tests requiring\n access to the application's data layer.\n\n You may define functions here to be used as helpers in\n your tests.\n\n Finally, if the test case interacts with the database,\n it cannot be async. For this reason, every test runs\n inside a transaction which is reset at the beginning\n of the test unless the test case is marked as async.\n \"\"\"\n\n use ExUnit.CaseTemplate\n\n using do\n quote do\n alias GroupherServer.Repo\n\n import Ecto\n import Ecto.Changeset\n import Ecto.Query\n import GroupherServer.DataCase\n end\n end\n\n setup tags do\n :ok = Ecto.Adapters.SQL.Sandbox.checkout(GroupherServer.Repo)\n\n unless tags[:async] do\n Ecto.Adapters.SQL.Sandbox.mode(GroupherServer.Repo, {:shared, self()})\n end\n\n :ok\n end\n\n @doc \"\"\"\n A helper that transform changeset errors to a map of messages.\n\n assert {:error, changeset} = Accounts.create_user(%{password: \"short\"})\n assert \"password is too short\" in errors_on(changeset).password\n assert %{password: [\"password is too short\"]} = errors_on(changeset)\n\n \"\"\"\n def errors_on(changeset) do\n Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->\n Enum.reduce(opts, message, fn {key, value}, acc ->\n String.replace(acc, \"%{#{key}}\", to_string(value))\n end)\n end)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/account/account_queries.ex","source":"defmodule GroupherServerWeb.Schema.Account.Queries do\n @moduledoc \"\"\"\n accounts GraphQL queries\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :account_queries do\n @desc \"get all users\"\n field :paged_users, non_null(:paged_users) do\n arg(:filter, non_null(:paged_users_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.users/3)\n end\n\n @desc \"get user by id\"\n field :user, :user do\n arg(:id, non_null(:id))\n\n resolve(&R.Accounts.user/3)\n end\n\n @desc \"get login-user's info\"\n field :account, :user do\n middleware(M.Authorize, :login)\n\n resolve(&R.Accounts.account/3)\n end\n\n @desc \"anyone can get anyone's subscribed communities\"\n field :subscribed_communities, :paged_communities do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.subscribed_communities/3)\n end\n\n @desc \"get user's follower\"\n field :paged_followers, :paged_users do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.paged_followers/3)\n end\n\n @desc \"get user's follower\"\n field :paged_followings, :paged_users do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.paged_followings/3)\n end\n\n @desc \"get favorited posts\"\n field :favorited_posts, :paged_posts do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.favorited_posts/3)\n end\n\n @desc \"get favorited jobs\"\n field :favorited_jobs, :paged_jobs do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.favorited_jobs/3)\n end\n\n @desc \"get all passport rules include system and community etc ...\"\n field :all_passport_rules_string, :rules do\n middleware(M.Authorize, :login)\n\n resolve(&R.Accounts.get_all_rules/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/logs/user_activity.ex","source":"defmodule GroupherServer.Logs.UserActivity do\n @moduledoc false\n # alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id source_title source_id source_type)a\n # @optional_fields ~w(source_type)a\n\n schema \"user_activity_logs\" do\n belongs_to(:user, User)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(user_activity, attrs) do\n user_activity\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,42,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/notification.mail.ex","source":"defmodule GroupherServer.Accounts.NotificationMail do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id action source_id source_type source_preview)a\n @optional_fields ~w(parent_id parent_type read)a\n\n @type t :: %NotificationMail{}\n schema \"notification_mails\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n field(:action, :string)\n\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:parent_id, :string)\n field(:parent_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%NotificationMail{} = notication_mail, attrs) do\n notication_mail\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,51,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"test/support/test_tools.ex","source":"defmodule GroupherServer.TestTools do\n @moduledoc \"\"\"\n helper for reduce import mudules in test files\n \"\"\"\n use ExUnit.CaseTemplate\n\n using do\n quote do\n use GroupherServerWeb.ConnCase, async: true\n\n import GroupherServer.Factory\n import GroupherServer.Test.ConnSimulator\n import GroupherServer.Test.AssertHelper\n import Ecto.Query, warn: false\n import Helper.ErrorCode\n\n import ShortMaps\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,8,null,null,null,null,null,null,null,2,null,2,null,null,null,null,null,null,null,8,8,null,null,null,null,null,null,null,1,null,1,null,null,null,null,null,null,null,null,null,null,null,null,null,59,59,null,null,59,null,null,null,null,null,null,1,1,1,null,null,null,null,59,null,null,null,null,null,null,null,null,null,null,null,null,76,75,null,null,null,null,2,null,1,null,null,null],"name":"lib/groupher_server/cms/delegates/community_operation.ex","source":"defmodule GroupherServer.CMS.Delegate.CommunityOperation do\n @moduledoc \"\"\"\n community operations, like: set/unset category/thread/editor...\n \"\"\"\n import ShortMaps\n\n alias Ecto.Multi\n alias Helper.{Certification, ORM}\n alias GroupherServer.Accounts.User\n alias GroupherServer.CMS.Delegate.PassportCURD\n alias GroupherServer.Repo\n\n alias GroupherServer.CMS.{\n Category,\n Community,\n CommunityCategory,\n CommunityEditor,\n CommunitySubscriber,\n CommunityThread,\n Thread\n }\n\n @doc \"\"\"\n set a category to community\n \"\"\"\n def set_category(%Community{id: community_id}, %Category{id: category_id}) do\n with {:ok, community_category} <-\n CommunityCategory |> ORM.create(~m(community_id category_id)a) do\n Community |> ORM.find(community_category.community_id)\n end\n end\n\n @doc \"\"\"\n unset a category to community\n \"\"\"\n def unset_category(%Community{id: community_id}, %Category{id: category_id}) do\n with {:ok, community_category} <-\n CommunityCategory |> ORM.findby_delete!(~m(community_id category_id)a) do\n Community |> ORM.find(community_category.community_id)\n end\n end\n\n @doc \"\"\"\n set to thread to a community\n \"\"\"\n def set_thread(%Community{id: community_id}, %Thread{id: thread_id}) do\n with {:ok, community_thread} <- CommunityThread |> ORM.create(~m(community_id thread_id)a) do\n Community |> ORM.find(community_thread.community_id)\n end\n end\n\n @doc \"\"\"\n unset to thread to a community\n \"\"\"\n def unset_thread(%Community{id: community_id}, %Thread{id: thread_id}) do\n with {:ok, community_thread} <-\n CommunityThread |> ORM.findby_delete!(~m(community_id thread_id)a) do\n Community |> ORM.find(community_thread.community_id)\n end\n end\n\n @doc \"\"\"\n set a community editor\n \"\"\"\n def set_editor(%Community{id: community_id}, title, %User{id: user_id}) do\n Multi.new()\n |> Multi.insert(\n :insert_editor,\n CommunityEditor.changeset(%CommunityEditor{}, ~m(user_id community_id title)a)\n )\n |> Multi.run(:stamp_passport, fn _ ->\n rules = Certification.passport_rules(cms: title)\n PassportCURD.stamp_passport(rules, %User{id: user_id})\n end)\n |> Repo.transaction()\n |> set_editor_result()\n end\n\n @doc \"\"\"\n unset a community editor\n \"\"\"\n def unset_editor(%Community{id: community_id}, %User{id: user_id}) do\n with {:ok, _} <- ORM.findby_delete!(CommunityEditor, ~m(user_id community_id)a),\n {:ok, _} <- PassportCURD.delete_passport(%User{id: user_id}) do\n User |> ORM.find(user_id)\n end\n end\n\n defp set_editor_result({:ok, %{insert_editor: editor}}) do\n User |> ORM.find(editor.user_id)\n end\n\n defp set_editor_result({:error, :stamp_passport, _result, _steps}),\n do: {:error, \"stamp passport error\"}\n\n defp set_editor_result({:error, :insert_editor, _result, _steps}),\n do: {:error, \"insert editor error\"}\n\n @doc \"\"\"\n subscribe a community. (ONLY community, post etc use watch )\n \"\"\"\n def subscribe_community(%Community{id: community_id}, %User{id: user_id}) do\n with {:ok, record} <- CommunitySubscriber |> ORM.create(~m(user_id community_id)a) do\n Community |> ORM.find(record.community_id)\n end\n end\n\n def unsubscribe_community(%Community{id: community_id}, %User{id: user_id}) do\n with {:ok, record} <-\n CommunitySubscriber |> ORM.findby_delete!(community_id: community_id, user_id: user_id) do\n Community |> ORM.find(record.community_id)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null],"name":"lib/groupher_server_web/schema/account/account_misc.ex","source":"defmodule GroupherServerWeb.Schema.Account.Misc do\n use Absinthe.Schema.Notation\n\n import GroupherServerWeb.Schema.Helper.Fields\n # import Helper.Utils, only: [get_config: 2]\n # @page_size get_config(:general, :page_size)\n\n @desc \"article_filter doc\"\n input_object :paged_users_filter do\n pagination_args()\n # field(:when, :when_enum)\n # field(:sort, :sort_enum)\n # field(:article_tag, :string, default_value: :all)\n # field(:community, :string)\n end\n\n input_object :github_profile_input do\n # is github_id in db table\n field(:id, non_null(:string))\n field(:login, non_null(:string))\n field(:avatar_url, non_null(:string))\n field(:url, :string)\n field(:html_url, :string)\n field(:name, :string)\n field(:company, :string)\n field(:blog, :string)\n field(:location, :string)\n field(:email, :string)\n field(:bio, :string)\n field(:public_repos, :integer)\n field(:public_gists, :integer)\n end\n\n input_object :user_profile_input do\n field(:nickname, :string)\n field(:bio, :string)\n field(:sex, :string)\n field(:education, :string)\n field(:location, :string)\n field(:company, :string)\n field(:email, :string)\n field(:qq, :string)\n field(:weibo, :string)\n field(:weichat, :string)\n end\n\n # see: https://github.com/absinthe-graphql/absinthe/issues/206\n # https://github.com/absinthe-graphql/absinthe/wiki/Scalar-Recipes\n scalar :json, name: \"Json\" do\n description(\"\"\"\n The `Json` scalar type represents arbitrary json string data, represented as UTF-8\n character sequences. The Json type is most often used to represent a free-form\n human-readable json string.\n \"\"\")\n\n serialize(&encode/1)\n parse(&decode/1)\n end\n\n @spec decode(Absinthe.Blueprint.Input.String.t()) :: {:ok, term()} | :error\n @spec decode(Absinthe.Blueprint.Input.Null.t()) :: {:ok, nil}\n defp decode(%Absinthe.Blueprint.Input.String{value: value}) do\n case Jason.decode(value) do\n {:ok, result} -> {:ok, result}\n _ -> :error\n end\n end\n\n defp decode(%Absinthe.Blueprint.Input.Null{}) do\n {:ok, nil}\n end\n\n defp decode(_) do\n :error\n end\n\n defp encode(value), do: value\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,18,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,null,null,null,null],"name":"lib/groupher_server/cms/community_category.ex","source":"defmodule GroupherServer.CMS.CommunityCategory do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Category, Community}\n\n @type t :: %CommunityCategory{}\n\n schema \"communities_categories\" do\n belongs_to(:community, Community, foreign_key: :community_id)\n belongs_to(:category, Category, foreign_key: :category_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @required_fields ~w(community_id category_id)a\n\n @doc false\n def changeset(%CommunityCategory{} = community_category, attrs) do\n community_category\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:community_id)\n |> foreign_key_constraint(:category_id)\n |> unique_constraint(\n :community_id,\n name: :communities_categories_community_id_category_id_index\n )\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,0,null,0,null,null,168,null],"name":"lib/groupher_server_web/middleware/general_error.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.GeneralError do\n @behaviour Absinthe.Middleware\n\n def call(%{errors: [List = errors]} = resolution, _) do\n message = [%{message: errors}]\n\n %{resolution | value: [], errors: message}\n end\n\n def call(resolution, _), do: resolution\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,11,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/cms/post_star.ex","source":"defmodule GroupherServer.CMS.PostStar do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Post\n\n @required_fields ~w(user_id post_id)a\n\n @type t :: %PostStar{}\n schema \"posts_stars\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:post, Post, foreign_key: :post_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostStar{} = post_star, attrs) do\n # |> unique_constraint(:user_id, name: :favorites_user_id_article_id_index)\n post_star\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:user_id, name: :posts_stars_user_id_post_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,197,197,196,196,null,196,196,null,null,null,null,196,null,196,null,null,null,153,43,null,null,null,null,null,5,5,5,null,5,5,null,null,null,null,null,null,null,null,null,null,null,null,11,11,null,11,null,null,null,11,null,null,null,null,3,3,3,null,3,null,3,null,null,null,null,9,9,9,null,9,null,null,null,null,null,null,9,9,null,null,null,null,7,null,2,null,null,null,7,7,null,7,7,null,7,null,null,null,null,null,2,2,null,2,null,2,null,null,null,null,null,196,null,null,null,196,null,null,null,null,9,null,null,null,9,null,null,162,45,null,7,2,null],"name":"lib/groupher_server/cms/delegates/comment_curd.ex","source":"defmodule GroupherServer.CMS.Delegate.CommentCURD do\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1]\n import GroupherServer.CMS.Helper.MatcherOld\n import ShortMaps\n\n alias GroupherServer.{Repo, Accounts}\n alias Helper.{ORM, QueryBuilder}\n alias GroupherServer.CMS.{PostCommentReply, JobCommentReply}\n\n @doc \"\"\"\n Creates a comment for psot, job ...\n \"\"\"\n def create_comment(thread, content_id, body, %Accounts.User{id: user_id}) do\n with {:ok, action} <- match_action(thread, :comment),\n {:ok, content} <- ORM.find(action.target, content_id),\n {:ok, user} <- ORM.find(Accounts.User, user_id) do\n next_floor = get_next_floor(thread, action.reactor, content.id)\n\n attrs = %{\n author_id: user.id,\n body: body,\n floor: next_floor\n }\n\n attrs = merge_comment_attrs(thread, attrs, content.id)\n\n action.reactor |> ORM.create(attrs)\n end\n end\n\n defp merge_comment_attrs(:post, attrs, id), do: attrs |> Map.merge(%{post_id: id})\n defp merge_comment_attrs(:job, attrs, id), do: attrs |> Map.merge(%{job_id: id})\n\n @doc \"\"\"\n Delete the comment and increase all the floor after this comment\n \"\"\"\n def delete_comment(thread, content_id) do\n with {:ok, action} <- match_action(thread, :comment),\n {:ok, comment} <- ORM.find(action.reactor, content_id) do\n case ORM.delete(comment) do\n {:ok, comment} ->\n Repo.update_all(\n from(p in action.reactor, where: p.id > ^comment.id),\n inc: [floor: -1]\n )\n\n {:ok, comment}\n\n {:error, error} ->\n {:error, error}\n end\n end\n end\n\n def paged_comments(thread, content_id, %{page: page, size: size} = filters) do\n with {:ok, action} <- match_action(thread, :comment) do\n dynamic = dynamic_comment_where(thread, content_id)\n\n action.reactor\n |> where(^dynamic)\n |> QueryBuilder.filter_pack(filters)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n end\n\n def paged_replies(thread, comment_id, %Accounts.User{id: user_id}) do\n with {:ok, action} <- match_action(thread, :comment) do\n action.reactor\n |> where([c], c.author_id == ^user_id)\n |> join(:inner, [c], r in assoc(c, :reply_to))\n |> where([c, r], r.id == ^comment_id)\n |> Repo.all()\n |> done()\n end\n end\n\n def reply_comment(thread, comment_id, body, %Accounts.User{id: user_id}) do\n with {:ok, action} <- match_action(thread, :comment),\n {:ok, comment} <- ORM.find(action.reactor, comment_id) do\n next_floor = get_next_floor(thread, action.reactor, comment)\n\n attrs = %{\n author_id: user_id,\n body: body,\n reply_to: comment,\n floor: next_floor\n }\n\n attrs = merge_reply_attrs(thread, attrs, comment)\n brige_reply(thread, action.reactor, comment, attrs)\n end\n end\n\n defp merge_reply_attrs(:post, attrs, comment),\n do: attrs |> Map.merge(%{post_id: comment.post_id})\n\n defp merge_reply_attrs(:job, attrs, comment), do: attrs |> Map.merge(%{job_id: comment.job_id})\n\n defp brige_reply(:post, queryable, comment, attrs) do\n # TODO: use Multi task to refactor\n with {:ok, reply} <- ORM.create(queryable, attrs) do\n ORM.update(reply, %{reply_id: comment.id})\n\n {:ok, _} =\n PostCommentReply |> ORM.create(%{post_comment_id: comment.id, reply_id: reply.id})\n\n queryable |> ORM.find(reply.id)\n end\n end\n\n defp brige_reply(:job, queryable, comment, attrs) do\n # TODO: use Multi task to refactor\n with {:ok, reply} <- ORM.create(queryable, attrs) do\n ORM.update(reply, %{reply_id: comment.id})\n\n {:ok, _} = JobCommentReply |> ORM.create(%{job_comment_id: comment.id, reply_id: reply.id})\n\n queryable |> ORM.find(reply.id)\n end\n end\n\n # for create comment\n defp get_next_floor(thread, queryable, id) when is_integer(id) do\n dynamic = dynamic_comment_where(thread, id)\n\n queryable\n |> where(^dynamic)\n |> ORM.next_count()\n end\n\n # for reply comment\n defp get_next_floor(thread, queryable, comment) do\n dynamic = dynamic_reply_where(thread, comment)\n\n queryable\n |> where(^dynamic)\n |> ORM.next_count()\n end\n\n defp dynamic_comment_where(:post, id), do: dynamic([c], c.post_id == ^id)\n defp dynamic_comment_where(:job, id), do: dynamic([c], c.job_id == ^id)\n\n defp dynamic_reply_where(:post, comment), do: dynamic([c], c.post_id == ^comment.post_id)\n defp dynamic_reply_where(:job, comment), do: dynamic([c], c.job_id == ^comment.job_id)\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,2,2,null,null,null,null,2,null,null,null,62,null,null,62,62,62,62,62,null,null,62,null,null,null,null,null,null,29,null,null,null,26,null,null],"name":"lib/groupher_server/delivery/delegates/notifications.ex","source":"defmodule GroupherServer.Delivery.Delegate.Notifications do\n @moduledoc \"\"\"\n The Delivery context.\n \"\"\"\n import Helper.Utils, only: [done: 2]\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.Delivery.{Notification, SysNotification}\n alias Helper.ORM\n\n alias GroupherServer.Delivery.Delegate.Utils\n\n # TODO: audience\n def publish_system_notification(info) do\n attrs = %{\n source_id: info.source_id,\n source_title: info.source_title,\n source_type: info |> Map.get(:source_type, \"\"),\n source_preview: info |> Map.get(:source_preview, \"\")\n }\n\n SysNotification |> ORM.create(attrs) |> done(:status)\n end\n\n def notify_someone(%User{id: from_user_id}, %User{id: to_user_id}, info) do\n attrs = %{\n from_user_id: from_user_id,\n to_user_id: to_user_id,\n action: info.action,\n source_id: info.source_id,\n source_title: info.source_title,\n source_type: info.source_type,\n source_preview: info.source_preview\n }\n\n Notification |> ORM.create(attrs)\n end\n\n @doc \"\"\"\n fetch notifications from Delivery\n \"\"\"\n def fetch_notifications(%User{} = user, %{page: _, size: _, read: _} = filter) do\n Utils.fetch_messages(user, Notification, filter)\n end\n\n def fetch_sys_notifications(%User{} = user, %{page: _, size: _} = filter) do\n Utils.fetch_messages(:sys_notification, user, filter)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,1722,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,34,null,null,null,null,null],"name":"lib/groupher_server/cms/post.ex","source":"defmodule GroupherServer.CMS.Post do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, PostComment, PostFavorite, PostStar, Tag}\n\n @required_fields ~w(title body digest length)a\n @optional_fields ~w(link_addr pin markDelete)\n\n @type t :: %Post{}\n schema \"cms_posts\" do\n field(:body, :string)\n field(:title, :string)\n field(:digest, :string)\n field(:link_addr, :string)\n field(:length, :integer)\n field(:views, :integer, default: 0)\n\n field(:pin, :boolean, default_value: false)\n field(:markDelete, :boolean, default_value: false)\n belongs_to(:author, Author)\n\n # TODO\n # 相关文章\n # has_may(:related_post, ...)\n\n has_many(:comments, {\"posts_comments\", PostComment})\n has_many(:favorites, {\"posts_favorites\", PostFavorite})\n has_many(:stars, {\"posts_stars\", PostStar})\n # The keys are inflected from the schema names!\n # see https://hexdocs.pm/ecto/Ecto.Schema.html\n many_to_many(\n :tags,\n Tag,\n join_through: \"posts_tags\",\n join_keys: [post_id: :id, tag_id: :id],\n # :delete_all will only remove data from the join source\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_posts\",\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Post{} = post, attrs) do\n post\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n\n # |> foreign_key_constraint(:posts_tags, name: :posts_tags_tag_id_fkey)\n # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,0,null,null,null,null,null,null,0,0,null,0,null,null,0,null,null,0,0,null,null,null,null,0,null,null,null,0,0,null,0,0,null,0,null,null,null,null,null,null,null,null,null,null,null,null,0,0,null,null,null,null,0,0,0,null,null,null,null],"name":"lib/helper/oauth2/github.ex","source":"defmodule Helper.OAuth2.Github do\n use Tesla, only: [:get, :post]\n import Helper.Utils, only: [get_config: 2]\n\n # see Tesla intro: https://medium.com/@teamon/introducing-tesla-the-flexible-http-client-for-elixir-95b699656d88\n @timeout_limit 5000\n @client_id get_config(:github_oauth, :client_id)\n @client_secret get_config(:github_oauth, :client_secret)\n @redirect_uri \"http://www.coderplanets.com\"\n\n # wired only this style works\n plug(Tesla.Middleware.BaseUrl, \"https://github.com/login/oauth\")\n # plug(Tesla.Middleware.BaseUrl, \"https://www.github.com/login/oauth\")\n # plug(Tesla.Middleware.BaseUrl, \"https://api.github.com/login/oauth\")\n plug(Tesla.Middleware.Headers, %{\n \"User-Agent\" => \"groupher server\"\n # \"Accept\" => \"application/json\"\n # \"Accept\" => \"application/json;application/vnd.github.jean-grey-preview+json\"\n })\n\n plug(Tesla.Middleware.Retry, delay: 200, max_retries: 2)\n plug(Tesla.Middleware.Timeout, timeout: @timeout_limit)\n plug(Tesla.Middleware.JSON)\n plug(Tesla.Middleware.FormUrlencoded)\n\n def user_profile(code) do\n # body = \"client_id=#{@client_id}&client_secret=#{@client_secret}&code=#{code}&redirect_uri=#{@redirect_uri}\"\n # post(\"access_token?#{body}\",%{})\n headers = %{\"Accept\" => \"application/json\"}\n\n query = [\n code: code,\n client_id: @client_id,\n client_secret: @client_secret,\n redirect_uri: @redirect_uri\n ]\n\n try do\n case post(\"/access_token\", %{}, query: query, headers: headers) do\n %{status: 200, body: %{\"error\" => error, \"error_description\" => description}} ->\n {:error, \"#{error}: #{description}\"}\n\n %{status: 200, body: %{\"access_token\" => access_token, \"token_type\" => \"bearer\"}} ->\n user_info(access_token)\n end\n rescue\n e ->\n e |> handle_tesla_error\n end\n end\n\n def user_info(access_token) do\n url = \"https://api.github.com/user\"\n # this special header is too get node_id\n # see: https://developer.github.com/v3/\n\n headers = %{\"Accept\" => \"application/vnd.github.jean-grey-preview+json\"}\n query = [access_token: access_token]\n\n try do\n case get(url, query: query, headers: headers) do\n %{status: 200, body: body} ->\n body = body |> Map.merge(%{\"access_token\" => access_token})\n {:ok, body}\n\n %{status: 401, body: body} ->\n {:error, \"OAuth2 Github: \" <> body[\"message\"]}\n\n %{status: 403, body: body} ->\n {:error, \"OAuth2 Github: \" <> body}\n\n _ ->\n {:error, \"OAuth2 Github: unhandle error\"}\n end\n rescue\n e ->\n e |> handle_tesla_error\n end\n end\n\n defp handle_tesla_error(error) do\n case error do\n %{reason: :timeout} -> {:error, \"OAuth2 Github: timeout in #{@timeout_limit} msec\"}\n %{reason: reason} -> {:error, \"OAuth2 Github: #{reason}\"}\n _ -> {:error, \"unhandle error #{inspect(error)}\"}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/cms_misc.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Misc do\n use Absinthe.Schema.Notation\n\n import GroupherServerWeb.Schema.Helper.Fields\n\n alias GroupherServer.CMS\n\n @default_inner_page_size 5\n\n enum :comment_replies_type do\n value(:comment_replies_type)\n end\n\n enum :post_type do\n value(:post)\n end\n\n enum :community_type do\n value(:community)\n end\n\n enum :favorite_action do\n value(:favorite)\n end\n\n enum :count_type do\n value(:count)\n end\n\n enum :viewer_did_type do\n value(:viewer_did)\n end\n\n enum :star_action do\n value(:star)\n end\n\n enum :comment_action do\n value(:comment)\n end\n\n enum :unique_type do\n value(true)\n value(false)\n end\n\n enum :cms_action do\n value(:favorite)\n value(:star)\n value(:watch)\n end\n\n enum :thread do\n value(:post)\n value(:job)\n value(:video)\n value(:repo)\n value(:wiki)\n end\n\n enum :cms_comment do\n value(:post_comment)\n end\n\n enum :order_enum do\n value(:asc)\n value(:desc)\n end\n\n enum :when_enum do\n value(:today)\n value(:this_week)\n value(:this_month)\n value(:this_year)\n end\n\n enum :comment_sort_enum do\n value(:asc_inserted)\n value(:desc_inserted)\n value(:most_likes)\n value(:most_dislikes)\n end\n\n enum :thread_sort_enum do\n value(:asc_index)\n value(:desc_index)\n value(:asc_inserted)\n value(:desc_inserted)\n end\n\n enum :sort_enum do\n value(:most_views)\n value(:most_updated)\n value(:most_favorites)\n value(:most_stars)\n value(:most_watched)\n value(:most_comments)\n value(:least_views)\n value(:least_updated)\n value(:least_favorites)\n value(:least_stars)\n value(:least_watched)\n value(:least_comments)\n value(:recent_updated)\n end\n\n enum :rainbow_color_enum do\n value(:red)\n value(:orange)\n value(:yellow)\n value(:green)\n value(:cyan)\n value(:blue)\n value(:purple)\n end\n\n @desc \"inline members-like filter for dataloader usage\"\n input_object :members_filter do\n field(:first, :integer, default_value: @default_inner_page_size)\n end\n\n input_object :comments_filter do\n pagination_args()\n field(:sort, :comment_sort_enum, default_value: :asc_inserted)\n end\n\n input_object :communities_filter do\n @desc \"limit of records (default 20), if first > 30, only return 30 at most\"\n pagination_args()\n field(:sort, :sort_enum)\n field(:category, :string)\n end\n\n input_object :threads_filter do\n pagination_args()\n field(:sort, :thread_sort_enum)\n end\n\n input_object :paged_filter do\n @desc \"limit of records (default 20), if first > 30, only return 30 at most\"\n pagination_args()\n field(:sort, :sort_enum)\n end\n\n @desc \"article_filter doc\"\n input_object :article_filter do\n @desc \"limit of records (default 20), if first > 30, only return 30 at most\"\n field(:first, :integer)\n\n @desc \"Matching a tag\"\n field(:article_tag, :string, default_value: :all)\n # field(:sort, :sort_input)\n field(:when, :when_enum)\n field(:sort, :sort_enum)\n # @desc \"Matching a tag\"\n # @desc \"Added to the menu after this date\"\n # field(:added_after, :datetime)\n end\n\n @desc \"article_filter doc\"\n input_object :paged_article_filter do\n @desc \"limit of records (default 20), if first > 30, only return 30 at most\"\n pagination_args()\n\n field(:when, :when_enum)\n field(:sort, :sort_enum)\n field(:article_tag, :string, default_value: :all)\n field(:community, :string)\n\n # @desc \"Matching a name\"\n # field(:order, :order_enum, default_value: :desc)\n\n # @desc \"Matching a tag\"\n # field(:article_tag, :string, default_value: :all)\n end\n\n @doc \"\"\"\n only used for reaction result, like: favorite/star/watch ...\n \"\"\"\n interface :article do\n field(:id, :id)\n field(:title, :string)\n\n resolve_type(fn\n %CMS.Post{}, _ -> :post\n _, _ -> nil\n end)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,3,2,0,null,null,252,null,null,99,4,3,3,null,null,12,null,null,30,null,null,28,25,null,null,2,4,6,null,null,0,4,3,null,null,6,4,null],"name":"lib/groupher_server/accounts/accounts.ex","source":"defmodule GroupherServer.Accounts do\n @moduledoc false\n\n alias GroupherServer.Accounts.Delegate.{\n Achievements,\n Billing,\n Customization,\n Fans,\n Mails,\n Profile,\n ReactedContents\n }\n\n # profile\n defdelegate update_profile(user, attrs), to: Profile\n defdelegate github_signin(github_user), to: Profile\n defdelegate default_subscribed_communities(filter), to: Profile\n defdelegate subscribed_communities(user, filter), to: Profile\n\n # achievement\n defdelegate achieve(user, operation, key), to: Achievements\n\n # fans\n defdelegate follow(user, follower), to: Fans\n defdelegate undo_follow(user, follower), to: Fans\n defdelegate fetch_followers(user, filter), to: Fans\n defdelegate fetch_followings(user, filter), to: Fans\n\n # reacted contents\n defdelegate reacted_contents(thread, react, filter, user), to: ReactedContents\n\n # mentions\n defdelegate fetch_mentions(user, filter), to: Mails\n\n # notifications\n defdelegate fetch_notifications(user, filter), to: Mails\n defdelegate fetch_sys_notifications(user, filter), to: Mails\n\n # common message\n defdelegate mailbox_status(user), to: Mails\n defdelegate mark_mail_read_all(user, opt), to: Mails\n defdelegate mark_mail_read(mail, user), to: Mails\n\n # purchase\n defdelegate purchase_service(user, key, value), to: Billing\n defdelegate purchase_service(user, key), to: Billing\n defdelegate has_purchased?(user, key), to: Billing\n\n # customization\n defdelegate add_custom_setting(user, key, value), to: Customization\n defdelegate add_custom_setting(user, key), to: Customization\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,18,null,null,null,10,null,null,null,31,31,31,null,null,31,31,null,null,null,null,null,1,1,1,null,null,1,1,null,null,null,null,null,null,null,null,10,10,10,10,null,null,null,8,8,null,null,null,null,null,null,null,null,1,1,1,null,null,1,1,null,null,null,null,null,null,10,10,null,10,null,10,null,null,10,null,null,null],"name":"lib/groupher_server/cms/delegates/article_operation.ex","source":"defmodule GroupherServer.CMS.Delegate.ArticleCommunity do\n @moduledoc \"\"\"\n set / unset operations for Article-like resource\n \"\"\"\n import GroupherServer.CMS.Helper.MatcherOld\n import Ecto.Query, warn: false\n import Helper.ErrorCode\n\n alias Helper.ORM\n alias GroupherServer.Accounts.User\n alias GroupherServer.CMS.{Community, Tag}\n alias GroupherServer.Repo\n\n @doc \"\"\"\n pin / unpin, markDelete / untrash articles\n \"\"\"\n def set_flag(queryable, id, %{pin: _} = attrs, %User{} = _user) do\n queryable |> ORM.find_update(id, attrs)\n end\n\n def set_flag(queryable, id, %{markDelete: _} = attrs, %User{} = _user) do\n queryable |> ORM.find_update(id, attrs)\n end\n\n def mirror_article(%Community{id: community_id}, thread, content_id) when valid_thread(thread) do\n with {:ok, action} <- match_action(thread, :community),\n {:ok, content} <- ORM.find(action.target, content_id, preload: :communities),\n {:ok, community} <- ORM.find(action.reactor, community_id) do\n content\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.put_assoc(:communities, content.communities ++ [community])\n |> Repo.update()\n end\n end\n\n def unmirror_article(%Community{id: community_id}, thread, content_id)\n when valid_thread(thread) do\n with {:ok, action} <- match_action(thread, :community),\n {:ok, content} <- ORM.find(action.target, content_id, preload: :communities),\n {:ok, community} <- ORM.find(action.reactor, community_id) do\n content\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.put_assoc(:communities, content.communities -- [community])\n |> Repo.update()\n end\n end\n\n @doc \"\"\"\n set tag for post / tuts / videos ...\n \"\"\"\n # check community first\n def set_tag(%Community{id: communitId}, thread, %Tag{id: tag_id}, content_id) do\n with {:ok, action} <- match_action(thread, :article_tag),\n {:ok, content} <- ORM.find(action.target, content_id, preload: :tags),\n {:ok, tag} <- ORM.find(action.reactor, tag_id) do\n case tag_in_community_thread?(%Community{id: communitId}, thread, tag) do\n true ->\n content\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.put_assoc(:tags, content.tags ++ [tag])\n |> Repo.update()\n\n _ ->\n {:error, message: \"Tag,Community,Thread not match\", code: ecode(:custom)}\n end\n end\n end\n\n def unset_tag(thread, %Tag{id: tag_id}, content_id) when valid_thread(thread) do\n with {:ok, action} <- match_action(thread, :article_tag),\n {:ok, content} <- ORM.find(action.target, content_id, preload: :tags),\n {:ok, tag} <- ORM.find(action.reactor, tag_id) do\n content\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.put_assoc(:tags, content.tags -- [tag])\n |> Repo.update()\n end\n end\n\n # make sure the reuest tag is in the current community thread\n # example: you can't set a other thread tag to this thread's article\n defp tag_in_community_thread?(%Community{id: communityId}, thread, tag) do\n with {:ok, community} <- ORM.find(Community, communityId) do\n matched_tags =\n Tag\n |> where([t], t.community_id == ^community.id)\n # |> where([t], t.thread == ^(to_string(thread) |> String.upcase()))\n |> where([t], t.thread == ^to_string(thread))\n |> Repo.all()\n\n tag in matched_tags\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1,0,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/endpoint.ex","source":"defmodule GroupherServerWeb.Endpoint do\n use Phoenix.Endpoint, otp_app: :groupher_server\n\n socket(\"/socket\", GroupherServerWeb.UserSocket)\n\n plug(Plug.RequestId)\n plug(Plug.Logger)\n\n plug(\n Plug.Parsers,\n parsers: [:urlencoded, :multipart, :json],\n pass: [\"*/*\"],\n json_decoder: Jason\n )\n\n plug(Plug.MethodOverride)\n plug(Plug.Head)\n\n # plug(:inspect_conn)\n\n plug(\n Corsica,\n # log: [rejected: :error],\n log: [rejected: :debug],\n origins: \"*\",\n allow_headers: [\n \"authorization\",\n \"content-type\",\n \"special\",\n \"accept\",\n \"origin\",\n \"x-requested-with\"\n ],\n allow_credentials: true\n )\n\n plug(GroupherServerWeb.Router)\n\n @doc \"\"\"\n Callback invoked for dynamically configuring the endpoint.\n\n It receives the endpoint configuration and checks if\n configuration should be loaded from the system environment.\n \"\"\"\n def init(_key, config) do\n if config[:load_from_system_env] do\n port = System.get_env(\"PORT\") || raise \"expected the PORT environment variable to be set\"\n {:ok, Keyword.put(config, :http, [:inet6, port: port])}\n else\n {:ok, config}\n end\n end\n\n # defp inspect_conn(conn, _), do: IO.inspect(conn)\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/cms/repo_builder.ex","source":"defmodule GroupherServer.CMS.RepoBuilder do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n @required_fields ~w(nickname avatar link)a\n @optional_fields ~w(bio)\n\n @type t :: %RepoBuilder{}\n schema \"cms_repo_users\" do\n field(:nickname, :string)\n field(:avatar, :string)\n field(:link, :string)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%RepoBuilder{} = repo_builder, attrs) do\n repo_builder\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/account/account_types.ex","source":"defmodule GroupherServerWeb.Schema.Account.Types do\n use Helper.GqlSchemaSuite\n\n import GroupherServerWeb.Schema.Helper.Fields\n import Absinthe.Resolution.Helpers\n\n alias GroupherServer.Accounts\n alias GroupherServerWeb.Schema\n\n import_types(Schema.Account.Misc)\n\n object :user do\n field(:id, :id)\n field(:nickname, :string)\n field(:avatar, :string)\n field(:bio, :string)\n field(:sex, :string)\n field(:email, :string)\n field(:location, :string)\n field(:education, :string)\n field(:company, :string)\n field(:qq, :string)\n field(:weibo, :string)\n field(:weichat, :string)\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n field(:from_github, :boolean)\n field(:github_profile, :github_profile, resolve: dataloader(Accounts, :github_profile))\n field(:achievement, :achievement, resolve: dataloader(Accounts, :achievement))\n\n field(:cms_passport_string, :string) do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.get_passport_string/3)\n end\n\n field(:cms_passport, :json) do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.get_passport/3)\n end\n\n field :subscribed_communities, list_of(:community) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(Accounts, :subscribed_communities))\n end\n\n field :subscribed_communities_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :subscribed_communities))\n middleware(M.ConvertToInt)\n end\n\n field :followers_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :followers))\n middleware(M.ConvertToInt)\n end\n\n field :followings_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :followings))\n middleware(M.ConvertToInt)\n end\n\n field :viewer_has_followed, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n middleware(M.PutCurrentUser)\n resolve(dataloader(Accounts, :followers))\n middleware(M.ViewerDidConvert)\n end\n\n field :favorited_posts, :paged_posts do\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.favorited_posts/3)\n end\n\n field :favorited_jobs, :paged_jobs do\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.favorited_jobs/3)\n end\n\n field :favorited_posts_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :favorited_posts))\n middleware(M.ConvertToInt)\n end\n\n field :favorited_jobs_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :favorited_jobs))\n middleware(M.ConvertToInt)\n end\n\n field :contributes, :contribute_map do\n resolve(&R.Statistics.list_contributes/3)\n end\n\n # TODO, for msg-bell UI\n # field :has_messges,\n # 1. has_mentions ?\n # 2. has_system_messages ?\n # 3. has_notifications ?\n # 4. has_watches ?\n\n field :mail_box, :mail_box_status do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.get_mail_box_status/3)\n end\n\n field :mentions, :paged_mentions do\n arg(:filter, :messages_filter)\n\n middleware(M.Authorize, :login)\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.fetch_mentions/3)\n end\n\n field :notifications, :paged_notifications do\n arg(:filter, :messages_filter)\n\n middleware(M.Authorize, :login)\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.fetch_notifications/3)\n end\n\n field :sys_notifications, :paged_sys_notifications do\n arg(:filter, :messages_filter)\n\n middleware(M.Authorize, :login)\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.fetch_sys_notifications/3)\n end\n end\n\n object :github_profile do\n field(:id, :id)\n field(:github_id, :string)\n # field(:user, :user, resolve: dataloader(Accounts, :user))\n field(:login, :string)\n field(:avatar_url, :string)\n field(:url, :string)\n field(:html_url, :string)\n field(:name, :string)\n field(:company, :string)\n field(:blog, :string)\n field(:location, :string)\n field(:email, :string)\n field(:bio, :string)\n field(:public_repos, :integer)\n field(:public_gists, :integer)\n end\n\n object :achievement do\n field(:reputation, :integer)\n field(:followers_count, :integer)\n field(:contents_stared_count, :integer)\n field(:contents_favorited_count, :integer)\n field(:contents_watched_count, :integer)\n end\n\n object :token_info do\n field(:token, :string)\n field(:user, :user)\n end\n\n object :rules do\n field(:cms, :json)\n end\n\n object :paged_users do\n field(:entries, list_of(:user))\n pagination_fields()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,39,null,39,39,null,null,null,null,null,152,null,152,152,null,null,null,null,null,10,null,10,10,null,null,null,null,null,26,26,26,null,null,null,12,12,12,null,null,null,2,null,null,null,null,null,null,null,null,null,null,null,98,null,null,null,null,null,null,null,null,null,null,9,null,null,null,null,null,null,87,null,null,null,null,null,null,87,null,87,null,null,null,null,null,null,null,null,null,null,null,null,151,null,null,null,null,null,null,null,0,null,null,null,null,null,null,3,null,null,null,null,null,null,13,null,null,null,null,13,null,13,null,null,null,null,null,null,null,6,6,null,null,null,null],"name":"test/support/assert_helper.ex","source":"defmodule GroupherServer.Test.AssertHelper do\n @moduledoc \"\"\"\n This module defines some helper function used by\n tests that require check from graphql response\n \"\"\"\n\n import Phoenix.ConnTest\n import Helper.Utils, only: [map_key_stringify: 1, get_config: 2]\n\n @endpoint GroupherServerWeb.Endpoint\n\n @page_size get_config(:general, :page_size)\n @inner_page_size get_config(:general, :inner_page_size)\n\n @doc \"\"\"\n used for non exsit id\n \"\"\"\n def non_exsit_id, do: 15_982_398_614\n def inner_page_size, do: @inner_page_size\n def page_size, do: @page_size\n\n def is_valid_kv?(obj, key, :list) when is_map(obj) do\n obj = map_key_stringify(obj)\n\n case Map.has_key?(obj, key) do\n true -> obj |> Map.get(key) |> is_list\n _ -> false\n end\n end\n\n def is_valid_kv?(obj, key, :int) when is_map(obj) do\n obj = map_key_stringify(obj)\n\n case Map.has_key?(obj, key) do\n true -> obj |> Map.get(key) |> is_integer\n _ -> false\n end\n end\n\n def is_valid_kv?(obj, key, :string) when is_map(obj) and is_binary(key) do\n obj = map_key_stringify(obj)\n\n case Map.has_key?(obj, key) do\n true -> String.length(Map.get(obj, key)) != 0\n _ -> false\n end\n end\n\n def is_valid_pagination?(obj) when is_map(obj) do\n is_valid_kv?(obj, \"entries\", :list) and is_valid_kv?(obj, \"totalPages\", :int) and\n is_valid_kv?(obj, \"totalCount\", :int) and is_valid_kv?(obj, \"pageSize\", :int) and\n is_valid_kv?(obj, \"pageNumber\", :int)\n end\n\n def is_valid_pagination?(obj, :raw) when is_map(obj) do\n is_valid_kv?(obj, \"entries\", :list) and is_valid_kv?(obj, \"total_pages\", :int) and\n is_valid_kv?(obj, \"total_count\", :int) and is_valid_kv?(obj, \"page_size\", :int) and\n is_valid_kv?(obj, \"page_number\", :int)\n end\n\n def has_boolen_value?(obj, key) do\n obj |> Map.get(key) |> is_boolean\n end\n\n @doc \"\"\"\n simulate the Graphiql murate operation\n \"\"\"\n def mutation_result(conn, query, variables, key) do\n conn\n |> post(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n # |> IO.inspect(label: \"debug\")\n |> Map.get(\"data\")\n |> Map.get(key)\n end\n\n @doc \"\"\"\n check if Graphiql murate get error\n \"\"\"\n def mutation_get_error?(conn, query, variables) do\n conn\n |> post(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n # |> IO.inspect(label: \"debug\")\n |> Map.has_key?(\"errors\")\n end\n\n @doc \"\"\"\n Graphiql murate error with code equal check\n \"\"\"\n def mutation_get_error?(conn, query, variables, code) when is_integer(code) do\n resp =\n conn\n |> post(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n\n # |> IO.inspect(label: \"debug\")\n\n case resp |> Map.has_key?(\"errors\") do\n true ->\n code == resp[\"errors\"] |> List.first() |> Map.get(\"code\")\n\n false ->\n false\n end\n end\n\n def query_result(conn, query, variables, key) do\n conn\n |> get(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n # |> IO.inspect(label: \"debug\")\n |> Map.get(\"data\")\n |> Map.get(key)\n end\n\n def query_result(conn, query, key) do\n conn\n |> get(\"/graphiql\", query: query, variables: %{})\n |> json_response(200)\n |> Map.get(\"data\")\n |> Map.get(key)\n end\n\n def query_get_error?(conn, query, variables) do\n conn\n |> get(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n |> Map.has_key?(\"errors\")\n end\n\n @doc \"\"\"\n check if Graphiql murate get error\n \"\"\"\n def query_get_error?(conn, query, variables, code) when is_integer(code) do\n resp =\n conn\n |> get(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n\n case resp |> Map.has_key?(\"errors\") do\n true ->\n code == resp[\"errors\"] |> List.first() |> Map.get(\"code\")\n\n false ->\n false\n end\n end\n\n def firstn_and_last(values, 3) do\n [value_1 | [value_2 | [value_3 | _]]] = values\n value_x = values |> List.last()\n\n [value_1, value_2, value_3, value_x]\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/utils/helper.ex","source":"defmodule GroupherServerWeb.Schema.Helper.Fields do\n import Helper.Utils, only: [get_config: 2]\n @page_size get_config(:general, :page_size)\n # @default_inner_page_size 5\n\n # see: https://github.com/absinthe-graphql/absinthe/issues/363\n defmacro pagination_args() do\n quote do\n field(:page, :integer, default_value: 1)\n field(:size, :integer, default_value: unquote(@page_size))\n end\n end\n\n defmacro pagination_fields() do\n quote do\n field(:total_count, :integer)\n field(:page_size, :integer)\n field(:total_pages, :integer)\n field(:page_number, :integer)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,14,1,null,null,24,null,null,null,1,null,null,null,0,null,null,null,5,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,2,null,null,null,2,null,null,null,2,null,null,null,2,null,null,null,null,2,null,null,null,null,20,null,null,null,1,null,null,null,1,null,null,null,null,20,null,null,null,20,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,0,null,null,null,null,1,null,null,null,null,0,null,null,null,1,null,null,null,2,null,null,null,2,null,null,null,null,null,null,null,null,null,1,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/resolvers/accounts_resolver.ex","source":"defmodule GroupherServerWeb.Resolvers.Accounts do\n @moduledoc \"\"\"\n accounts resolvers\n \"\"\"\n import ShortMaps\n\n alias Helper.{Certification, ORM}\n alias GroupherServer.{Accounts, CMS}\n\n alias Accounts.{MentionMail, NotificationMail, SysNotificationMail, User}\n\n def user(_root, %{id: id}, _info), do: User |> ORM.find(id)\n def users(_root, ~m(filter)a, _info), do: User |> ORM.find_all(filter)\n\n def account(_root, _args, %{context: %{cur_user: cur_user}}) do\n User |> ORM.find(cur_user.id)\n end\n\n def update_profile(_root, %{profile: profile}, %{context: %{cur_user: cur_user}}) do\n Accounts.update_profile(%User{id: cur_user.id}, profile)\n end\n\n def github_signin(_root, %{github_user: github_user}, _info) do\n Accounts.github_signin(github_user)\n end\n\n def follow(_root, ~m(user_id)a, %{context: %{cur_user: cur_user}}) do\n Accounts.follow(cur_user, %User{id: user_id})\n end\n\n def undo_follow(_root, ~m(user_id)a, %{context: %{cur_user: cur_user}}) do\n Accounts.undo_follow(cur_user, %User{id: user_id})\n end\n\n def paged_followers(_root, ~m(user_id filter)a, _info) do\n Accounts.fetch_followers(%User{id: user_id}, filter)\n end\n\n def paged_followers(_root, ~m(filter)a, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_followers(cur_user, filter)\n end\n\n def paged_followings(_root, ~m(user_id filter)a, _info) do\n Accounts.fetch_followings(%User{id: user_id}, filter)\n end\n\n def paged_followings(_root, ~m(filter)a, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_followings(cur_user, filter)\n end\n\n # for check other users query\n def favorited_posts(_root, ~m(user_id filter)a, _info) do\n Accounts.reacted_contents(:post, :favorite, filter, %User{id: user_id})\n end\n\n def favorited_posts(_root, ~m(filter)a, %{context: %{cur_user: cur_user}}) do\n Accounts.reacted_contents(:post, :favorite, filter, cur_user)\n end\n\n def favorited_jobs(_root, ~m(user_id filter)a, _info) do\n Accounts.reacted_contents(:job, :favorite, filter, %User{id: user_id})\n end\n\n def favorited_jobs(_root, ~m(filter)a, %{context: %{cur_user: cur_user}}) do\n Accounts.reacted_contents(:job, :favorite, filter, cur_user)\n end\n\n # TODO: refactor\n def get_mail_box_status(_root, _args, %{context: %{cur_user: cur_user}}) do\n Accounts.mailbox_status(cur_user)\n end\n\n # mentions\n def fetch_mentions(_root, %{filter: filter}, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_mentions(cur_user, filter)\n end\n\n def mark_mention_read(_root, %{id: id}, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read(%MentionMail{id: id}, cur_user)\n end\n\n def mark_mention_read_all(_root, _args, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read_all(cur_user, :mention)\n end\n\n # notification\n def fetch_notifications(_root, %{filter: filter}, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_notifications(cur_user, filter)\n end\n\n def fetch_sys_notifications(_root, %{filter: filter}, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_sys_notifications(cur_user, filter)\n end\n\n def mark_notification_read(_root, %{id: id}, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read(%NotificationMail{id: id}, cur_user)\n end\n\n def mark_notification_read_all(_root, _args, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read_all(cur_user, :notification)\n end\n\n def mark_sys_notification_read(_root, %{id: id}, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read(%SysNotificationMail{id: id}, cur_user)\n end\n\n # for user self's\n def subscribed_communities(_root, %{filter: filter}, %{cur_user: cur_user}) do\n Accounts.subscribed_communities(%User{id: cur_user.id}, filter)\n end\n\n #\n def subscribed_communities(_root, %{user_id: \"\", filter: filter}, _info) do\n Accounts.default_subscribed_communities(filter)\n end\n\n # for check other users subscribed_communities\n def subscribed_communities(_root, %{user_id: user_id, filter: filter}, _info) do\n Accounts.subscribed_communities(%User{id: user_id}, filter)\n end\n\n def subscribed_communities(_root, %{filter: filter}, _info) do\n Accounts.default_subscribed_communities(filter)\n end\n\n def get_passport(root, _args, %{context: %{cur_user: _}}) do\n CMS.get_passport(%User{id: root.id})\n end\n\n def get_passport_string(root, _args, %{context: %{cur_user: _}}) do\n case CMS.get_passport(%User{id: root.id}) do\n {:ok, passport} ->\n {:ok, Jason.encode!(passport)}\n\n {:error, _} ->\n {:ok, nil}\n end\n end\n\n def get_all_rules(_root, _args, %{context: %{cur_user: _}}) do\n cms_rules = Certification.all_rules(:cms, :stringify)\n\n {:ok,\n %{\n cms: cms_rules\n }}\n end\n\n # def create_user(_root, args, %{context: %{cur_user: %{root: true}}}) do\n # Accounts.create_user2(args)\n # end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/logs/logs.ex","source":"defmodule GroupherServer.Logs do\n @moduledoc \"\"\"\n The Logs context.\n \"\"\"\n\n # import Ecto.Query, warn: false\n # alias GroupherServer.Repo\n\n # alias GroupherServer.Logs.UserActivity\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/statistics/statistics_types.ex","source":"defmodule GroupherServerWeb.Schema.Statistics.Types do\n use Absinthe.Schema.Notation\n use Absinthe.Ecto, repo: GroupherServer.Repo\n\n # import Absinthe.Resolution.Helpers\n\n # alias GroupherServer.Accounts\n\n object :user_contribute do\n field(:count, :integer)\n field(:date, :date)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,5,5,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,null,1,null,1,null,null,null,2,null,null,null,null,null,null,null,2,2,null,null,null,null,null,null,null,0,null,0,null,null,0,null,null,null,null,null,2,null,null,2,null,null,2,null,null,2,null,null,null,null,null,null,null,null,3,null,null,null,null,null,2,null,null,null,null,null,null,null,null,null,2,null,null,null,null,2,null,2,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/delegates/profile.ex","source":"defmodule GroupherServer.Accounts.Delegate.Profile do\n @moduledoc \"\"\"\n accounts profile\n \"\"\"\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1, get_config: 2]\n import ShortMaps\n\n alias Helper.{Guardian, ORM, QueryBuilder}\n alias GroupherServer.Accounts.{GithubUser, User}\n alias GroupherServer.{CMS, Repo}\n\n alias Ecto.Multi\n\n @default_subscribed_communities get_config(:general, :default_subscribed_communities)\n\n def update_profile(%User{id: id}, attrs \\\\ %{}) do\n with {:ok, user} <- ORM.find(User, id) do\n case user.id === id do\n true -> user |> ORM.update(attrs)\n false -> {:error, \"Error: not qualified\"}\n end\n end\n end\n\n @doc \"\"\"\n github_signin steps:\n ------------------\n step 0: get access_token is enough, even profile is not need?\n step 1: check is access_token valid or not, think use a Middleware\n step 2.1: if access_token's github_id exsit, then login\n step 2.2: if access_token's github_id not exsit, then signup\n step 3: return groupher token\n \"\"\"\n def github_signin(github_user) do\n case ORM.find_by(GithubUser, github_id: to_string(github_user[\"id\"])) do\n {:ok, g_user} ->\n {:ok, user} = ORM.find(User, g_user.user_id)\n # IO.inspect label: \"send back from db\"\n token_info(user)\n\n {:error, _} ->\n # IO.inspect label: \"register then send\"\n register_github_user(github_user)\n end\n end\n\n @doc \"\"\"\n get default subscribed communities for unlogin user\n \"\"\"\n def default_subscribed_communities(%{page: _, size: _} = filter) do\n filter = Map.merge(filter, %{size: @default_subscribed_communities})\n CMS.Community |> ORM.find_all(filter)\n end\n\n @doc \"\"\"\n get users subscribed communities\n \"\"\"\n def subscribed_communities(%User{id: id}, %{page: page, size: size} = filter) do\n CMS.CommunitySubscriber\n |> where([c], c.user_id == ^id)\n |> join(:inner, [c], cc in assoc(c, :community))\n |> select([c, cc], cc)\n |> QueryBuilder.filter_pack(filter)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n defp register_github_user(github_profile) do\n Multi.new()\n |> Multi.run(:create_user, fn _ ->\n create_user(github_profile, :github)\n end)\n |> Multi.run(:create_profile, fn %{create_user: user} ->\n create_profile(user, github_profile, :github)\n end)\n |> Repo.transaction()\n |> register_github_result()\n end\n\n defp register_github_result({:ok, %{create_user: user}}), do: token_info(user)\n\n defp register_github_result({:error, :create_user, _result, _steps}),\n do: {:error, \"Accounts create_user internal error\"}\n\n defp register_github_result({:error, :create_profile, _result, _steps}),\n do: {:error, \"Accounts create_profile internal error\"}\n\n defp token_info(%User{} = user) do\n with {:ok, token, _info} <- Guardian.jwt_encode(user) do\n {:ok, %{token: token, user: user}}\n end\n end\n\n defp create_user(user, :github) do\n user = %User{\n nickname: user[\"login\"],\n avatar: user[\"avatar_url\"],\n bio: user[\"bio\"],\n location: user[\"location\"],\n email: user[\"email\"],\n company: user[\"company\"],\n from_github: true\n }\n\n Repo.insert(user)\n end\n\n defp create_profile(user, github_profile, :github) do\n # attrs = github_user |> Map.merge(%{github_id: github_user.id, user_id: 1}) |> Map.delete(:id)\n attrs =\n github_profile\n |> Map.merge(%{\"github_id\" => to_string(github_profile[\"id\"]), \"user_id\" => user.id})\n # |> Map.merge(%{\"github_id\" => github_profile[\"id\"], \"user_id\" => user.id})\n |> Map.delete(\"id\")\n\n %GithubUser{}\n |> GithubUser.changeset(attrs)\n |> Repo.insert()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,null,null,190,null,null,null,null,3,3,null,3,null,0,0,0,null,null,null,3,3,3,null,null,null,null,null,null,3,null,0,null,null,null,3,null,0,null,null,null,null,null,null,3,null,null,null,null,null],"name":"lib/groupher_server_web/middleware/changeset_errors.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.ChangesetErrors do\n @behaviour Absinthe.Middleware\n import Helper.Utils, only: [handle_absinthe_error: 3]\n import Helper.ErrorCode\n\n alias GroupherServerWeb.Gettext, as: Translator\n\n def call(%{errors: [%Ecto.Changeset{} = changeset]} = resolution, _) do\n # IO.inspect changeset, label: \"Changeset error\"\n # IO.inspect transform_errors(changeset), label: \"transform_errors\"\n resolution\n |> handle_absinthe_error(transform_errors(changeset), ecode(:changeset))\n end\n\n def call(resolution, _), do: resolution\n\n defp transform_errors(changeset) do\n changeset\n |> Ecto.Changeset.traverse_errors(&format_error/1)\n |> Enum.map(fn {key, err_msg_list} ->\n err_msg = err_msg_list |> List.first()\n\n cond do\n Map.has_key?(err_msg, :count) ->\n %{\n key: Translator |> Gettext.dgettext(\"fields\", \"#{key}\"),\n message: Translator |> Gettext.dgettext(\"errors\", err_msg.raw, count: err_msg.count)\n }\n\n true ->\n %{\n key: Translator |> Gettext.dgettext(\"fields\", \"#{key}\"),\n message: Translator |> Gettext.dgettext(\"errors\", err_msg.msg)\n }\n end\n end)\n end\n\n defp format_error({msg, opts}) do\n err_string =\n Enum.reduce(opts, msg, fn {key, value}, acc ->\n String.replace(acc, \"%{#{key}}\", to_string(value))\n end)\n\n # TODO handle: number type\n cond do\n String.contains?(msg, \"%{count}\") ->\n %{\n msg: err_string,\n count: Keyword.get(opts, :count),\n raw: msg\n }\n\n true ->\n %{\n msg: err_string\n }\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/delivery/delivery_types.ex","source":"defmodule GroupherServerWeb.Schema.Delivery.Types do\n use Absinthe.Schema.Notation\n use Absinthe.Ecto, repo: GroupherServer.Repo\n\n import GroupherServerWeb.Schema.Helper.Fields\n import Helper.Utils, only: [get_config: 2]\n\n @page_size get_config(:general, :page_size)\n\n object :mail_box_status do\n field(:has_mail, :boolean)\n field(:total_count, :integer)\n field(:mention_count, :integer)\n field(:notification_count, :integer)\n end\n\n object :mention do\n field(:id, :id)\n field(:from_user_id, :id)\n field(:to_user_id, :id)\n\n field(:source_title, :string)\n field(:read, :boolean)\n end\n\n object :sys_notification do\n field(:id, :id)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_preview, :string)\n field(:source_type, :string)\n\n field(:read, :boolean)\n end\n\n object :notification do\n field(:id, :id)\n field(:from_user_id, :id)\n field(:to_user_id, :id)\n field(:action, :string)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_preview, :string)\n field(:source_type, :string)\n field(:read, :boolean)\n end\n\n object :sys_notification do\n field(:id, :id)\n field(:user_id, :id)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_preview, :string)\n field(:source_type, :string)\n field(:read, :boolean)\n end\n\n object :paged_mentions do\n field(:entries, list_of(:mention))\n pagination_fields()\n end\n\n object :paged_notifications do\n field(:entries, list_of(:notification))\n pagination_fields()\n end\n\n object :paged_sys_notifications do\n field(:entries, list_of(:sys_notification))\n pagination_fields()\n end\n\n input_object :messages_filter do\n field(:read, :boolean, default_value: false)\n\n field(:page, :integer, default_value: 1)\n field(:size, :integer, default_value: @page_size)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,26,null,3,null,null,4,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,7,null,null,null,null,null,null,null,null,null,20,null,null,null,null,null,null,null,null,null,8,null,null,null,null,null,null,37,null,null,null,null,21,null,null,null,null,72,null,18,null,54,7,null,47,10,null,37,37,null,null,null,0,null,null,null,null,37,37,null,37,null,24,null,null,null,13,null,null,null,null,7,7,null,7,null,null,null,null,7,null,6,null,null,null,1,null,null,null,null,18,18,null,18,null,18,null,null,null,null,null,18,null,16,null,null,null,2,null,null,null,null,10,10,null,10,null,null,15,15,null,null,null,10,null,7,null,null,null,3,null,null,null],"name":"lib/groupher_server_web/middleware/passport.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\n# RBAC vs CBAC\n# https://stackoverflow.com/questions/22814023/role-based-access-control-rbac-vs-claims-based-access-control-cbac-in-asp-n\n\n# 本中间件会隐式的加载 community 的 rules 信息,并应用该 rules 信息\ndefmodule GroupherServerWeb.Middleware.Passport do\n @moduledoc \"\"\"\n c? -> community / communities\n t? -> thread, could be post / job / tut / video ...\n \"\"\"\n @behaviour Absinthe.Middleware\n\n import Helper.Utils\n import Helper.ErrorCode\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(%{arguments: %{passport_is_owner: true}} = resolution, claim: \"owner\"), do: resolution\n\n def call(%{arguments: %{passport_is_owner: true}} = resolution, claim: \"owner;\" <> _rest),\n do: resolution\n\n def call(\n %{\n context: %{cur_user: %{cur_passport: _}},\n arguments: %{community: _, thread: _}\n } = resolution,\n claim: \"cms->c?->t?.\" <> _rest = claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(\n %{\n context: %{cur_user: %{cur_passport: _}},\n arguments: %{thread: _}\n } = resolution,\n claim: \"cms->t?.\" <> _rest = claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(\n %{\n context: %{cur_user: %{cur_passport: _}},\n arguments: %{passport_communities: _}\n } = resolution,\n claim: \"cms->c?->\" <> _rest = claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(\n %{\n context: %{cur_user: %{cur_passport: _}},\n arguments: %{passport_communities: _}\n } = resolution,\n claim: \"owner;\" <> claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(\n %{context: %{cur_user: %{cur_passport: _}}} = resolution,\n claim: \"cms->\" <> _rest = claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(resolution, _) do\n resolution\n |> handle_absinthe_error(\"PassportError: your passport not qualified.\", ecode(:passport))\n end\n\n defp check_passport_stamp(resolution, claim) do\n # TODO: refactor\n cond do\n claim |> String.starts_with?(\"cms->c?->t?.\") ->\n resolution |> cp_check(claim)\n\n claim |> String.starts_with?(\"cms->t?.\") ->\n resolution |> p_check(claim)\n\n claim |> String.starts_with?(\"cms->c?->\") ->\n resolution |> c_check(claim)\n\n claim |> String.starts_with?(\"cms->\") ->\n resolution |> do_check(claim)\n\n true ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\n\n defp do_check(resolution, claim) do\n cur_passport = resolution.context.cur_user.cur_passport\n path = claim |> String.split(\"->\")\n\n case get_in(cur_passport, path) do\n true ->\n resolution\n\n nil ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\n\n defp p_check(resolution, claim) do\n cur_passport = resolution.context.cur_user.cur_passport\n thread = resolution.arguments.thread |> to_string\n\n path =\n claim\n |> String.replace(\"t?\", thread)\n |> String.split(\"->\")\n\n case get_in(cur_passport, path) do\n true ->\n resolution\n\n nil ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\n\n defp cp_check(resolution, claim) do\n cur_passport = resolution.context.cur_user.cur_passport\n community_title = resolution.arguments.passport_communities |> List.first() |> Map.get(:title)\n\n thread = resolution.arguments.thread |> to_string\n\n path =\n claim\n |> String.replace(\"c?\", community_title)\n |> String.replace(\"t?\", thread)\n |> String.split(\"->\")\n\n case get_in(cur_passport, path) do\n true ->\n resolution\n\n nil ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\n\n defp c_check(resolution, claim) do\n cur_passport = resolution.context.cur_user.cur_passport\n communities = resolution.arguments.passport_communities\n\n result =\n communities\n |> Enum.filter(fn community ->\n path = claim |> String.replace(\"c?\", community.title) |> String.split(\"->\")\n get_in(cur_passport, path) == true\n end)\n |> length\n\n case result > 0 do\n true ->\n resolution\n\n false ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,148,null,null,null,null,null,null,null,null,null,null,null,73,null,null],"name":"lib/groupher_server/cms/post_favorite.ex","source":"defmodule GroupherServer.CMS.PostFavorite do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Post\n\n @required_fields ~w(user_id post_id)a\n\n @type t :: %PostFavorite{}\n schema \"posts_favorites\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:post, Post, foreign_key: :post_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostFavorite{} = post_favorite, attrs) do\n post_favorite\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:user_id, name: :posts_favorites_user_id_post_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,2806,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/cms/author.ex","source":"defmodule GroupherServer.CMS.Author do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Post\n\n @type t :: %Author{}\n\n schema \"cms_authors\" do\n field(:role, :string)\n # field(:user_id, :id)\n has_many(:posts, Post)\n # user_id filed in own-table\n belongs_to(:user, Accounts.User)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Author{} = author, attrs) do\n # |> foreign_key_constraint(:user_id)\n author\n |> cast(attrs, [:role])\n |> validate_required([:role])\n |> unique_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,104,null,null,null,null,null,null,null,null,null,null,null,null,null,97,null,null],"name":"lib/groupher_server/accounts/user_following.ex","source":"defmodule GroupherServer.Accounts.UserFollowing do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id following_id)a\n\n @type t :: %UserFollowing{}\n schema \"users_followings\" do\n belongs_to(:user, User, foreign_key: :user_id)\n belongs_to(:following, User, foreign_key: :following_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%UserFollowing{} = user_following, attrs) do\n user_following\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> foreign_key_constraint(:following_id)\n |> unique_constraint(:user_id, name: :users_followers_user_id_following_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,361,null,null,null,null,0,0,null,null,null,null,null,null,null,0,null,null,null,null,0,null,null,null,0,null,null,null,null,null,0,null,0,null,null,null,0,null,null,null,null,null,0,0,null,null,null,null,null,0,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,0,null,null,null,0,0,null,null,0,null,null,null,null,null,null,null,12,null,null,null,3,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,0,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,2,null,null,null,1,null,null,null,6,null,null,null,2,null,null,null,null,null,null,1,null,null,null,null,2,null,null,null,null,null,0,null,null,null,null,null,4,null,null,null,2,null,null,null,null,1,null,null,null,null,null,4,null,null,null,null,0,null,null,null,null,1,null,null,null,null,null,null,1,null,null,null,null,2,null,null,null,null,null,0,null,null,null,null,null,null,null,null,26,null,null],"name":"lib/groupher_server/cms/utils/loader.ex","source":"defmodule GroupherServer.CMS.Helper.Loader do\n @moduledoc \"\"\"\n dataloader for cms context\n \"\"\"\n import Ecto.Query, warn: false\n\n alias Helper.QueryBuilder\n alias GroupherServer.Repo\n # alias GroupherServer.Accounts\n alias GroupherServer.CMS.{\n Author,\n CommunityEditor,\n CommunitySubscriber,\n CommunityThread,\n JobCommentReply,\n Post,\n PostComment,\n PostCommentDislike,\n PostCommentLike,\n PostCommentReply,\n PostFavorite,\n PostStar\n # job comment\n # JobComment,\n }\n\n def data, do: Dataloader.Ecto.new(Repo, query: &query/2, run_batch: &run_batch/5)\n\n # Big thanks: https://elixirforum.com/t/grouping-error-in-absinthe-dadaloader/13671/2\n # see also: https://github.com/absinthe-graphql/dataloader/issues/25\n def run_batch(Post, post_query, :posts_count, community_ids, repo_opts) do\n query =\n from(\n p in post_query,\n join: c in assoc(p, :communities),\n where: c.id in ^community_ids,\n group_by: c.id,\n select: {c.id, [count(p.id)]}\n )\n\n results =\n query\n |> Repo.all(repo_opts)\n |> Map.new()\n\n for id <- community_ids, do: Map.get(results, id, [0])\n end\n\n def run_batch(PostComment, comment_query, :cp_count, post_ids, repo_opts) do\n results =\n comment_query\n |> join(:inner, [c], a in assoc(c, :author))\n # |> distinct([c, a], a.id)\n |> group_by([c, a], a.id)\n |> group_by([c, a], c.post_id)\n |> select([c, a], {c.post_id, count(a.id)})\n |> Repo.all(repo_opts)\n |> Enum.group_by(fn {x, _} -> x end)\n |> Enum.map(fn {x, y} -> {x, [length(y)]} end)\n |> Map.new()\n\n for id <- post_ids, do: Map.get(results, id, [0])\n end\n\n def run_batch(PostComment, comment_query, :cp_users, post_ids, repo_opts) do\n # IO.inspect(comment_query, label: \"# run_batch # comment_query\")\n\n sq =\n from(\n pc in comment_query,\n join: a in assoc(pc, :author),\n select: %{id: a.id, row_number: fragment(\"row_number() OVER (PARTITION BY author_id)\")}\n )\n\n query =\n from(\n pc in comment_query,\n join: s in subquery(sq),\n on: s.id == pc.author_id,\n where: s.row_number == 10,\n select: {pc.post_id, s.id}\n )\n\n # query = comment_query\n # |> join(:inner, [c], a in assoc(c, :author))\n # |> distinct([c, a], c.post_id)\n # |> join(:inner_lateral, [c, a], u in fragment(\"SELECT * FROM users AS us WHERE us.id = ? LIMIT 1\", a.id))\n # |> join(:inner_lateral, [c, a], u in fragment(\"SELECT * FROM users AS us WHERE us.id > ? LIMIT 1\", 100))\n # |> select([c, a, u], {c.post_id, u.id, u.nickname})\n\n results =\n query\n # |> IO.inspect(label: \"before\")\n |> Repo.all(repo_opts)\n # |> IO.inspect(label: \"geting fuck\")\n |> bat_man()\n\n # results =\n # comment_query\n # |> join(:inner, [c], a in assoc(c, :author))\n # |> group_by([c, a], a.id)\n # |> group_by([c, a], c.post_id)\n # |> select([c, a], {c.post_id, a})\n # ---------\n # |> join(:inner, [c], s in subquery(sq), on: s.id == c.post_id)\n # |> join(:inner, [c], a in subquery(isubquery), c.post_id == 106)\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT * FROM users AS u WHERE u.id = ? LIMIT 3\", c.post_id))\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT * FROM users WHERE users.id > ? LIMIT 3\", 100))\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT * FROM posts_comments JOIN users ON users.id = ? LIMIT 2\", c.author_id))\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT * FROM posts_comments AS pc WHERE pc.author_id = ? LIMIT 2\", 185))\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT ROW_NUMBER() OVER (PARTITION BY ?) FROM posts_comments AS pc GROUP BY pc.post_id\", c.post_id))\n # |> distinct([c, a], c.post_id)\n # |> join(:inner_lateral, [c, a], x in fragment(\"SELECT * FROM posts_comments JOIN users ON users.id = posts_comments.author_id WHERE post_id = ? LIMIT 2\", c.post_id))\n # |> join(:inner_lateral, [c, a], x in fragment(\"SELECT * FROM posts_comments JOIN users ON users.id = posts_comments.author_id LIMIT 3\"))\n # |> select([c,a,x], {c.post_id, x.author_id})\n # |> select([c,a,x], {c.post_id, a.id})\n # |> where([c, a], a.row_number < 3)\n # |> join(:inner, [c], a in assoc(c, :author))\n # |> join(:inner, [c], a in subquery(isubquery))\n # |> group_by([c, a, x], x.author_id)\n # |> distinct([c, a], a.author_id)\n # |> select([c, a], {c.post_id, a.author_id})\n # |> select([c, a], {c.post_id, fragment(\"max(?) OVER (PARTITION BY ?)\", a.id, a.id)})\n # |> select([c, a], %{post_id: c.post_id, user: fragment(\"max(?) OVER (PARTITION BY ?)\", a.id, a.id)})\n # |> select([c, a], fragment(\"SELECT ROW_NUMBER() OVER (PARTITION BY ?) FROM cms_authors AS r , \", a.id))\n # |> join([c], c in subquery(sq), on: c.post_id == bq.id)\n # |> having([c, a], count(\"*\") < 10)\n # |> having([c, a], a.id < 180)\n # |> limit(3)\n # |> order_by([p, s], desc: fragment(\"count(?)\", s.id))\n # |> distinct([c, a], a.id)\n # |> Repo.all(repo_opts)\n # |> IO.inspect(label: \"get fuck\")\n # |> bat_man()\n\n for id <- post_ids, do: Map.get(results, id, [])\n end\n\n # TODO: use meta-programing to extract all query below\n # --------------------\n def bat_man(data) do\n # TODO refactor later\n data\n |> Enum.group_by(fn {x, _} -> x end)\n |> Enum.map(fn {x, y} ->\n {x,\n Enum.reduce(y, [], fn kv, acc ->\n {_, v} = kv\n acc ++ [v]\n end)}\n end)\n |> Map.new()\n end\n\n def query(Author, _args) do\n # you cannot use preload with select together\n # https://stackoverflow.com/questions/43010352/ecto-select-relations-from-preload\n # see also\n # https://github.com/elixir-ecto/ecto/issues/1145\n from(a in Author, join: u in assoc(a, :user), select: u)\n end\n\n def query({\"communities_threads\", CommunityThread}, _info) do\n from(\n ct in CommunityThread,\n join: t in assoc(ct, :thread),\n order_by: [asc: t.index],\n select: t\n )\n end\n\n @doc \"\"\"\n get unique participators join in comments\n \"\"\"\n def query({\"posts_comments\", PostComment}, %{filter: filter, unique: true}) do\n # def query({\"posts_comments\", PostComment}, %{unique: true}) do\n PostComment\n # |> QueryBuilder.members_pack(args)\n |> QueryBuilder.filter_pack(filter)\n |> join(:inner, [c], a in assoc(c, :author))\n |> distinct([c, a], a.id)\n |> select([c, a], a)\n end\n\n def query({\"posts_comments\", PostComment}, %{count: _, unique: true}) do\n # TODO: not very familar with SQL, but it has to be 2 group_by to work, check later\n # and the expect count should be the length of reault\n PostComment\n |> join(:inner, [c], a in assoc(c, :author))\n |> distinct([c, a], a.id)\n |> group_by([c, a], a.id)\n |> group_by([c, a], c.post_id)\n |> select([c, a], count(c.id))\n end\n\n def query({\"posts_comments\", PostComment}, %{count: _}) do\n PostComment\n |> group_by([c], c.post_id)\n |> select([c], count(c.id))\n end\n\n # def query({\"posts_comments\", PostComment}, %{filter: %{first: first}} = filter) do\n def query({\"posts_comments\", PostComment}, %{filter: filter}) do\n PostComment\n # |> limit(3)\n |> QueryBuilder.filter_pack(filter)\n end\n\n @doc \"\"\"\n handle query:\n 1. bacic filter of pagi,when,sort ...\n 2. count of the reactions\n 3. check is viewer reacted\n \"\"\"\n def query({\"posts_favorites\", PostFavorite}, args) do\n PostFavorite |> QueryBuilder.members_pack(args)\n end\n\n def query({\"posts_stars\", PostStar}, args) do\n PostStar |> QueryBuilder.members_pack(args)\n end\n\n def query({\"communities_subscribers\", CommunitySubscriber}, args) do\n CommunitySubscriber |> QueryBuilder.members_pack(args)\n end\n\n def query({\"communities_editors\", CommunityEditor}, args) do\n CommunityEditor |> QueryBuilder.members_pack(args)\n end\n\n # for comments replies, likes, repliesCount, likesCount...\n def query({\"posts_comments_replies\", PostCommentReply}, %{count: _}) do\n PostCommentReply\n |> group_by([c], c.post_comment_id)\n |> select([c], count(c.id))\n end\n\n def query({\"posts_comments_replies\", PostCommentReply}, %{filter: filter}) do\n PostCommentReply\n |> QueryBuilder.load_inner_replies(filter)\n end\n\n def query({\"posts_comments_replies\", PostCommentReply}, %{reply_to: _}) do\n PostCommentReply\n |> join(:inner, [c], r in assoc(c, :post_comment))\n |> select([c, r], r)\n end\n\n def query({\"posts_comments_likes\", PostCommentLike}, %{count: _}) do\n PostCommentLike\n |> group_by([f], f.post_comment_id)\n |> select([f], count(f.id))\n end\n\n def query({\"posts_comments_likes\", PostCommentLike}, %{viewer_did: _, cur_user: cur_user}) do\n PostCommentLike |> where([f], f.user_id == ^cur_user.id)\n end\n\n def query({\"posts_comments_likes\", PostCommentLike}, %{filter: _filter} = args) do\n PostCommentLike\n |> QueryBuilder.members_pack(args)\n end\n\n def query({\"posts_comments_dislikes\", PostCommentDislike}, %{count: _}) do\n PostCommentDislike\n |> group_by([f], f.post_comment_id)\n |> select([f], count(f.id))\n end\n\n # component dislikes\n def query({\"posts_comments_dislikes\", PostCommentDislike}, %{viewer_did: _, cur_user: cur_user}) do\n PostCommentDislike |> where([f], f.user_id == ^cur_user.id)\n end\n\n def query({\"posts_comments_dislikes\", PostCommentDislike}, %{filter: _filter} = args) do\n PostCommentDislike\n |> QueryBuilder.members_pack(args)\n end\n\n # ---- job comments ------\n def query({\"jobs_comments_replies\", JobCommentReply}, %{count: _}) do\n JobCommentReply\n |> group_by([c], c.job_comment_id)\n |> select([c], count(c.id))\n end\n\n def query({\"jobs_comments_replies\", JobCommentReply}, %{filter: filter}) do\n JobCommentReply\n |> QueryBuilder.load_inner_replies(filter)\n end\n\n def query({\"jobs_comments_replies\", JobCommentReply}, %{reply_to: _}) do\n JobCommentReply\n |> join(:inner, [c], r in assoc(c, :job_comment))\n |> select([c, r], r)\n end\n\n # ---- job ------\n\n # default loader\n def query(queryable, _args) do\n # IO.inspect(queryable, label: \"default loader\")\n # IO.inspect(args, label: \"default args\")\n queryable\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,153,null,null,null,null,null,null,null,null,null,null,null,null,null,76,null,null],"name":"lib/groupher_server/cms/community_subscriber.ex","source":"defmodule GroupherServer.CMS.CommunitySubscriber do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Community\n\n @required_fields ~w(user_id community_id)a\n\n @type t :: %CommunitySubscriber{}\n schema \"communities_subscribers\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:community, Community, foreign_key: :community_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%CommunitySubscriber{} = community_subscriber, attrs) do\n community_subscriber\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:community_id)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user_id, name: :communities_subscribers_user_id_community_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,20,null,null,null,2,null,null,null,14,null,null,null,2,null,null,null,null,34,34,null,34,null,1,null,null,33,null,33,null,null,null,null,null,4,4,4,4,null,null,null],"name":"lib/groupher_server/cms/delegates/comment_reaction.ex","source":"defmodule GroupherServer.CMS.Delegate.CommentReaction do\n import GroupherServer.CMS.Helper.MatcherOld\n\n alias GroupherServer.Accounts\n alias Helper.ORM\n\n def like_comment(thread, comment_id, %Accounts.User{id: user_id}) do\n feel_comment(thread, comment_id, user_id, :like)\n end\n\n def undo_like_comment(thread, comment_id, %Accounts.User{id: user_id}) do\n undo_feel_comment(thread, comment_id, user_id, :like)\n end\n\n def dislike_comment(thread, comment_id, %Accounts.User{id: user_id}) do\n feel_comment(thread, comment_id, user_id, :dislike)\n end\n\n def undo_dislike_comment(thread, comment_id, %Accounts.User{id: user_id}) do\n undo_feel_comment(thread, comment_id, user_id, :dislike)\n end\n\n defp feel_comment(thread, comment_id, user_id, feeling)\n when valid_feeling(feeling) do\n with {:ok, action} <- match_action(thread, feeling) do\n clause = %{post_comment_id: comment_id, user_id: user_id}\n\n case ORM.find_by(action.reactor, clause) do\n {:ok, _} ->\n {:error, \"user has #{to_string(feeling)}d this comment\"}\n\n {:error, _} ->\n action.reactor |> ORM.create(clause)\n\n ORM.find(action.target, comment_id)\n end\n end\n end\n\n defp undo_feel_comment(thread, comment_id, user_id, feeling) do\n with {:ok, action} <- match_action(thread, feeling) do\n clause = %{post_comment_id: comment_id, user_id: user_id}\n ORM.findby_delete!(action.reactor, clause)\n ORM.find(action.target, comment_id)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/helper/spec_type.ex","source":"defmodule Helper.Types do\n @moduledoc \"\"\"\n custom @types\n \"\"\"\n\n @typedoc \"\"\"\n Type GraphQL flavor the error format\n \"\"\"\n @type gq_error :: {:error, [message: String.t(), code: non_neg_integer()]}\n\n @typedoc \"\"\"\n general response conventions\n \"\"\"\n @type done :: {:ok, map} | {:error, map}\n\n @type id :: non_neg_integer() | String.t()\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/statistics/statistics_queries.ex","source":"defmodule GroupherServerWeb.Schema.Statistics.Queries do\n @moduledoc \"\"\"\n Statistics.Queries\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :statistics_queries do\n @desc \"list of user contribute in last 6 month\"\n field :user_contributes, list_of(:user_contribute) do\n arg(:id, non_null(:id))\n\n resolve(&R.Statistics.list_contributes/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,5273,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,null,null,null,null,null],"name":"lib/groupher_server/cms/community.ex","source":"defmodule GroupherServer.CMS.Community do\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.CMS.{\n Category,\n Post,\n Video,\n Repo,\n Job,\n CommunityThread,\n CommunitySubscriber,\n CommunityEditor\n }\n\n alias GroupherServer.Accounts\n\n @required_fields ~w(title desc user_id logo raw)a\n # @required_fields ~w(title desc user_id)a\n @optional_fields ~w(label)a\n\n schema \"communities\" do\n field(:title, :string)\n field(:desc, :string)\n field(:logo, :string)\n # field(:category, :string)\n field(:label, :string)\n field(:raw, :string)\n\n belongs_to(:author, Accounts.User, foreign_key: :user_id)\n\n has_many(:threads, {\"communities_threads\", CommunityThread})\n has_many(:subscribers, {\"communities_subscribers\", CommunitySubscriber})\n has_many(:editors, {\"communities_editors\", CommunityEditor})\n\n many_to_many(\n :categories,\n Category,\n join_through: \"communities_categories\",\n join_keys: [community_id: :id, category_id: :id],\n # :delete_all will only remove data from the join source\n on_delete: :delete_all\n # on_replace: :delete\n )\n\n many_to_many(\n :posts,\n Post,\n join_through: \"communities_posts\",\n join_keys: [community_id: :id, post_id: :id]\n )\n\n many_to_many(\n :videos,\n Video,\n join_through: \"communities_videos\",\n join_keys: [community_id: :id, video_id: :id]\n )\n\n many_to_many(\n :repos,\n Repo,\n join_through: \"communities_repos\",\n join_keys: [community_id: :id, repo_id: :id]\n )\n\n many_to_many(\n :jobs,\n Job,\n join_through: \"communities_jobs\",\n join_keys: [community_id: :id, job_id: :id]\n )\n\n # posts_managers\n # jobs_managers\n # tuts_managers\n # videos_managers\n #\n # posts_block_list ...\n # videos_block_list ...\n timestamps(type: :utc_datetime)\n end\n\n def changeset(%Community{} = community, attrs) do\n # |> cast_assoc(:author)\n # |> unique_constraint(:title, name: :communities_title_index)\n community\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> validate_length(:title, min: 3, max: 30)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:title, name: :communities_title_index)\n\n # |> foreign_key_constraint(:communities_author_fkey)\n # |> unique_constraint(:user_id, name: :posts_favorites_user_id_post_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,1,0,null,null,4,null,null,3,3,null,null,2,null,2,null,null,null,null,9,3,3,2,null,16,14,14,9,null,null,12,null,null,null,4,null,5,null,null,1,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,null,null,0,null,null,null,0,null,null,null,0,null,null,null,null,null,2,null,null,1,null,null,1,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,null,3,null,null,1,null,null,1,null,null,null,1,null,null,null,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,null,null,3,null,null,1,null,1,null,null,10,null,null,null,1,null,null,1,null,null,null,1,null,null,null,null,null,null,1,null,null,null,null,null,2,null,null,null,2,null,null,null,1,null,null,null,5,null,null,null,1,null,null,null,null,null,null,10,null,null,8,null,null,null,3,null,null,null,2,null,null,null,2,null,null,null,1,null,null,null,2,null,null,null,1,null,null,null,3,null,null],"name":"lib/groupher_server_web/resolvers/cms_resolver.ex","source":"defmodule GroupherServerWeb.Resolvers.CMS do\n @moduledoc false\n\n import ShortMaps\n import Ecto.Query, warn: false\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.CMS\n alias GroupherServer.CMS.{Post, Video, Repo, Job, Community, Category, Tag, Thread}\n alias Helper.ORM\n\n # #######################\n # community ..\n # #######################\n def community(_root, %{id: id}, _info), do: Community |> ORM.find(id)\n def community(_root, %{title: title}, _info), do: Community |> ORM.find_by(title: title)\n def community(_root, %{raw: raw}, _info), do: Community |> ORM.find_by(raw: raw)\n\n def community(_root, _args, _info), do: {:error, \"please provide community id or title or raw\"}\n def paged_communities(_root, ~m(filter)a, _info), do: Community |> ORM.find_all(filter)\n\n def create_community(_root, args, %{context: %{cur_user: user}}) do\n args = args |> Map.merge(%{user_id: user.id})\n Community |> ORM.create(args)\n end\n\n def update_community(_root, args, _info), do: Community |> ORM.find_update(args)\n\n def delete_community(_root, %{id: id}, _info), do: Community |> ORM.find_delete!(id)\n\n # #######################\n # community thread (post, job)\n # #######################\n def post(_root, %{id: id}, _info), do: Post |> ORM.read(id, inc: :views)\n def video(_root, %{id: id}, _info), do: Video |> ORM.read(id, inc: :views)\n def repo(_root, %{id: id}, _info), do: Repo |> ORM.read(id, inc: :views)\n def job(_root, %{id: id}, _info), do: Job |> ORM.read(id, inc: :views)\n\n def paged_posts(_root, ~m(filter)a, _info), do: Post |> CMS.paged_contents(filter)\n def paged_videos(_root, ~m(filter)a, _info), do: Video |> CMS.paged_contents(filter)\n def paged_repos(_root, ~m(filter)a, _info), do: Repo |> CMS.paged_contents(filter)\n def paged_jobs(_root, ~m(filter)a, _info), do: Job |> ORM.find_all(filter)\n\n def create_article(_root, ~m(community_id thread)a = args, %{context: %{cur_user: user}}) do\n CMS.create_article(%Community{id: community_id}, thread, args, user)\n end\n\n def update_article(_root, %{passport_source: content} = args, _info),\n do: ORM.update(content, args)\n\n def delete_content(_root, %{passport_source: content}, _info), do: ORM.delete(content)\n\n def pin_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{pin: true}, user)\n end\n\n def undo_pin_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{pin: false}, user)\n end\n\n def trash_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{markDelete: true}, user)\n end\n\n def undo_trash_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{markDelete: false}, user)\n end\n\n # #######################\n # thread reaction ..\n # #######################\n def reaction(_root, ~m(id thread action)a, %{context: %{cur_user: user}}) do\n CMS.reaction(thread, action, id, user)\n end\n\n def undo_reaction(_root, ~m(id thread action)a, %{context: %{cur_user: user}}) do\n CMS.undo_reaction(thread, action, id, user)\n end\n\n def reaction_users(_root, ~m(id action thread filter)a, _info) do\n CMS.reaction_users(thread, action, id, filter)\n end\n\n # #######################\n # category ..\n # #######################\n def paged_categories(_root, ~m(filter)a, _info), do: Category |> ORM.find_all(filter)\n\n def create_category(_root, ~m(title raw)a, %{context: %{cur_user: user}}) do\n CMS.create_category(%Category{title: title, raw: raw}, user)\n end\n\n def delete_category(_root, %{id: id}, _info), do: Category |> ORM.find_delete!(id)\n\n def update_category(_root, ~m(id title)a, %{context: %{cur_user: _}}) do\n CMS.update_category(~m(%Category id title)a)\n end\n\n def set_category(_root, ~m(community_id category_id)a, %{context: %{cur_user: _}}) do\n CMS.set_category(%Community{id: community_id}, %Category{id: category_id})\n end\n\n def unset_category(_root, ~m(community_id category_id)a, %{context: %{cur_user: _}}) do\n CMS.unset_category(%Community{id: community_id}, %Category{id: category_id})\n end\n\n # #######################\n # thread ..\n # #######################\n def paged_threads(_root, ~m(filter)a, _info), do: Thread |> ORM.find_all(filter)\n\n def create_thread(_root, ~m(title raw index)a, _info),\n do: CMS.create_thread(~m(title raw index)a)\n\n def set_thread(_root, ~m(community_id thread_id)a, _info) do\n CMS.set_thread(%Community{id: community_id}, %Thread{id: thread_id})\n end\n\n def unset_thread(_root, ~m(community_id thread_id)a, _info) do\n CMS.unset_thread(%Community{id: community_id}, %Thread{id: thread_id})\n end\n\n # #######################\n # editors ..\n # #######################\n def set_editor(_root, ~m(community_id user_id title)a, _) do\n CMS.set_editor(%Community{id: community_id}, title, %User{id: user_id})\n end\n\n def unset_editor(_root, ~m(community_id user_id)a, _) do\n CMS.unset_editor(%Community{id: community_id}, %User{id: user_id})\n end\n\n def update_editor(_root, ~m(community_id user_id title)a, _) do\n CMS.update_editor(%Community{id: community_id}, title, %User{id: user_id})\n end\n\n def community_editors(_root, ~m(id filter)a, _info) do\n CMS.community_members(:editors, %Community{id: id}, filter)\n end\n\n # #######################\n # tags ..\n # #######################\n def create_tag(_root, args, %{context: %{cur_user: user}}) do\n CMS.create_tag(args.thread, args, user)\n end\n\n def delete_tag(_root, %{id: id}, _info), do: Tag |> ORM.find_delete!(id)\n\n def update_tag(_root, args, _info), do: CMS.update_tag(args)\n\n def set_tag(_root, ~m(community_id thread id tag_id)a, _info) do\n CMS.set_tag(%Community{id: community_id}, thread, %Tag{id: tag_id}, id)\n end\n\n def unset_tag(_root, ~m(id thread tag_id)a, _info),\n do: CMS.unset_tag(thread, %Tag{id: tag_id}, id)\n\n def get_tags(_root, ~m(community_id thread)a, _info) do\n CMS.get_tags(%Community{id: community_id}, thread)\n end\n\n def get_tags(_root, ~m(community thread)a, _info) do\n CMS.get_tags(%Community{raw: community}, thread)\n end\n\n def get_tags(_root, %{thread: _thread}, _info) do\n {:error, \"community_id or community is needed\"}\n end\n\n def get_tags(_root, ~m(filter)a, _info), do: CMS.get_tags(filter)\n\n # #######################\n # community subscribe ..\n # #######################\n def subscribe_community(_root, ~m(community_id)a, %{context: %{cur_user: cur_user}}) do\n CMS.subscribe_community(%Community{id: community_id}, cur_user)\n end\n\n def unsubscribe_community(_root, ~m(community_id)a, %{context: %{cur_user: cur_user}}) do\n CMS.unsubscribe_community(%Community{id: community_id}, cur_user)\n end\n\n def community_subscribers(_root, ~m(id filter)a, _info) do\n CMS.community_members(:subscribers, %Community{id: id}, filter)\n end\n\n def mirror_article(_root, ~m(thread id community_id)a, _info) do\n CMS.mirror_article(%Community{id: community_id}, thread, id)\n end\n\n def unmirror_article(_root, ~m(thread id community_id)a, _info) do\n CMS.unmirror_article(%Community{id: community_id}, thread, id)\n end\n\n # #######################\n # comemnts ..\n # #######################\n def paged_comments(_root, ~m(id thread filter)a, _info),\n do: CMS.paged_comments(thread, id, filter)\n\n def create_comment(_root, ~m(thread id body)a, %{context: %{cur_user: user}}) do\n CMS.create_comment(thread, id, body, user)\n end\n\n def delete_comment(_root, ~m(thread id)a, _info) do\n CMS.delete_comment(thread, id)\n end\n\n def reply_comment(_root, ~m(thread id body)a, %{context: %{cur_user: user}}) do\n CMS.reply_comment(thread, id, body, user)\n end\n\n def like_comment(_root, ~m(thread id)a, %{context: %{cur_user: user}}) do\n CMS.like_comment(thread, id, user)\n end\n\n def undo_like_comment(_root, ~m(thread id)a, %{context: %{cur_user: user}}) do\n CMS.undo_like_comment(thread, id, user)\n end\n\n def dislike_comment(_root, ~m(thread id)a, %{context: %{cur_user: user}}) do\n CMS.dislike_comment(thread, id, user)\n end\n\n def undo_dislike_comment(_root, ~m(thread id)a, %{context: %{cur_user: user}}) do\n CMS.undo_dislike_comment(thread, id, user)\n end\n\n def stamp_passport(_root, ~m(user_id rules)a, %{context: %{cur_user: _user}}) do\n CMS.stamp_passport(rules, %User{id: user_id})\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,0,0,null,null,null,null,null,null,null,0,0,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,0,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/cms_types.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Types do\n @moduledoc \"\"\"\n cms types used in queries & mutations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n import GroupherServerWeb.Schema.Helper.Fields\n import Ecto.Query, warn: false\n import Absinthe.Resolution.Helpers, only: [dataloader: 2, on_load: 2]\n\n alias GroupherServer.CMS\n alias GroupherServerWeb.Schema\n\n import_types(Schema.CMS.Misc)\n\n object :idlike do\n field(:id, :id)\n end\n\n object :comment do\n field(:id, :id)\n field(:body, :string)\n field(:floor, :integer)\n field(:author, :user, resolve: dataloader(CMS, :author))\n\n field :reply_to, :comment do\n resolve(dataloader(CMS, :reply_to))\n end\n\n field :likes, list_of(:user) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :likes))\n end\n\n field :likes_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(CMS, :likes))\n middleware(M.ConvertToInt)\n end\n\n field :viewer_has_liked, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n # put current user into dataloader's args\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :likes))\n middleware(M.ViewerDidConvert)\n end\n\n field :dislikes, list_of(:user) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :dislikes))\n end\n\n field :viewer_has_disliked, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n # put current user into dataloader's args\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :dislikes))\n middleware(M.ViewerDidConvert)\n end\n\n field :dislikes_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(CMS, :dislikes))\n middleware(M.ConvertToInt)\n end\n\n field :replies, list_of(:comment) do\n arg(:filter, :members_filter)\n\n middleware(M.ForceLoader)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :replies))\n end\n\n field :replies_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(CMS, :replies))\n middleware(M.ConvertToInt)\n end\n\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :post do\n interface(:article)\n field(:id, :id)\n field(:title, :string)\n field(:digest, :string)\n field(:length, :integer)\n field(:link_addr, :string)\n field(:body, :string)\n field(:views, :integer)\n field(:pin, :boolean)\n field(:markDelete, :boolean)\n field(:tags, list_of(:article_tag), resolve: dataloader(CMS, :tags))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n\n field :comments, list_of(:comment) do\n arg(:filter, :members_filter)\n\n # middleware(M.ForceLoader)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :comments))\n end\n\n field :comments_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(CMS, :comments))\n middleware(M.ConvertToInt)\n end\n\n field :comments_participators, list_of(:user) do\n arg(:filter, :members_filter)\n arg(:unique, :unique_type, default_value: true)\n\n middleware(M.ForceLoader)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :comments))\n end\n\n field :comments_participators2, list_of(:user) do\n arg(:filter, :members_filter)\n arg(:unique, :unique_type, default_value: true)\n\n middleware(M.PageSizeProof)\n\n resolve(fn post, _args, %{context: %{loader: loader}} ->\n loader\n |> Dataloader.load(CMS, {:many, CMS.PostComment}, cp_users: post.id)\n |> on_load(fn loader ->\n {:ok, Dataloader.get(loader, CMS, {:many, CMS.PostComment}, cp_users: post.id)}\n end)\n end)\n end\n\n field :comments_participators_count, :integer do\n resolve(fn post, _args, %{context: %{loader: loader}} ->\n loader\n |> Dataloader.load(CMS, {:one, CMS.PostComment}, cp_count: post.id)\n |> on_load(fn loader ->\n {:ok, Dataloader.get(loader, CMS, {:one, CMS.PostComment}, cp_count: post.id)}\n end)\n end)\n end\n\n field :comments_participators_count_wired, :integer do\n arg(:unique, :unique_type, default_value: true)\n arg(:count, :count_type, default_value: :count)\n\n # middleware(M.ForceLoader)\n resolve(dataloader(CMS, :comments))\n # middleware(M.CountLength)\n end\n\n field :viewer_has_favorited, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n # put current user into dataloader's args\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :favorites))\n middleware(M.ViewerDidConvert)\n end\n\n field :viewer_has_starred, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :stars))\n middleware(M.ViewerDidConvert)\n end\n\n field :favorited_users, list_of(:user) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :favorites))\n end\n\n field :favorited_count, :integer do\n arg(:count, :count_type, default_value: :count)\n arg(:type, :post_type, default_value: :post)\n # middleware(M.SeeMe)\n resolve(dataloader(CMS, :favorites))\n middleware(M.ConvertToInt)\n end\n\n field :starred_count, :integer do\n arg(:count, :count_type, default_value: :count)\n arg(:type, :post_type, default_value: :post)\n\n resolve(dataloader(CMS, :stars))\n middleware(M.ConvertToInt)\n end\n\n field :starred_users, list_of(:user) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :stars))\n end\n end\n\n object :video do\n interface(:article)\n field(:id, :id)\n field(:title, :string)\n field(:desc, :string)\n field(:duration, :string)\n field(:author, :user, resolve: dataloader(CMS, :author))\n\n field(:source, :string)\n field(:link, :string)\n field(:original_author, :string)\n field(:original_author_link, :string)\n field(:views, :integer)\n\n field(:pin, :boolean)\n field(:markDelete, :boolean)\n\n # field(:tags, list_of(:article_tag), resolve: dataloader(CMS, :tags))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :repo do\n # interface(:article)\n field(:id, :id)\n field(:repo_name, :string)\n field(:desc, :string)\n field(:readme, :string)\n field(:language, :string)\n field(:author, :user, resolve: dataloader(CMS, :author))\n\n field(:repo_link, :string)\n field(:producer, :string)\n field(:producer_link, :integer)\n\n field(:repo_star_count, :integer)\n field(:repo_fork_count, :integer)\n field(:repo_watch_count, :integer)\n field(:views, :integer)\n\n field(:pin, :boolean)\n field(:markDelete, :boolean)\n\n # field(:tags, list_of(:article_tag), resolve: dataloader(CMS, :tags))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :job do\n interface(:article)\n field(:id, :id)\n field(:title, :string)\n field(:desc, :string)\n field(:company, :string)\n field(:company_logo, :string)\n field(:digest, :string)\n field(:location, :string)\n field(:length, :integer)\n field(:link_addr, :string)\n field(:body, :string)\n field(:views, :integer)\n\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:tags, list_of(:article_tag), resolve: dataloader(CMS, :tags))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :thread do\n field(:id, :id)\n field(:title, :string)\n field(:raw, :string)\n field(:index, :integer)\n end\n\n object :contribute do\n field(:date, :date)\n field(:count, :integer)\n end\n\n object :contribute_map do\n field(:start_date, :date)\n field(:end_date, :date)\n field(:total_count, :integer)\n field(:records, list_of(:contribute))\n end\n\n object :community do\n # meta(:cache, max_age: 30)\n\n field(:id, :id)\n field(:title, :string)\n field(:desc, :string)\n field(:raw, :string)\n field(:logo, :string)\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:threads, list_of(:thread), resolve: dataloader(CMS, :threads))\n field(:categories, list_of(:category), resolve: dataloader(CMS, :categories))\n\n # Big thanks: https://elixirforum.com/t/grouping-error-in-absinthe-dadaloader/13671/2\n # see also: https://github.com/absinthe-graphql/dataloader/issues/25\n field :posts_count, :integer do\n resolve(fn community, _args, %{context: %{loader: loader}} ->\n loader\n |> Dataloader.load(CMS, {:one, CMS.Post}, posts_count: community.id)\n |> on_load(fn loader ->\n {:ok, Dataloader.get(loader, CMS, {:one, CMS.Post}, posts_count: community.id)}\n end)\n end)\n end\n\n field :subscribers, list_of(:user) do\n arg(:filter, :members_filter)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :subscribers))\n end\n\n field :subscribers_count, :integer do\n arg(:count, :count_type, default_value: :count)\n arg(:type, :community_type, default_value: :community)\n resolve(dataloader(CMS, :subscribers))\n middleware(M.ConvertToInt)\n end\n\n field :viewer_has_subscribed, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :subscribers))\n middleware(M.ViewerDidConvert)\n end\n\n field :editors, list_of(:user) do\n arg(:filter, :members_filter)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :editors))\n end\n\n field :editors_count, :integer do\n arg(:count, :count_type, default_value: :count)\n arg(:type, :community_type, default_value: :community)\n resolve(dataloader(CMS, :editors))\n middleware(M.ConvertToInt)\n end\n\n field :contributes, list_of(:contribute) do\n # TODO add complex here to warning N+1 problem\n resolve(&R.Statistics.list_contributes/3)\n end\n\n field :contributes_digest, list_of(:integer) do\n # TODO add complex here to warning N+1 problem\n resolve(&R.Statistics.list_contributes_digest/3)\n end\n end\n\n object :category do\n field(:id, :id)\n field(:title, :string)\n field(:raw, :string)\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :article_tag do\n field(:id, :id)\n field(:title, :string)\n field(:color, :string)\n field(:thread, :string)\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:community, :community, resolve: dataloader(CMS, :community))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :paged_categories do\n field(:entries, list_of(:category))\n pagination_fields()\n end\n\n object :paged_posts do\n field(:entries, list_of(:post))\n pagination_fields()\n end\n\n object :paged_videos do\n field(:entries, list_of(:video))\n pagination_fields()\n end\n\n object :paged_repos do\n field(:entries, list_of(:repo))\n pagination_fields()\n end\n\n object :paged_jobs do\n field(:entries, list_of(:job))\n pagination_fields()\n end\n\n object :paged_comments do\n field(:entries, list_of(:comment))\n pagination_fields()\n end\n\n object :paged_communities do\n field(:entries, list_of(:community))\n pagination_fields()\n end\n\n object :paged_tags do\n field(:entries, list_of(:article_tag))\n pagination_fields()\n end\n\n object :paged_threads do\n field(:entries, list_of(:thread))\n pagination_fields()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/cms_queries.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Queries do\n @moduledoc \"\"\"\n CMS queries\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_queries do\n field :community, :community do\n # arg(:id, non_null(:id))\n arg(:id, :id)\n arg(:title, :string)\n arg(:raw, :string)\n resolve(&R.CMS.community/3)\n end\n\n @desc \"communities with pagination info\"\n field :paged_communities, :paged_communities do\n arg(:filter, non_null(:communities_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_communities/3)\n end\n\n @desc \"paged subscribers of a community\"\n field :community_subscribers, :paged_users do\n arg(:id, non_null(:id))\n arg(:filter, :paged_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.community_subscribers/3)\n end\n\n @desc \"paged subscribers of a community\"\n field :community_editors, :paged_users do\n arg(:id, non_null(:id))\n arg(:filter, :paged_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.community_editors/3)\n end\n\n @desc \"get all categories\"\n field :paged_categories, :paged_categories do\n arg(:filter, :paged_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_categories/3)\n end\n\n @desc \"get all the threads across all communities\"\n field :paged_threads, :paged_threads do\n arg(:filter, :threads_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_threads/3)\n end\n\n @desc \"get post by id\"\n field :post, non_null(:post) do\n arg(:id, non_null(:id))\n resolve(&R.CMS.post/3)\n end\n\n @desc \"get paged posts\"\n field :paged_posts, :paged_posts do\n arg(:filter, non_null(:paged_article_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_posts/3)\n end\n\n @desc \"get video by id\"\n field :video, non_null(:video) do\n arg(:id, non_null(:id))\n resolve(&R.CMS.video/3)\n end\n\n @desc \"get paged videos\"\n field :paged_videos, :paged_videos do\n arg(:filter, non_null(:paged_article_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_videos/3)\n end\n\n @desc \"get repo by id\"\n field :repo, non_null(:repo) do\n arg(:id, non_null(:id))\n resolve(&R.CMS.repo/3)\n end\n\n @desc \"get paged videos\"\n field :paged_repos, :paged_repos do\n arg(:filter, non_null(:paged_article_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_repos/3)\n end\n\n @desc \"get job by id\"\n field :job, non_null(:job) do\n arg(:id, non_null(:id))\n resolve(&R.CMS.job/3)\n end\n\n @desc \"get paged jobs\"\n field :paged_jobs, :paged_jobs do\n arg(:filter, non_null(:paged_article_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_jobs/3)\n end\n\n field :favorite_users, :paged_users do\n arg(:id, non_null(:id))\n arg(:type, :thread, default_value: :post)\n arg(:action, :favorite_action, default_value: :favorite)\n arg(:filter, :paged_article_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.reaction_users/3)\n end\n\n # get all tags\n field :paged_tags, :paged_tags do\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.get_tags/3)\n end\n\n # TODO: remove\n field :tags, :paged_tags do\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n # TODO: should be passport\n resolve(&R.CMS.get_tags/3)\n end\n\n # partial\n @desc \"get paged tags belongs to community_id or community\"\n field :partial_tags, list_of(:article_tag) do\n arg(:community_id, :id)\n arg(:community, :string)\n arg(:thread, :thread, default_value: :post)\n\n resolve(&R.CMS.get_tags/3)\n end\n\n @desc \"get paged comments\"\n field :paged_comments, :paged_comments do\n arg(:id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n arg(:filter, :comments_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_comments/3)\n end\n\n # comments\n field :comments, :paged_comments do\n arg(:id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n arg(:filter, :comments_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_comments/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,5,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/purchase.ex","source":"defmodule GroupherServer.Accounts.Purchase do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id)a\n @optional_fields ~w(theme community_chart brainwash_free)a\n\n @type t :: %Purchase{}\n schema \"purchases\" do\n belongs_to(:user, User)\n\n field(:theme, :boolean)\n field(:community_chart, :boolean)\n field(:brainwash_free, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Purchase{} = purchase, attrs) do\n purchase\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,1,null,null,null,0,null,null,null,0,null,null,null,0,null,null,null,3,null,null],"name":"lib/groupher_server_web/resolvers/statistics_resolver.ex","source":"defmodule GroupherServerWeb.Resolvers.Statistics do\n @moduledoc \"\"\"\n resolvers for Statistics\n \"\"\"\n alias GroupherServer.{Accounts, CMS, Statistics}\n # alias Helper.ORM\n\n # tmp for test\n def list_contributes(_root, %{id: id}, _info) do\n Statistics.list_contributes(%Accounts.User{id: id})\n end\n\n def list_contributes(%Accounts.User{id: id}, _args, _info) do\n Statistics.list_contributes(%Accounts.User{id: id})\n end\n\n def list_contributes(%CMS.Community{id: id}, _args, _info) do\n Statistics.list_contributes(%CMS.Community{id: id})\n end\n\n def list_contributes_digest(%CMS.Community{id: id}, _args, _info) do\n Statistics.list_contributes_digest(%CMS.Community{id: id})\n end\n\n def make_contrubute(_root, %{user_id: user_id}, _info) do\n Statistics.make_contribute(%Accounts.User{id: user_id})\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,99,98,null,null,null,null,null,null,null,null,null,null,95,null,null,97,null,null,null,null,null,null,null,null,null,null,null,95,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,4,4,null,null,4,null,null,4,null,null,4,null,null,4,null,null,null,null,null,null,null,null,null,null,4,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,3,3,null,null,null,null,null,null,null,null,3,3,3,null,null,null,null,null,6,null,null,6,null,null],"name":"lib/groupher_server/accounts/delegates/fans.ex","source":"defmodule GroupherServer.Accounts.Delegate.Fans do\n @moduledoc \"\"\"\n user followers / following related\n \"\"\"\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1]\n import Helper.ErrorCode\n import ShortMaps\n\n alias Helper.{ORM, QueryBuilder, SpecType}\n alias GroupherServer.{Accounts, Repo}\n\n alias GroupherServer.Accounts.{User, UserFollower, UserFollowing}\n\n alias Ecto.Multi\n\n @doc \"\"\"\n follow a user\n \"\"\"\n @spec follow(User.t(), User.t()) :: {:ok, User.t()} | SpecType.gq_error()\n def follow(%User{id: user_id}, %User{id: follower_id}) do\n with true <- to_string(user_id) !== to_string(follower_id),\n {:ok, _follow_user} <- ORM.find(User, follower_id) do\n Multi.new()\n |> Multi.insert(\n :create_follower,\n UserFollower.changeset(%UserFollower{}, ~m(user_id follower_id)a)\n )\n |> Multi.insert(\n :create_following,\n UserFollowing.changeset(%UserFollowing{}, %{user_id: user_id, following_id: follower_id})\n )\n |> Multi.run(:add_achievement, fn _ ->\n Accounts.achieve(%User{id: follower_id}, :add, :follow)\n end)\n |> Repo.transaction()\n |> follow_result()\n else\n false ->\n {:error, [message: \"can't follow yourself\", code: ecode(:self_conflict)]}\n\n {:error, error} ->\n {:error, [message: error, code: ecode(:not_exsit)]}\n end\n end\n\n @spec follow_result({:ok, map()}) :: SpecType.done()\n defp follow_result({:ok, %{create_follower: user_follower}}) do\n User |> ORM.find(user_follower.follower_id)\n end\n\n defp follow_result({:error, :create_follower, _result, _steps}) do\n {:error, [message: \"already followed\", code: ecode(:already_did)]}\n end\n\n defp follow_result({:error, :create_following, _result, _steps}) do\n {:error, [message: \"follow fails\", code: ecode(:react_fails)]}\n end\n\n defp follow_result({:error, :add_achievement, _result, _steps}) do\n {:error, [message: \"follow acieve fails\", code: ecode(:react_fails)]}\n end\n\n @doc \"\"\"\n undo a follow action to a user\n \"\"\"\n @spec undo_follow(User.t(), User.t()) :: {:ok, User.t()} | SpecType.gq_error()\n def undo_follow(%User{id: user_id}, %User{id: follower_id}) do\n with true <- to_string(user_id) !== to_string(follower_id),\n {:ok, _follow_user} <- ORM.find(User, follower_id) do\n Multi.new()\n |> Multi.run(:delete_follower, fn _ ->\n ORM.findby_delete!(UserFollower, ~m(user_id follower_id)a)\n end)\n |> Multi.run(:delete_following, fn _ ->\n ORM.findby_delete!(UserFollowing, %{user_id: user_id, following_id: follower_id})\n end)\n |> Multi.run(:minus_achievement, fn _ ->\n Accounts.achieve(%User{id: follower_id}, :minus, :follow)\n end)\n |> Repo.transaction()\n |> undo_follow_result()\n else\n false ->\n {:error, [message: \"can't undo follow yourself\", code: ecode(:self_conflict)]}\n\n {:error, error} ->\n {:error, [message: error, code: ecode(:not_exsit)]}\n end\n end\n\n defp undo_follow_result({:ok, %{delete_follower: user_follower}}) do\n User |> ORM.find(user_follower.follower_id)\n end\n\n defp undo_follow_result({:error, :delete_follower, _result, _steps}) do\n {:error, [message: \"already unfollowed\", code: ecode(:already_did)]}\n end\n\n defp undo_follow_result({:error, :delete_following, _result, _steps}) do\n {:error, [message: \"unfollow fails\", code: ecode(:react_fails)]}\n end\n\n defp undo_follow_result({:error, :minus_achievement, _result, _steps}) do\n {:error, [message: \"follow acieve fails\", code: ecode(:react_fails)]}\n end\n\n @doc \"\"\"\n get paged followers of a user\n \"\"\"\n @spec fetch_followers(User.t(), map()) :: {:ok, map()} | {:error, String.t()}\n def fetch_followers(%User{id: user_id}, filter) do\n UserFollower\n |> where([uf], uf.follower_id == ^user_id)\n |> join(:inner, [uf], u in assoc(uf, :user))\n |> load_fans(filter)\n end\n\n @doc \"\"\"\n get paged followings of a user\n \"\"\"\n @spec fetch_followings(User.t(), map()) :: {:ok, map()} | {:error, String.t()}\n def fetch_followings(%User{id: user_id}, filter) do\n UserFollowing\n |> where([uf], uf.user_id == ^user_id)\n |> join(:inner, [uf], u in assoc(uf, :following))\n |> load_fans(filter)\n end\n\n @spec load_fans(Ecto.Queryable.t(), map()) :: {:ok, map()} | {:error, String.t()}\n defp load_fans(queryable, ~m(page size)a = filter) do\n queryable\n |> select([uf, u], u)\n |> QueryBuilder.filter_pack(filter)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,18,null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,null],"name":"lib/groupher_server/cms/community_thread.ex","source":"defmodule GroupherServer.CMS.CommunityThread do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.CMS.{Community, Thread}\n\n @required_fields ~w(community_id thread_id)a\n\n @type t :: %CommunityThread{}\n schema \"communities_threads\" do\n belongs_to(:community, Community, foreign_key: :community_id)\n belongs_to(:thread, Thread, foreign_key: :thread_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%CommunityThread{} = community_thread, attrs) do\n community_thread\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:community_id)\n |> foreign_key_constraint(:thread_id)\n |> unique_constraint(:community_id, name: :communities_threads_community_id_thread_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,176,null,12,null,null,164,null,null,null,null,164,null,16,148,null,164,null,null,0,162,null,14,null,null,162,null,150,null,null,null,12,null,null,null],"name":"lib/groupher_server_web/middleware/pagesize_proof.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.PageSizeProof do\n @behaviour Absinthe.Middleware\n\n import Helper.Utils, only: [handle_absinthe_error: 3, get_config: 2]\n import Helper.ErrorCode\n\n @max_page_size get_config(:general, :page_size)\n @inner_page_size get_config(:general, :inner_page_size)\n\n # 1. if has filter:first and filter:size -> makesure it not too large\n # 2. if not has filter: marge to default first: 5\n # 3. large size should trigger error\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(resolution, _) do\n # IO.inspect resolution.arguments, label: \"resolution arguments\"\n # IO.inspect valid_size(resolution.arguments), label: \"valid_size\"\n\n case valid_size(resolution.arguments) do\n {:error, msg} ->\n resolution |> handle_absinthe_error(msg, ecode(:pagination))\n\n arguments ->\n %{resolution | arguments: sort_desc_by_default(arguments)}\n end\n end\n\n defp sort_desc_by_default(%{filter: filter} = arguments) do\n filter =\n if Map.has_key?(filter, :sort),\n do: filter,\n else: filter |> Map.merge(%{sort: :desc_inserted})\n\n arguments |> Map.merge(%{filter: filter})\n end\n\n defp valid_size(%{filter: %{first: size}} = arg), do: do_size_check(size, arg)\n defp valid_size(%{filter: %{size: size}} = arg), do: do_size_check(size, arg)\n\n defp valid_size(arg), do: arg |> Map.merge(%{filter: %{first: @inner_page_size}})\n\n defp do_size_check(size, arg) do\n case size in 1..@max_page_size do\n true ->\n arg\n\n _ ->\n {:error,\n \"SIZE_RANGE_ERROR: size shuold between 0 and #{@max_page_size}, current: #{size}\"}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/operation.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Operation do\n @moduledoc \"\"\"\n CMS mutations for cms operations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_opertion_mutations do\n @desc \"set category to a community\"\n field :set_category, :community do\n arg(:community_id, non_null(:id))\n arg(:category_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.set\")\n\n resolve(&R.CMS.set_category/3)\n end\n\n @desc \"unset category to a community\"\n field :unset_category, :community do\n arg(:community_id, non_null(:id))\n arg(:category_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.unset\")\n\n resolve(&R.CMS.unset_category/3)\n end\n\n @desc \"bind a thread to a exist community\"\n field :set_thread, :community do\n arg(:community_id, non_null(:id))\n arg(:thread_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->thread.set\")\n\n resolve(&R.CMS.set_thread/3)\n end\n\n @desc \"remove a thread from a exist community, thread content is not delete\"\n field :unset_thread, :community do\n arg(:community_id, non_null(:id))\n arg(:thread_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->thread.unset\")\n\n resolve(&R.CMS.unset_thread/3)\n end\n\n @desc \"stamp rules on user's passport\"\n field :stamp_cms_passport, :idlike do\n arg(:user_id, non_null(:id))\n arg(:rules, non_null(:json))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->community.stamp_passport\")\n\n resolve(&R.CMS.stamp_passport/3)\n end\n\n @desc \"subscribe a community so it can appear in sidebar\"\n field :subscribe_community, :community do\n arg(:community_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.subscribe_community/3)\n end\n\n @desc \"unsubscribe a community\"\n field :unsubscribe_community, :community do\n arg(:community_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.unsubscribe_community/3)\n end\n\n @desc \"set a tag within community\"\n field :set_tag, :article_tag do\n arg(:id, non_null(:id))\n arg(:tag_id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.set\")\n\n resolve(&R.CMS.set_tag/3)\n end\n\n @desc \"unset a tag within community\"\n field :unset_tag, :article_tag do\n # thread id\n arg(:id, non_null(:id))\n arg(:tag_id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.unset\")\n\n resolve(&R.CMS.unset_tag/3)\n end\n\n # TODO: use community loader\n field :mirror_article, :community do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->t?.community.mirror\")\n resolve(&R.CMS.mirror_article/3)\n end\n\n # TODO: can't not unset the oldest community\n field :unmirror_article, :community do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->t?.community.unmirror\")\n resolve(&R.CMS.unmirror_article/3)\n end\n\n field :reaction, :article do\n arg(:id, non_null(:id))\n arg(:thread, non_null(:thread))\n arg(:action, non_null(:cms_action))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.reaction/3)\n end\n\n field :undo_reaction, :article do\n arg(:id, non_null(:id))\n arg(:thread, non_null(:thread))\n arg(:action, non_null(:cms_action))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.undo_reaction/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,17,9,7,5,3,null,null,null,2,null,null,null,2,null,null,null,2,null,null,null,8,null,null,null,null,null,0,null,null,null,null,9,9,null,9,null,null,null,null,null,null,7,null,7,null,null,null,null,null,null,5,null,5,null,null,null,null,null],"name":"lib/groupher_server_web/middleware/publish_throttle.ex","source":"defmodule GroupherServerWeb.Middleware.PublishThrottle do\n @behaviour Absinthe.Middleware\n import Helper.Utils, only: [handle_absinthe_error: 3, get_config: 2]\n import Helper.ErrorCode\n\n alias GroupherServer.{Statistics, Accounts}\n\n @interval_minutes get_config(:general, :publish_throttle_interval_minutes)\n @hour_limit get_config(:general, :publish_throttle_hour_limit)\n @day_total get_config(:general, :publish_throttle_day_limit)\n\n def call(%{context: %{cur_user: cur_user}} = resolution, opt) do\n with {:ok, record} <- Statistics.load_throttle_record(%Accounts.User{id: cur_user.id}),\n {:ok, _} <- interval_check(record, opt),\n {:ok, _} <- hour_limit_check(record, opt),\n {:ok, _} <- day_limit_check(record, opt) do\n resolution\n else\n {:error, :interval_check} ->\n resolution\n |> handle_absinthe_error(\"throttle_interval\", ecode(:throttle_inverval))\n\n {:error, :hour_limit_check} ->\n resolution\n |> handle_absinthe_error(\"throttle_hour\", ecode(:throttle_hour))\n\n {:error, :day_limit_check} ->\n resolution\n |> handle_absinthe_error(\"throttle_day\", ecode(:throttle_day))\n\n {:error, _error} ->\n # publish first time ignore\n resolution\n end\n end\n\n def call(resolution, _) do\n resolution\n |> handle_absinthe_error(\"Authorize: need login\", ecode(:account_login))\n end\n\n # TODO: option: passport ..\n defp interval_check(%Statistics.PublishThrottle{last_publish_time: last_publish_time}, opt) do\n interval_opt = Keyword.get(opt, :interval) || @interval_minutes\n latest_valid_time = Timex.shift(last_publish_time, minutes: interval_opt)\n\n case Timex.before?(latest_valid_time, Timex.now()) do\n true -> {:ok, :interval_check}\n false -> {:error, :interval_check}\n end\n end\n\n defp hour_limit_check(%Statistics.PublishThrottle{hour_count: hour_count}, opt) do\n hour_count_opt = Keyword.get(opt, :hour_limit) || @hour_limit\n\n case hour_count < hour_count_opt do\n true -> {:ok, :hour_limit_check}\n false -> {:error, :hour_limit_check}\n end\n end\n\n defp day_limit_check(%Statistics.PublishThrottle{date_count: day_count}, opt) do\n day_limit_opt = Keyword.get(opt, :day_limit) || @day_total\n\n case day_count < day_limit_opt do\n true -> {:ok, :day_limit_check}\n false -> {:error, :day_limit_check}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,9771,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,null,null,null,null],"name":"lib/groupher_server/accounts/user.ex","source":"defmodule GroupherServer.Accounts.User do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.Accounts.{\n Achievement,\n Customization,\n GithubUser,\n Purchase,\n UserBill,\n UserFollower,\n UserFollowing\n }\n\n alias GroupherServer.CMS\n\n @type t :: %User{}\n schema \"users\" do\n field(:nickname, :string)\n field(:avatar, :string)\n field(:sex, :string)\n field(:bio, :string)\n field(:email, :string)\n field(:location, :string)\n field(:education, :string)\n field(:company, :string)\n field(:qq, :string)\n field(:weibo, :string)\n field(:weichat, :string)\n field(:from_github, :boolean)\n has_one(:achievement, Achievement)\n has_one(:github_profile, GithubUser)\n has_one(:cms_passport, CMS.Passport)\n\n has_many(:followers, {\"users_followers\", UserFollower})\n has_many(:followings, {\"users_followings\", UserFollowing})\n\n has_many(:subscribed_communities, {\"communities_subscribers\", CMS.CommunitySubscriber})\n has_many(:favorited_posts, {\"posts_favorites\", CMS.PostFavorite})\n has_many(:favorited_jobs, {\"jobs_favorites\", CMS.JobFavorite})\n\n field(:sponsor_member, :boolean)\n field(:paid_member, :boolean)\n field(:platinum_member, :boolean)\n\n has_many(:bills, {\"users_bills\", UserBill})\n has_one(:customization, Customization)\n has_one(:purchase, Purchase)\n\n timestamps(type: :utc_datetime)\n end\n\n @required_fields ~w(nickname avatar)a\n @optional_fields ~w(nickname bio avatar sex location email company education qq weichat weibo)a\n\n @doc false\n def changeset(%User{} = user, attrs) do\n # |> cast(attrs, [:username, :nickname, :bio, :company])\n # |> validate_required([:username])\n # |> cast(attrs, @required_fields, @optional_fields)\n user\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> validate_length(:nickname, min: 3, max: 30)\n |> validate_length(:bio, min: 3, max: 100)\n |> validate_inclusion(:sex, [\"dude\", \"girl\"])\n |> validate_format(:email, ~r/@/)\n |> validate_length(:location, min: 2, max: 30)\n |> validate_length(:company, min: 3, max: 30)\n |> validate_length(:qq, min: 8, max: 15)\n |> validate_length(:weichat, min: 3, max: 30)\n |> validate_length(:weibo, min: 3, max: 30)\n\n # |> unique_constraint(:username)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,4,null,null],"name":"lib/groupher_server/accounts/customization.ex","source":"defmodule GroupherServer.Accounts.Customization do\n @moduledoc false\n\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id)a\n @optional_fields ~w(theme sidebar_layout community_chart brainwash_free)a\n\n @type t :: %Customization{}\n schema \"customizations\" do\n belongs_to(:user, User)\n\n field(:theme, :boolean)\n field(:sidebar_layout, :map)\n field(:community_chart, :boolean)\n field(:brainwash_free, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Customization{} = customization, attrs) do\n customization\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,0,null,null,0,null,null,null,0,null,null,0,null],"name":"lib/groupher_server_web/middleware/count_length.ex","source":"# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.CountLength do\n @behaviour Absinthe.Middleware\n # google: must appear in the GROUP BY clause or be used in an aggregate function\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(%{value: []} = resolution, _) do\n %{resolution | value: 0}\n end\n\n def call(%{value: value} = resolution, _) when is_list(value) do\n %{resolution | value: length(value)}\n end\n\n def call(resolution, _), do: resolution\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,58,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/delivery/sys_notification.ex","source":"defmodule GroupherServer.Delivery.SysNotification do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n @required_fields ~w(source_title source_id source_type)a\n @optional_fields ~w(source_preview)a\n\n @type t :: %SysNotification{}\n schema \"sys_notifications\" do\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:source_preview, :string)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%SysNotification{} = sys_notification, attrs) do\n sys_notification\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,881,null,881,null,null,null,null,null,null,null,null,null,null,null,null,null,null,537,null,537,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,536,null,536,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,542,542,null,542,null,null,null,542,null,null,null,null,null,null,null,null,null,null,null,null,0,null,0,null,null,null,0,null,0,null,null,null,null,0,null,null,null,null,2628,null,null,null,0,null,null,null,59,59,null,null,null,4718,4718,null,4718,4718,null,4718,null,null,null,null,null,null,69,null,69,69,69,null,null,null,null,null,63,null,63,63,null,null,null,null,null,null,null,null,null,null,27,null,27,27,27,null,27,null,null,null,null,8433,null,8433,null,8433,null,null,null,null,null,null,1,null,1,1,1,1,1,1,null,null,null,null,null,null,null,null,null,null,null,235,209,0,27,4,3,6,5,0,0,null,null,0,null,8,1,8,1,null,null,null,null,null,null,null,null,null,854,533,533,536,0,0,2628,61,55,null,null,26,null,8224,4713,59,null,null,0,null,null,3735,null,null,null,null,null,null,null,3735,null,null,null,134,null,2617,2617,null,null,134,null,null,null,null,null,null,6,null,null,null,12,null,12,28,null,28,null,28,null,28,null,null,28,null,null,null,null,12,null,12,62,null,62,null,62,null,62,null,null,null,62,null,null,null],"name":"test/support/factory.ex","source":"defmodule GroupherServer.Factory do\n @moduledoc \"\"\"\n This module defines the mock data/func to be used by\n tests that require insert some mock data to db.\n\n for example you can db_insert(:user) to insert user into db\n \"\"\"\n import Helper.Utils, only: [done: 1]\n\n alias GroupherServer.Repo\n alias GroupherServer.{CMS, Accounts, Delivery}\n\n defp mock_meta(:post) do\n body = Faker.Lorem.sentence(%Range{first: 80, last: 120})\n\n %{\n title: Faker.Lorem.Shakespeare.king_richard_iii(),\n body: body,\n digest: String.slice(body, 1, 150),\n length: String.length(body),\n author: mock(:author),\n views: Enum.random(0..2000),\n communities: [\n mock(:community),\n mock(:community)\n ]\n }\n end\n\n defp mock_meta(:video) do\n desc = Faker.Lorem.sentence(%Range{first: 15, last: 60})\n\n %{\n title: Faker.Lorem.Shakespeare.king_richard_iii(),\n poster: Faker.Avatar.image_url(),\n desc: desc,\n duration: \"03:30\",\n duration_sec: Enum.random(300..12000),\n source: \"youtube\",\n link: \"http://www.youtube.com/video/1\",\n original_author: \"simon\",\n original_author_link: \"http://www.youtube.com/user/1\",\n author: mock(:author),\n views: Enum.random(0..2000),\n communities: [\n mock(:community),\n mock(:community)\n ]\n }\n end\n\n defp mock_meta(:repo) do\n desc = Faker.Lorem.sentence(%Range{first: 15, last: 60})\n\n %{\n repo_name: Faker.Lorem.Shakespeare.king_richard_iii(),\n desc: desc,\n readme: desc,\n language: \"javascript\",\n author: mock(:author),\n repo_link: \"http://www.github.com/mydearxym\",\n producer: \"mydearxym\",\n producer_link: \"http://www.github.com/mydearxym\",\n repo_star_count: Enum.random(0..2000),\n repo_fork_count: Enum.random(0..2000),\n repo_watch_count: Enum.random(0..2000),\n views: Enum.random(0..2000),\n communities: [\n mock(:community),\n mock(:community)\n ]\n }\n end\n\n defp mock_meta(:job) do\n body = Faker.Lorem.sentence(%Range{first: 80, last: 120})\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n title: Faker.Lorem.Shakespeare.king_richard_iii(),\n company: Faker.Company.name(),\n company_logo: Faker.Avatar.image_url(),\n location: \"location #{unique_num}\",\n body: body,\n digest: String.slice(body, 1, 150),\n length: String.length(body),\n author: mock(:author),\n views: Enum.random(0..2000),\n communities: [\n mock(:community)\n ]\n }\n end\n\n defp mock_meta(:comment) do\n body = Faker.Lorem.sentence(%Range{first: 30, last: 80})\n\n %{body: body}\n end\n\n defp mock_meta(:mention) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n from_user: mock(:user),\n to_user: mock(:user),\n source_id: \"1\",\n source_type: \"post\",\n source_preview: \"source_preview #{unique_num}.\"\n }\n end\n\n defp mock_meta(:author) do\n %{role: \"normal\", user: mock(:user)}\n end\n\n defp mock_meta(:communities_threads) do\n %{community_id: 1, thread_id: 1}\n end\n\n defp mock_meta(:thread) do\n unique_num = System.unique_integer([:positive, :monotonic])\n %{title: \"thread #{unique_num}\", raw: \"thread #{unique_num}\", index: :rand.uniform(20)}\n end\n\n defp mock_meta(:community) do\n unique_num = System.unique_integer([:positive, :monotonic])\n random_num = Enum.random(0..2000)\n\n %{\n title: \"community_#{random_num}_#{unique_num}\",\n desc: \"community desc\",\n raw: \"community_#{unique_num}\",\n logo: \"https://coderplanets.oss-cn-beijing.aliyuncs.com/icons/pl/elixir.svg\",\n author: mock(:user)\n }\n end\n\n defp mock_meta(:category) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n title: \"category#{unique_num}\",\n raw: \"category#{unique_num}\",\n author: mock(:author)\n }\n end\n\n defp mock_meta(:article_tag) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n title: \"#{Faker.Pizza.cheese()} #{unique_num}\",\n thread: \"POST\",\n color: \"YELLOW\",\n # community: Faker.Pizza.topping(),\n community: mock(:community),\n author: mock(:author)\n # user_id: 1\n }\n end\n\n defp mock_meta(:sys_notification) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n source_id: \"#{unique_num}\",\n source_title: \"#{Faker.Pizza.cheese()}\",\n source_type: \"post\",\n source_preview: \"#{Faker.Pizza.cheese()}\"\n }\n end\n\n defp mock_meta(:user) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n # username: \"#{Faker.Name.first_name()} #{unique_num}\",\n nickname: \"#{Faker.Name.first_name()} #{unique_num}\",\n bio: Faker.Lorem.Shakespeare.romeo_and_juliet(),\n avatar: Faker.Avatar.image_url()\n }\n end\n\n defp mock_meta(:github_profile) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n id: \"#{Faker.Name.first_name()} #{unique_num}\",\n login: \"#{Faker.Name.first_name()} #{unique_num}\",\n github_id: \"#{unique_num + 1000}\",\n node_id: \"#{unique_num + 2000}\",\n access_token: \"#{unique_num + 3000}\",\n bio: Faker.Lorem.Shakespeare.romeo_and_juliet(),\n company: Faker.Company.name(),\n location: \"chengdu\",\n email: Faker.Internet.email(),\n avatar_url: Faker.Avatar.image_url(),\n html_url: Faker.Avatar.image_url(),\n followers: unique_num * unique_num,\n following: unique_num * unique_num * unique_num\n }\n end\n\n def mock_attrs(_, attrs \\\\ %{})\n def mock_attrs(:user, attrs), do: mock_meta(:user) |> Map.merge(attrs)\n def mock_attrs(:author, attrs), do: mock_meta(:author) |> Map.merge(attrs)\n def mock_attrs(:post, attrs), do: mock_meta(:post) |> Map.merge(attrs)\n def mock_attrs(:video, attrs), do: mock_meta(:video) |> Map.merge(attrs)\n def mock_attrs(:repo, attrs), do: mock_meta(:repo) |> Map.merge(attrs)\n def mock_attrs(:job, attrs), do: mock_meta(:job) |> Map.merge(attrs)\n def mock_attrs(:community, attrs), do: mock_meta(:community) |> Map.merge(attrs)\n def mock_attrs(:thread, attrs), do: mock_meta(:thread) |> Map.merge(attrs)\n def mock_attrs(:mention, attrs), do: mock_meta(:mention) |> Map.merge(attrs)\n\n def mock_attrs(:communities_threads, attrs),\n do: mock_meta(:communities_threads) |> Map.merge(attrs)\n\n def mock_attrs(:article_tag, attrs), do: mock_meta(:article_tag) |> Map.merge(attrs)\n def mock_attrs(:sys_notification, attrs), do: mock_meta(:sys_notification) |> Map.merge(attrs)\n def mock_attrs(:category, attrs), do: mock_meta(:category) |> Map.merge(attrs)\n def mock_attrs(:github_profile, attrs), do: mock_meta(:github_profile) |> Map.merge(attrs)\n\n # NOTICE: avoid Recursive problem\n # bad example:\n # mismatch mismatch\n # | |\n # defp mock(:user), do: Accounts.User |> struct(mock_meta(:community))\n\n # this line of code will cause SERIOUS Recursive problem\n\n defp mock(:post), do: CMS.Post |> struct(mock_meta(:post))\n defp mock(:video), do: CMS.Video |> struct(mock_meta(:video))\n defp mock(:repo), do: CMS.Repo |> struct(mock_meta(:repo))\n defp mock(:job), do: CMS.Job |> struct(mock_meta(:job))\n defp mock(:comment), do: CMS.Comment |> struct(mock_meta(:comment))\n defp mock(:mention), do: Delivery.Mention |> struct(mock_meta(:mention))\n defp mock(:author), do: CMS.Author |> struct(mock_meta(:author))\n defp mock(:category), do: CMS.Category |> struct(mock_meta(:category))\n defp mock(:article_tag), do: CMS.Tag |> struct(mock_meta(:article_tag))\n\n defp mock(:sys_notification),\n do: Delivery.SysNotification |> struct(mock_meta(:sys_notification))\n\n defp mock(:user), do: Accounts.User |> struct(mock_meta(:user))\n defp mock(:community), do: CMS.Community |> struct(mock_meta(:community))\n defp mock(:thread), do: CMS.Thread |> struct(mock_meta(:thread))\n\n defp mock(:communities_threads),\n do: CMS.CommunityThread |> struct(mock_meta(:communities_threads))\n\n defp mock(factory_name, attributes) do\n factory_name |> mock() |> struct(attributes)\n end\n\n # \"\"\"\n # not use changeset because in test we may insert some attrs which not in schema\n # like: views, insert/update ... to test filter-sort,when ...\n # \"\"\"\n def db_insert(factory_name, attributes \\\\ []) do\n Repo.insert(mock(factory_name, attributes))\n end\n\n def db_insert_multi(factory_name, count \\\\ 2) do\n results =\n Enum.reduce(1..count, [], fn _, acc ->\n {:ok, value} = db_insert(factory_name)\n acc ++ [value]\n end)\n\n results |> done\n end\n\n alias GroupherServer.Accounts.User\n\n def mock_sys_notification(count \\\\ 3) do\n # {:ok, sys_notifications} = db_insert_multi(:sys_notification, count)\n db_insert_multi(:sys_notification, count)\n end\n\n def mock_mentions_for(%User{id: _to_user_id} = user, count \\\\ 3) do\n {:ok, users} = db_insert_multi(:user, count)\n\n Enum.map(users, fn u ->\n unique_num = System.unique_integer([:positive, :monotonic])\n\n info = %{\n source_id: \"1\",\n source_title: \"Title #{unique_num}\",\n source_type: \"post\",\n source_preview: \"preview #{unique_num}\"\n }\n\n {:ok, _} = Delivery.mention_someone(u, user, info)\n end)\n end\n\n def mock_notifications_for(%User{id: _to_user_id} = user, count \\\\ 3) do\n {:ok, users} = db_insert_multi(:user, count)\n\n Enum.map(users, fn u ->\n unique_num = System.unique_integer([:positive, :monotonic])\n\n info = %{\n source_id: \"1\",\n source_title: \"Title #{unique_num}\",\n source_type: \"post\",\n source_preview: \"preview #{unique_num}\",\n action: \"like\"\n }\n\n {:ok, _} = Delivery.notify_someone(u, user, info)\n end)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,368,null,null,null,null,null,null,null,null,null,null,null,null,140,null,null],"name":"lib/groupher_server/cms/passport.ex","source":"defmodule GroupherServer.CMS.Passport do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n @type t :: %Passport{}\n schema \"cms_passports\" do\n field(:rules, :map)\n belongs_to(:user, Accounts.User)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Passport{} = passport, attrs) do\n passport\n |> cast(attrs, [:rules, :user_id])\n |> validate_required([:rules, :user_id])\n |> unique_constraint(:user_id)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,null,3,null,null,null,null,null,null,211,79,null,null,null,null,null,null,null,null,139,null,5,5,null,null,134,134,null,null,null,null,4,4,null,3,null,null,1,null,null,null,null,null,1,null,null,null,139,null,null,null,139,null,null],"name":"lib/groupher_server/cms/delegates/passport_curd.ex","source":"defmodule GroupherServer.CMS.Delegate.PassportCURD do\n @moduledoc \"\"\"\n passport curd\n \"\"\"\n import Helper.Utils, only: [done: 1, deep_merge: 2]\n import Ecto.Query, warn: false\n import ShortMaps\n\n alias Helper.{NestedFilter, ORM}\n alias GroupherServer.CMS.Passport, as: UserPasport\n alias GroupherServer.{Accounts, Repo}\n\n # https://medium.com/front-end-hacking/use-github-oauth-as-your-sso-seamlessly-with-react-3e2e3b358fa1\n # http://www.ubazu.com/using-postgres-jsonb-columns-in-ecto\n # http://www.ubazu.com/using-postgres-jsonb-columns-in-ecto\n\n def paged_passports(community, key) do\n UserPasport\n |> where([p], fragment(\"(?->?->>?)::boolean = ?\", p.rules, ^community, ^key, true))\n |> Repo.all()\n |> done\n end\n\n @doc \"\"\"\n return a user's passport in CMS context\n \"\"\"\n def get_passport(%Accounts.User{} = user) do\n with {:ok, passport} <- ORM.find_by(UserPasport, user_id: user.id) do\n {:ok, passport.rules}\n end\n end\n\n # TODO passport should be public utils\n @doc \"\"\"\n insert or update a user's passport in CMS context\n \"\"\"\n def stamp_passport(rules, %Accounts.User{id: user_id}) do\n case ORM.find_by(UserPasport, user_id: user_id) do\n {:ok, passport} ->\n rules = passport.rules |> deep_merge(rules) |> reject_invalid_rules\n passport |> ORM.update(~m(rules)a)\n\n {:error, _} ->\n rules = rules |> reject_invalid_rules\n UserPasport |> ORM.create(~m(user_id rules)a)\n end\n end\n\n def erase_passport(rules, %Accounts.User{id: user_id}) when is_list(rules) do\n with {:ok, passport} <- ORM.find_by(UserPasport, user_id: user_id) do\n case pop_in(passport.rules, rules) do\n {nil, _} ->\n {:error, \"#{rules} not found\"}\n\n {_, lefts} ->\n passport |> ORM.update(%{rules: lefts})\n end\n end\n end\n\n def delete_passport(%Accounts.User{id: user_id}) do\n ORM.findby_delete!(UserPasport, ~m(user_id)a)\n end\n\n defp reject_invalid_rules(rules) when is_map(rules) do\n rules |> NestedFilter.drop_by_value([false]) |> reject_empty_values\n end\n\n defp reject_empty_values(map) when is_map(map) do\n for {k, v} <- map, v != %{}, into: %{}, do: {k, v}\n end\nend"},{"coverage":[null,null,null,361,null,null,null,null,null,null,null,361,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/router.ex","source":"defmodule GroupherServerWeb.Router do\n use GroupherServerWeb, :router\n\n pipeline :api do\n plug(:accepts, [\"json\"])\n plug(GroupherServerWeb.Context)\n end\n\n scope \"/graphiql\" do\n pipe_through(:api)\n\n forward(\n \"/\",\n Absinthe.Plug.GraphiQL,\n schema: GroupherServerWeb.Schema,\n pipeline: {ApolloTracing.Pipeline, :plug},\n interface: :playground,\n context: %{pubsub: GroupherServerWeb.Endpoint}\n )\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,0,null,0,null,null,0,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,9,null,4,null,null,1,null,null,4,null,null,0,null,null,null,null,null,null],"name":"lib/groupher_server/cms/utils/matcher.ex","source":"defmodule GroupherServer.CMS.Helper.MatcherOld do\n @moduledoc \"\"\"\n this module defined the matches and handy guard ...\n \"\"\"\n import Ecto.Query, warn: false\n\n alias GroupherServer.CMS.{\n Community,\n Post,\n Video,\n Repo,\n Job,\n PostFavorite,\n JobFavorite,\n PostStar,\n JobStar,\n PostComment,\n JobComment,\n Tag,\n Community,\n PostCommentLike,\n PostCommentDislike\n }\n\n @support_thread [:post, :video, :repo, :job]\n @support_react [:favorite, :star, :watch, :comment, :article_tag, :self]\n\n defguard valid_thread(thread) when thread in @support_thread\n defguard invalid_thread(thread) when thread not in @support_thread\n\n defguard valid_reaction(thread, react)\n when valid_thread(thread) and react in @support_react\n\n defguard invalid_reaction(thread, react)\n when invalid_thread(thread) and react not in @support_react\n\n defguard valid_feeling(feel) when feel in [:like, :dislike]\n\n # posts ...\n def match_action(:post, :self), do: {:ok, %{target: Post, reactor: Post, preload: :author}}\n\n def match_action(:post, :favorite),\n do: {:ok, %{target: Post, reactor: PostFavorite, preload: :user, preload_right: :post}}\n\n def match_action(:post, :star), do: {:ok, %{target: Post, reactor: PostStar, preload: :user}}\n def match_action(:post, :article_tag), do: {:ok, %{target: Post, reactor: Tag}}\n def match_action(:post, :community), do: {:ok, %{target: Post, reactor: Community}}\n\n def match_action(:post, :comment),\n do: {:ok, %{target: Post, reactor: PostComment, preload: :author}}\n\n def match_action(:post_comment, :like),\n do: {:ok, %{target: PostComment, reactor: PostCommentLike}}\n\n def match_action(:post_comment, :dislike),\n do: {:ok, %{target: PostComment, reactor: PostCommentDislike}}\n\n # videos ...\n def match_action(:video, :community), do: {:ok, %{target: Video, reactor: Community}}\n\n # repos ...\n def match_action(:repo, :community), do: {:ok, %{target: Repo, reactor: Community}}\n\n # jobs ...\n def match_action(:job, :self), do: {:ok, %{target: Job, reactor: Job, preload: :author}}\n def match_action(:job, :community), do: {:ok, %{target: Job, reactor: Community}}\n def match_action(:job, :star), do: {:ok, %{target: Job, reactor: JobStar, preload: :user}}\n def match_action(:job, :article_tag), do: {:ok, %{target: Job, reactor: Tag}}\n\n def match_action(:job, :comment),\n do: {:ok, %{target: Job, reactor: JobComment, preload: :author}}\n\n def match_action(:job, :favorite),\n do: {:ok, %{target: Job, reactor: JobFavorite, preload: :user}}\n\n def dynamic_where(thread, id) do\n case thread do\n :post ->\n {:ok, dynamic([p], p.post_id == ^id)}\n\n :post_comment ->\n {:ok, dynamic([p], p.post_comment_id == ^id)}\n\n :job ->\n {:ok, dynamic([p], p.job_id == ^id)}\n\n :job_comment ->\n {:ok, dynamic([p], p.job_comment_id == ^id)}\n\n _ ->\n {:error, 'where is not match'}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,84,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,29,null,null],"name":"lib/groupher_server/delivery/mention.ex","source":"defmodule GroupherServer.Delivery.Mention do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id source_title source_id source_type source_preview)a\n @optional_fields ~w(parent_id parent_type read)a\n\n @type t :: %Mention{}\n schema \"mentions\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:parent_id, :string)\n field(:parent_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Mention{} = mention, attrs) do\n mention\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,10,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/cms/job_star.ex","source":"defmodule GroupherServer.CMS.JobStar do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Job\n\n @required_fields ~w(user_id job_id)a\n\n @type t :: %JobStar{}\n schema \"jobs_stars\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:job, Job, foreign_key: :job_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%JobStar{} = job_star, attrs) do\n # |> unique_constraint(:user_id, name: :favorites_user_id_article_id_index)\n job_star\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:user_id, name: :jobs_stars_user_id_job_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server_web.ex","source":"defmodule GroupherServerWeb do\n @moduledoc \"\"\"\n The entrypoint for defining your web interface, such\n as controllers, views, channels and so on.\n\n This can be used in your application as:\n\n use GroupherServerWeb, :controller\n use GroupherServerWeb, :view\n\n The definitions below will be executed for every view,\n controller, etc, so keep them short and clean, focused\n on imports, uses and aliases.\n\n Do NOT define functions inside the quoted expressions\n below. Instead, define any helper function in modules\n and import those modules here.\n \"\"\"\n\n def controller do\n quote do\n use Phoenix.Controller, namespace: GroupherServerWeb\n import Plug.Conn\n import GroupherServerWeb.Router.Helpers\n import GroupherServerWeb.Gettext\n end\n end\n\n def view do\n quote do\n use Phoenix.View,\n root: \"lib/groupher_server_web/templates\",\n namespace: GroupherServerWeb\n\n # Import convenience functions from controllers\n import Phoenix.Controller, only: [get_flash: 2, view_module: 1]\n\n import GroupherServerWeb.Router.Helpers\n import GroupherServerWeb.ErrorHelpers\n import GroupherServerWeb.Gettext\n end\n end\n\n def router do\n quote do\n use Phoenix.Router\n import Plug.Conn\n import Phoenix.Controller\n end\n end\n\n def channel do\n quote do\n use Phoenix.Channel\n import GroupherServerWeb.Gettext\n end\n end\n\n @doc \"\"\"\n When used, dispatch to the appropriate controller/view/etc.\n \"\"\"\n defmacro __using__(which) when is_atom(which) do\n apply(__MODULE__, which, [])\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,14,null,14,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,105,null,null,null,null,null,null,0,null,null,null,null,null,0,null,null,null,60,60,null,null,null,142,null,null,null,15,null,null,null,5,4,1,null,null,0,0,null,null,null,null,6,null,39,8,null,null,null,null,null,6,null,null,null,null,null,4,null,null,null,null,4,null],"name":"lib/helper/utils.ex","source":"defmodule Helper.Utils do\n @moduledoc \"\"\"\n unitil functions\n \"\"\"\n import Ecto.Query, warn: false\n import Helper.ErrorHandler\n import Helper.ErrorCode\n\n def get_config(section, key, app \\\\ :groupher_server) do\n app\n |> Application.get_env(section)\n # |> IO.inspect(label: \"debug ci\")\n |> case do\n nil -> \"\"\n config -> Keyword.get(config, key)\n end\n end\n\n @doc \"\"\"\n handle General {:ok, ..} or {:error, ..} return\n \"\"\"\n def done(nil, :boolean), do: {:ok, false}\n def done(_, :boolean), do: {:ok, true}\n def done(nil, err_msg), do: {:error, err_msg}\n def done({:ok, _}, with: result), do: {:ok, result}\n\n def done({:ok, %{id: id}}, :status), do: {:ok, %{done: true, id: id}}\n def done({:error, _}, :status), do: {:ok, %{done: false}}\n\n def done(nil, queryable, id), do: {:error, not_found_formater(queryable, id)}\n def done(result, _, _), do: {:ok, result}\n\n def done(nil), do: {:error, \"record not found.\"}\n\n # def done({:error, error}), do: {:error, error}\n def done(result), do: {:ok, result}\n\n @doc \"\"\"\n see: https://hexdocs.pm/absinthe/errors.html#content for error format\n \"\"\"\n def handle_absinthe_error(resolution, err_msg, code) when is_integer(code) do\n resolution\n |> Absinthe.Resolution.put_result({:error, message: err_msg, code: code})\n end\n\n def handle_absinthe_error(resolution, err_msg) when is_list(err_msg) do\n # %{resolution | value: [], errors: transform_errors(changeset)}\n resolution\n # |> Absinthe.Resolution.put_result({:error, err_msg})\n |> Absinthe.Resolution.put_result({:error, message: err_msg, code: ecode()})\n end\n\n def handle_absinthe_error(resolution, err_msg) when is_binary(err_msg) do\n resolution\n # |> Absinthe.Resolution.put_result({:error, err_msg})\n |> Absinthe.Resolution.put_result({:error, message: err_msg, code: ecode()})\n end\n\n def map_key_stringify(%{__struct__: _} = map) when is_map(map) do\n map = Map.from_struct(map)\n map |> Enum.reduce(%{}, fn {key, val}, acc -> Map.put(acc, to_string(key), val) end)\n end\n\n def map_key_stringify(map) when is_map(map) do\n map |> Enum.reduce(%{}, fn {key, val}, acc -> Map.put(acc, to_string(key), val) end)\n end\n\n def deep_merge(left, right) do\n Map.merge(left, right, &deep_resolve/3)\n end\n\n def tobe_integer(val) do\n if is_integer(val),\n do: val,\n else: val |> String.to_integer()\n end\n\n def repeat(times, [x]) when is_integer(x), do: to_string(for _ <- 1..times, do: x)\n def repeat(times, x), do: for(_ <- 1..times, do: x)\n\n def add(num, offset \\\\ 1) when is_integer(num) and is_integer(offset), do: num + offset\n\n def map_atom_value(attrs, :string) do\n results =\n Enum.map(attrs, fn {k, v} ->\n if is_atom(v) do\n {k, to_string(v)}\n else\n {k, v}\n end\n end)\n\n results |> Enum.into(%{})\n end\n\n # Key exists in both maps, and both values are maps as well.\n # These can be merged recursively.\n # defp deep_resolve(_key, left = %{},right = %{}) do\n defp deep_resolve(_key, %{} = left, %{} = right), do: deep_merge(left, right)\n\n # Key exists in both maps, but at least one of the values is\n # NOT a map. We fall back to standard merge behavior, preferring\n # the value on the right.\n defp deep_resolve(_key, _left, right), do: right\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,53,null,null,null,null,null,null,null,null,null,null,null,377,null,377,364,null,null,null,null,null],"name":"test/support/conn_case.ex","source":"defmodule GroupherServerWeb.ConnCase do\n @moduledoc \"\"\"\n This module defines the test case to be used by\n tests that require setting up a connection.\n\n Such tests rely on `Phoenix.ConnTest` and also\n import other functionality to make it easier\n to build common datastructures and query the data layer.\n\n Finally, if the test case interacts with the database,\n it cannot be async. For this reason, every test runs\n inside a transaction which is reset at the beginning\n of the test unless the test case is marked as async.\n \"\"\"\n\n use ExUnit.CaseTemplate\n\n using do\n quote do\n # Import conveniences for testing with connections\n use Phoenix.ConnTest\n import GroupherServerWeb.Router.Helpers\n\n # The default endpoint for testing\n @endpoint GroupherServerWeb.Endpoint\n end\n end\n\n setup tags do\n :ok = Ecto.Adapters.SQL.Sandbox.checkout(GroupherServer.Repo)\n\n unless tags[:async] do\n Ecto.Adapters.SQL.Sandbox.mode(GroupherServer.Repo, {:shared, self()})\n end\n\n {:ok, conn: Phoenix.ConnTest.build_conn()}\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,125,null,null,null,null,null,null,null,null,null,null,null,62,null,null],"name":"lib/groupher_server/cms/job_favorite.ex","source":"defmodule GroupherServer.CMS.JobFavorite do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Job\n\n @required_fields ~w(user_id job_id)a\n\n @type t :: %JobFavorite{}\n schema \"jobs_favorites\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:job, Job, foreign_key: :job_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%JobFavorite{} = job_favorite, attrs) do\n job_favorite\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:user_id, name: :jobs_favorites_user_id_job_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,983,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,6,null,null],"name":"lib/groupher_server/cms/job.ex","source":"defmodule GroupherServer.CMS.Job do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, JobFavorite, Tag}\n\n @required_fields ~w(title company company_logo location body digest length)a\n @optional_fields ~w(link_addr link_source min_education)a\n\n @type t :: %Job{}\n schema \"cms_jobs\" do\n field(:title, :string)\n field(:company, :string)\n field(:bonus, :string)\n field(:company_logo, :string)\n field(:location, :string)\n field(:desc, :string)\n field(:body, :string)\n belongs_to(:author, Author)\n field(:views, :integer, default: 0)\n field(:link_addr, :string)\n field(:link_source, :string)\n\n field(:min_salary, :integer, default: 0)\n field(:max_salary, :integer, default: 10_000_000)\n\n field(:min_experience, :integer, default: 1)\n field(:max_experience, :integer, default: 3)\n\n # college - bachelor - master - doctor\n field(:min_education, :string)\n\n field(:digest, :string)\n field(:length, :integer)\n\n # has_many(:comments, {\"jobs_comments\", JobComment})\n has_many(:favorites, {\"jobs_favorites\", JobFavorite})\n # has_many(:stars, {\"posts_stars\", PostStar})\n\n many_to_many(\n :tags,\n Tag,\n join_through: \"jobs_tags\",\n join_keys: [job_id: :id, tag_id: :id],\n # :delete_all will only remove data from the join source\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_jobs\",\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Job{} = job, attrs) do\n job\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,26,26,null,26,26,26,null,26,null,4,4,null,4,4,null,null,22,22,22,22,null,null,null,null,null,17,9,9,null,9,null,9,9,null,9,9,null,9,9,null,null,null,null,null,null,null,null,18,null,null,null,9,9,null,9,null,9,null,null,null,null,6,6,6,null,null,null,null,2,2,null,null,null,null,1,1,1,null,null,null,null,2,2,null,null,null,null,1,1,1,null,null,null],"name":"lib/groupher_server/statistics/delegates/throttle.ex","source":"defmodule GroupherServer.Statistics.Delegate.Throttle do\n import Ecto.Query, warn: false\n import ShortMaps\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.Statistics.PublishThrottle\n alias Helper.{ORM}\n\n def log_publish_action(%User{id: user_id}) do\n cur_date = Timex.today() |> Date.to_iso8601()\n cur_datetime = DateTime.utc_now() |> DateTime.to_iso8601()\n\n last_publish_time = cur_datetime\n publish_hour = cur_datetime\n publish_date = cur_date\n\n case PublishThrottle |> ORM.find_by(~m(user_id)a) do\n {:ok, record} ->\n date_count = record.date_count + 1\n hour_count = record.hour_count + 1\n\n attrs = ~m(user_id publish_date publish_hour date_count hour_count last_publish_time)a\n record |> ORM.update(attrs)\n\n {:error, _} ->\n date_count = 1\n hour_count = 1\n attrs = ~m(user_id publish_date publish_hour date_count hour_count last_publish_time)a\n PublishThrottle |> ORM.create(attrs)\n end\n end\n\n # auto run check for same hour / day\n def load_throttle_record(%User{id: user_id}) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n date_count = if is_same_day?(record.publish_date), do: record.date_count, else: 0\n hour_count = if is_same_hour?(record.publish_hour), do: record.hour_count, else: 0\n\n case date_count !== 0 or hour_count !== 0 do\n true ->\n cur_date = Timex.today() |> Date.to_iso8601()\n cur_datetime = DateTime.utc_now() |> DateTime.to_iso8601()\n\n publish_hour = cur_datetime\n publish_date = cur_date\n\n attrs = ~m(publish_date publish_hour date_count hour_count)a\n record |> ORM.update(attrs)\n\n false ->\n {:ok, record}\n end\n end\n end\n\n defp is_same_day?(datetime) do\n datetime |> Timex.to_date() |> Timex.equal?(Timex.to_date(Timex.now()))\n end\n\n defp is_same_hour?(datetime) do\n {_date, {record_hour, _min, _sec}} = datetime |> Timex.to_erl()\n {_date, {cur_hour, _min, _sec}} = Timex.now() |> Timex.to_erl()\n\n same_hour? = record_hour == cur_hour\n\n is_same_day?(datetime) and same_hour?\n end\n\n # NOTE: the mock_xxx is only use for test\n def mock_throttle_attr(:last_publish_time, %User{id: user_id}, minutes: minutes) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n last_publish_time = Timex.shift(record.last_publish_time, minutes: minutes)\n record |> ORM.update(~m(last_publish_time)a)\n end\n end\n\n def mock_throttle_attr(:hour_count, %User{id: user_id}, count: hour_count) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n record |> ORM.update(~m(hour_count)a)\n end\n end\n\n def mock_throttle_attr(:publish_hour, %User{id: user_id}, hours: hours) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n publish_hour = Timex.shift(record.publish_hour, hours: hours)\n record |> ORM.update(~m(publish_hour)a)\n end\n end\n\n def mock_throttle_attr(:date_count, %User{id: user_id}, count: date_count) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n record |> ORM.update(~m(date_count)a)\n end\n end\n\n def mock_throttle_attr(:publish_date, %User{id: user_id}, days: days) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n publish_date = Timex.shift(record.publish_hour, days: days)\n record |> ORM.update(~m(publish_date)a)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,250,null,null,null,42,null,null],"name":"lib/groupher_server_web/middleware/authorize.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# ---\ndefmodule GroupherServerWeb.Middleware.Authorize do\n @behaviour Absinthe.Middleware\n import Helper.Utils, only: [handle_absinthe_error: 3]\n import Helper.ErrorCode\n\n def call(%{context: %{cur_user: _}} = resolution, _info), do: resolution\n\n def call(resolution, _) do\n resolution\n |> handle_absinthe_error(\"Authorize: need login\", ecode(:account_login))\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/helper/gql_schema_suite.ex","source":"defmodule Helper.GqlSchemaSuite do\n @moduledoc \"\"\"\n helper for reduce boilerplate import/use/alias in absinthe schema\n \"\"\"\n\n defmacro __using__(_opts) do\n quote do\n use Absinthe.Schema.Notation\n use Absinthe.Ecto, repo: GroupherServer.Repo\n\n alias GroupherServerWeb.Resolvers, as: R\n alias GroupherServerWeb.Middleware, as: M\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,37,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/sys_notification_mail.ex","source":"defmodule GroupherServer.Accounts.SysNotificationMail do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id source_id source_type)a\n @optional_fields ~w(source_preview read)a\n\n @type t :: %SysNotificationMail{}\n schema \"sys_notification_mails\" do\n belongs_to(:user, User)\n\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%SysNotificationMail{} = sys_notication_mail, attrs) do\n sys_notication_mail\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,0,null,null,0,0,null,null,null,0,null,null,null],"name":"lib/groupher_server_web/middleware/github_user.ex","source":"defmodule GroupherServerWeb.Middleware.GithubUser do\n @behaviour Absinthe.Middleware\n\n import Helper.Utils, only: [handle_absinthe_error: 2]\n alias Helper.OAuth2.Github\n\n def call(%{arguments: %{code: code}} = resolution, _) do\n # IO.inspect(access_token, label: \"GithubUser middleware token\")\n\n case Github.user_profile(code) do\n {:ok, user} ->\n # IO.inspect user,label: \"get ok\"\n arguments = resolution.arguments |> Map.merge(%{github_user: user})\n %{resolution | arguments: arguments}\n\n {:error, err_msg} ->\n resolution\n |> handle_absinthe_error(err_msg)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,2,null,null,null,4,null,null,null,2,null,null],"name":"lib/groupher_server_web/middleware/viewer_did_convert.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\n\ndefmodule GroupherServerWeb.Middleware.ViewerDidConvert do\n @behaviour Absinthe.Middleware\n\n def call(%{value: nil} = resolution, _) do\n %{resolution | value: false}\n end\n\n def call(%{value: []} = resolution, _) do\n %{resolution | value: false}\n end\n\n def call(%{value: [_]} = resolution, _) do\n %{resolution | value: true}\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/delivery/delivery_mutations.ex","source":"defmodule GroupherServerWeb.Schema.Delivery.Mutations do\n @moduledoc \"\"\"\n Delivery.Mutations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :delivery_mutations do\n field :mention_someone, :status do\n arg(:user_id, non_null(:id))\n\n arg(:source_id, non_null(:id))\n arg(:source_title, non_null(:string))\n arg(:source_type, non_null(:string))\n arg(:source_preview, non_null(:string))\n arg(:parent_id, :id)\n arg(:parent_type, :string)\n\n middleware(M.Authorize, :login)\n\n resolve(&R.Delivery.mention_someone/3)\n end\n\n field :publish_system_notification, :status do\n arg(:source_id, non_null(:id))\n arg(:source_title, non_null(:string))\n arg(:source_type, non_null(:string))\n arg(:source_preview, :string)\n\n middleware(M.Authorize, :login)\n # TODO: use delivery passport system instead of cms's\n middleware(M.Passport, claim: \"cms->system_notification.publish\")\n\n resolve(&R.Delivery.publish_system_notification/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null,null,5,null,null,null,null,7,null,null,7,null,null,null,1,null,1,1,null,null,null,null,null,null,null,7,7,7,5,5,null,5,null,null,null,null,1,1,null,null,null,null,null,null,1,null,null,null,null,1,null,1,null,null,null,1,null,null,null,null,1,null,1,null,null,null,null,null,null,null,null,null,1,null,null,null,9,9,null,null,null,null,3,3,null,null,null,null,null,null,null,6,6,6,null,6,null,null],"name":"lib/groupher_server/cms/delegates/community_curd.ex","source":"defmodule GroupherServer.CMS.Delegate.CommunityCURD do\n @moduledoc \"\"\"\n community curd\n \"\"\"\n import Ecto.Query, warn: false\n import GroupherServer.CMS.Helper.MatcherOld\n import Helper.Utils, only: [done: 1, map_atom_value: 2]\n import GroupherServer.CMS.Delegate.ArticleCURD, only: [ensure_author_exists: 1]\n import ShortMaps\n\n alias Helper.ORM\n alias Helper.QueryBuilder\n alias GroupherServer.{Accounts, Repo}\n\n alias GroupherServer.CMS.{\n Category,\n Community,\n CommunityEditor,\n CommunitySubscriber,\n Tag,\n Thread\n }\n\n @doc \"\"\"\n return paged community subscribers\n \"\"\"\n def community_members(:editors, %Community{id: id}, filters) do\n load_community_members(id, CommunityEditor, filters)\n end\n\n def community_members(:subscribers, %Community{id: id}, filters) do\n load_community_members(id, CommunitySubscriber, filters)\n end\n\n defp load_community_members(id, model, %{page: page, size: size} = filters) do\n model\n |> where([c], c.community_id == ^id)\n |> QueryBuilder.load_inner_users(filters)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n def update_editor(%Community{id: community_id}, title, %Accounts.User{id: user_id}) do\n clauses = ~m(user_id community_id)a\n\n with {:ok, _} <- CommunityEditor |> ORM.update_by(clauses, ~m(title)a) do\n Accounts.User |> ORM.find(user_id)\n end\n end\n\n @doc \"\"\"\n create a Tag base on type: post / tuts / videos ...\n \"\"\"\n def create_tag(thread, attrs, %Accounts.User{id: user_id}) when valid_thread(thread) do\n with {:ok, action} <- match_action(thread, :article_tag),\n {:ok, author} <- ensure_author_exists(%Accounts.User{id: user_id}),\n {:ok, _community} <- ORM.find(Community, attrs.community_id) do\n attrs = attrs |> Map.merge(%{author_id: author.id})\n attrs = attrs |> map_atom_value(:string)\n\n action.reactor |> ORM.create(attrs)\n end\n end\n\n def update_tag(%{id: _id} = attrs) do\n attrs = attrs |> map_atom_value(:string)\n Tag |> ORM.find_update(%{id: attrs.id, title: attrs.title, color: attrs.color})\n end\n\n @doc \"\"\"\n get tags belongs to a community / thread\n \"\"\"\n def get_tags(%Community{id: community_id}, thread) when not is_nil(community_id) do\n thread = to_string(thread)\n\n Tag\n |> join(:inner, [t], c in assoc(t, :community))\n |> where([t, c], c.id == ^community_id and t.thread == ^thread)\n |> distinct([t], t.title)\n |> Repo.all()\n |> done()\n end\n\n def get_tags(%Community{raw: community_raw}, thread) when not is_nil(community_raw) do\n thread = to_string(thread)\n\n Tag\n |> join(:inner, [t], c in assoc(t, :community))\n |> where([t, c], c.raw == ^community_raw and t.thread == ^thread)\n |> distinct([t], t.title)\n |> Repo.all()\n |> done()\n end\n\n @doc \"\"\"\n get all paged tags\n \"\"\"\n def get_tags(%{page: page, size: size} = filter) do\n Tag\n |> QueryBuilder.filter_pack(filter)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n def create_category(%Category{title: title, raw: raw}, %Accounts.User{id: user_id}) do\n with {:ok, author} <- ensure_author_exists(%Accounts.User{id: user_id}) do\n Category |> ORM.create(%{title: title, raw: raw, author_id: author.id})\n end\n end\n\n def update_category(~m(%Category id title)a) do\n with {:ok, category} <- ORM.find(Category, id) do\n category |> ORM.update(~m(title)a)\n end\n end\n\n @doc \"\"\"\n TODO: create_thread\n \"\"\"\n def create_thread(attrs) do\n raw = to_string(attrs.raw)\n title = attrs.title\n index = attrs |> Map.get(:index, 0)\n\n Thread |> ORM.create(~m(title raw index)a)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/statistics/statistics_mutations.ex","source":"defmodule GroupherServerWeb.Schema.Statistics.Mutations do\n @moduledoc \"\"\"\n Statistics mutations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :statistics_mutations do\n field :make_contrubute, :user_contribute do\n arg(:user_id, non_null(:id))\n\n resolve(&R.Statistics.make_contrubute/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,254,null,null,null,148,148,148,null,148,null,null,null,38,null,38,null,null,null,59,null,59,null,null,null,61,61,null,61,null,61,null,61,null,null,null,6,null,6,null,6,null,null,null,312,312,null,312,null,null,null],"name":"test/support/conn_simulator.ex","source":"defmodule GroupherServer.Test.ConnSimulator do\n @moduledoc \"\"\"\n mock user_conn, owner_conn, guest_conn\n \"\"\"\n import GroupherServer.Factory\n import Phoenix.ConnTest, only: [build_conn: 0]\n import Plug.Conn, only: [put_req_header: 3]\n\n alias GroupherServer.{Accounts, CMS}\n alias Helper.{Guardian, ORM}\n\n def simu_conn(:guest) do\n build_conn()\n end\n\n def simu_conn(:user) do\n user_attr = mock_attrs(:user)\n {:ok, user} = db_insert(:user, user_attr)\n token = gen_jwt_token(id: user.id)\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n def simu_conn(:owner, content) do\n token = gen_jwt_token(id: content.author.user.id)\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n def simu_conn(:user, %Accounts.User{} = user) do\n token = gen_jwt_token(id: user.id)\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n def simu_conn(:user, cms: passport_rules) do\n user_attr = mock_attrs(:user)\n {:ok, user} = db_insert(:user, user_attr)\n\n token = gen_jwt_token(id: user.id)\n\n {:ok, _passport} = CMS.stamp_passport(passport_rules, %Accounts.User{id: user.id})\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n def simu_conn(:user, %Accounts.User{} = user, cms: passport_rules) do\n token = gen_jwt_token(id: user.id)\n\n {:ok, _passport} = CMS.stamp_passport(passport_rules, %Accounts.User{id: user.id})\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n defp gen_jwt_token(clauses) do\n with {:ok, user} <- ORM.find_by(Accounts.User, clauses) do\n {:ok, token, _info} = Guardian.jwt_encode(user)\n\n \"Bearer #{token}\"\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,911,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,9,null,null,null,null,null],"name":"lib/groupher_server/cms/video.ex","source":"defmodule GroupherServer.CMS.Video do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, Tag}\n\n @required_fields ~w(title poster desc duration duration_sec source)a\n @optional_fields ~w(link original_author original_author_link publish_at pin markDelete)\n\n @type t :: %Video{}\n schema \"cms_videos\" do\n field(:title, :string)\n field(:poster, :string)\n field(:desc, :string)\n field(:duration, :string)\n field(:duration_sec, :integer)\n\n field(:source, :string)\n field(:link, :string)\n\n field(:original_author, :string)\n field(:original_author_link, :string)\n\n field(:views, :integer, default: 0)\n field(:pin, :boolean, default_value: false)\n field(:markDelete, :boolean, default_value: false)\n\n field(:publish_at, :utc_datetime)\n\n belongs_to(:author, Author)\n\n # has_many(:comments, {\"posts_comments\", PostComment})\n\n many_to_many(\n :tags,\n Tag,\n join_through: \"videos_tags\",\n join_keys: [video_id: :id, tag_id: :id],\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_videos\",\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Video{} = video, attrs) do\n video\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n\n # |> foreign_key_constraint(:posts_tags, name: :posts_tags_tag_id_fkey)\n # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,0,null,null,null,0,null,0,null],"name":"lib/groupher_server_web/middleware/put_root_source.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.PutRootSource do\n @behaviour Absinthe.Middleware\n\n # def call(%{source: %{id: id}} = resolution, _) do\n # arguments = resolution.arguments |> Map.merge(%{root_source_id: id})\n\n # %{resolution | arguments: arguments}\n # end\n\n def call(%{source: %{id: id}} = resolution, _) do\n arguments = resolution.arguments |> Map.merge(%{jj: id})\n\n %{resolution | arguments: arguments}\n # resolution\n end\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(resolution, _), do: resolution\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,911,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,9,null,null,null,null,null],"name":"lib/groupher_server/cms/repo.ex","source":"defmodule GroupherServer.CMS.Repo do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, RepoBuilder, Tag}\n\n @required_fields ~w(repo_name desc readme language producer producer_link repo_link repo_star_count repo_fork_count repo_watch_count)a\n @optional_fields ~w(views pin markDelete last_fetch_time)\n\n @type t :: %Repo{}\n schema \"cms_repos\" do\n field(:repo_name, :string)\n field(:desc, :string)\n field(:readme, :string)\n field(:language, :string)\n belongs_to(:author, Author)\n\n field(:repo_link, :string)\n field(:producer, :string)\n field(:producer_link, :string)\n\n field(:repo_star_count, :integer)\n field(:repo_fork_count, :integer)\n field(:repo_watch_count, :integer)\n\n field(:views, :integer, default: 0)\n field(:pin, :boolean, default_value: false)\n field(:markDelete, :boolean, default_value: false)\n\n field(:last_fetch_time, :utc_datetime)\n # TODO: replace RepoBuilder with paged user map\n has_many(:builders, {\"repos_builders\", RepoBuilder})\n\n many_to_many(\n :tags,\n Tag,\n join_through: \"repos_tags\",\n join_keys: [repo_id: :id, tag_id: :id],\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_repos\",\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Repo{} = repo, attrs) do\n repo\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n\n # |> foreign_key_constraint(:posts_tags, name: :posts_tags_tag_id_fkey)\n # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,693,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,432,null,null],"name":"lib/groupher_server/accounts/achievement.ex","source":"defmodule GroupherServer.Accounts.Achievement do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id)a\n @optional_fields ~w(contents_stared_count contents_favorited_count contents_watched_count followers_count reputation)a\n\n @type t :: %Achievement{}\n schema \"user_achievements\" do\n belongs_to(:user, User)\n\n field(:contents_stared_count, :integer, default: 0)\n field(:contents_favorited_count, :integer, default: 0)\n field(:contents_watched_count, :integer, default: 0)\n field(:followers_count, :integer, default: 0)\n field(:reputation, :integer, default: 0)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Achievement{} = achievement, attrs) do\n achievement\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/repo.ex","source":"defmodule GroupherServer.Repo do\n import Helper.Utils, only: [get_config: 2]\n\n use Ecto.Repo, otp_app: :groupher_server\n use Scrivener, page_size: get_config(:general, :page_size)\n\n @dialyzer {:nowarn_function, rollback: 1}\n\n @doc \"\"\"\n Dynamically loads the repository url from the\n DATABASE_URL environment variable.\n \"\"\"\n def init(_, opts) do\n {:ok, Keyword.put(opts, :url, System.get_env(\"DATABASE_URL\"))}\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,18,null,null,null,null,null,null,null,null,null,null,null,null,null,6,null,null,null,null],"name":"lib/groupher_server/statistics/community_contribute.ex","source":"defmodule GroupherServer.Statistics.CommunityContribute do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS\n\n @type t :: %CommunityContribute{}\n schema \"community_contributes\" do\n field(:count, :integer)\n field(:date, :date)\n # field(:community_id, :id)\n belongs_to(:community, CMS.Community)\n\n timestamps()\n end\n\n @doc false\n def changeset(%CommunityContribute{} = community_contribute, attrs) do\n community_contribute\n |> cast(attrs, [:date, :count, :community_id])\n |> validate_required([:date, :count, :community_id])\n |> foreign_key_constraint(:community_id)\n\n # |> unique_constraint(:community_id, name: :communities_threads_community_id_thread_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,33,null,null,null,null,null,null,null,null,null,null,null,null,null,14,null,null],"name":"lib/groupher_server/cms/post_comment_dislike.ex","source":"defmodule GroupherServer.CMS.PostCommentDislike do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.PostComment\n\n @required_fields ~w(post_comment_id user_id)a\n\n @type t :: %PostCommentDislike{}\n schema \"posts_comments_dislikes\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:post_comment, PostComment, foreign_key: :post_comment_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostCommentDislike{} = post_comment_dislike, attrs) do\n post_comment_dislike\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:post_comment_id)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user_id, name: :posts_comments_dislikes_user_id_post_comment_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,4,4,4,null,4,4,4,null,4,null,4,null,null,null,3,null,2,2,null,null,null,null,null,26,null,26,null,null,26,null,null,null,26,26,null,null,null,60,60,null,60,null,null,null,8,null,null,8,null,8,null,8,null,null,null,60,null,60,null,60,null,60,null,60,null,60,null,null,null,60,null,60,null,null,null,null,null,null,null,10,null,null,null,9,null,null,null,7,null,null,null,26,26,26,null,26,null,null,null,null,7,null,7,null,null,7,null,null,null,null,null,null,null,19,19,null,19,null,null,null,1,null,null,null,18,null,null,19,null,null,null,35,35,null,null,null,33,33,null,null,null,26,26,null,null,null,null,null,null,null,68,null,null,null,null,null,null,94,null,94,null,null,41,null,null,null,null,null,null,16,16,null,null,null,null,null,null,null,null,null,null,null,null,null,19,19,null,19,19,null,null,19,19,null,null,null,4,null,4,null,4,4,null,null,null,null,null,null,null,null,null,null,null,41,null,null],"name":"lib/groupher_server/delivery/delegates/utils.ex","source":"defmodule GroupherServer.Delivery.Delegate.Utils do\n @moduledoc \"\"\"\n The Delivery context.\n \"\"\"\n # commons\n import Ecto.Query, warn: false\n import Helper.Utils\n import ShortMaps\n\n alias GroupherServer.Repo\n\n alias GroupherServer.Delivery.{Notification, SysNotification, Mention, Record}\n alias GroupherServer.Accounts.User\n alias Helper.ORM\n\n def mailbox_status(%User{} = user) do\n filter = %{page: 1, size: 1, read: false}\n {:ok, mention_mail} = fetch_mails(user, Mention, filter)\n {:ok, notification_mail} = fetch_mails(user, Notification, filter)\n\n mention_count = mention_mail.total_count\n notification_count = notification_mail.total_count\n total_count = mention_count + notification_count\n\n has_mail = total_count > 0\n\n result = ~m(has_mail total_count mention_count notification_count)a\n {:ok, result}\n end\n\n def fetch_record(%User{id: user_id}), do: Record |> ORM.find_by(user_id: user_id)\n\n def mark_read_all(%User{} = user, :mention), do: Mention |> do_mark_read_all(user)\n def mark_read_all(%User{} = user, :notification), do: Notification |> do_mark_read_all(user)\n\n @doc \"\"\"\n fetch mentions / notifications\n \"\"\"\n def fetch_messages(:sys_notification, %User{} = user, %{page: page, size: size}) do\n {:ok, last_fetch_time} = get_last_fetch_time(SysNotification, user)\n\n mails =\n SysNotification\n |> order_by(desc: :inserted_at)\n |> where([m], m.inserted_at > ^last_fetch_time)\n |> ORM.paginater(~m(page size)a)\n |> done()\n\n record_operation(user, SysNotification, mails)\n mails\n end\n\n def fetch_messages(%User{} = user, queryable, %{page: _page, size: _size, read: read} = filter) do\n mails = fetch_mails_and_delete(user, queryable, filter)\n record_operation(queryable, read, mails)\n\n mails\n end\n\n defp fetch_mails(user, queryable, %{page: page, size: size, read: read}) do\n {:ok, last_fetch_time} = get_last_fetch_time(queryable, read, user)\n\n queryable\n |> where([m], m.to_user_id == ^user.id)\n |> where([m], m.inserted_at > ^last_fetch_time)\n |> where([m], m.read == ^read)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n defp fetch_mails_and_delete(user, queryable, %{page: page, size: size, read: read}) do\n {:ok, last_fetch_time} = get_last_fetch_time(queryable, read, user)\n\n query =\n queryable\n |> where([m], m.to_user_id == ^user.id)\n |> where([m], m.inserted_at > ^last_fetch_time)\n |> where([m], m.read == ^read)\n\n mails =\n query\n |> order_by(desc: :inserted_at)\n |> ORM.paginater(~m(page size)a)\n |> done()\n\n delete_items(query, mails)\n\n mails\n end\n\n defp record_operation(Mention, _read, {:ok, %{entries: []}}), do: {:ok, \"\"}\n defp record_operation(Notification, _read, {:ok, %{entries: []}}), do: {:ok, \"\"}\n defp record_operation(_, SysNotification, {:ok, %{entries: []}}), do: {:ok, \"\"}\n\n defp record_operation(Mention, read, {:ok, %{entries: entries}}) do\n do_record_operation(:mentions_record, read, {:ok, %{entries: entries}})\n end\n\n defp record_operation(Notification, read, {:ok, %{entries: entries}}) do\n do_record_operation(:notifications_record, read, {:ok, %{entries: entries}})\n end\n\n defp record_operation(%User{} = user, SysNotification, {:ok, %{entries: entries}}) do\n do_record_operation(user, :sys_notifications_record, {:ok, %{entries: entries}})\n end\n\n defp get_record_lasttime(entries) do\n first_insert = entries |> List.first() |> Map.get(:inserted_at)\n last_insert = entries |> List.last() |> Map.get(:inserted_at)\n newest_insert = Enum.max([first_insert, last_insert])\n\n newest_insert |> Timex.to_datetime() |> to_string\n end\n\n # sys_notification\n defp do_record_operation(%User{id: user_id}, record_name, {:ok, %{entries: entries}}) do\n record_last_fetch_time = get_record_lasttime(entries)\n\n attrs =\n %{user_id: user_id} |> Map.put(record_name, %{last_fetch_time: record_last_fetch_time})\n\n Record |> ORM.upsert_by([user_id: user_id], attrs)\n end\n\n # last_fetch_read_time\n # > the last fetch time of mails that is read\n # last_fetch_unread_time\n # > the last fetch time of mails that is read\n defp do_record_operation(record_name, read, {:ok, %{entries: entries}}) do\n record_last_fetch_time = get_record_lasttime(entries)\n user_id = entries |> List.first() |> Map.get(:to_user_id)\n\n attrs =\n case read do\n true ->\n %{user_id: user_id}\n |> Map.put(record_name, %{last_fetch_read_time: record_last_fetch_time})\n\n false ->\n %{user_id: user_id}\n |> Map.put(record_name, %{last_fetch_unread_time: record_last_fetch_time})\n end\n\n Record |> ORM.upsert_by([user_id: user_id], attrs)\n end\n\n defp get_last_fetch_time(Mention, read, user) do\n timekey = get_record_lasttime_key(read)\n do_get_last_fetch_time(:mentions_record, user, timekey)\n end\n\n defp get_last_fetch_time(Notification, read, user) do\n timekey = get_record_lasttime_key(read)\n do_get_last_fetch_time(:notifications_record, user, timekey)\n end\n\n defp get_last_fetch_time(SysNotification, user) do\n timekey = get_record_lasttime_key(:sys_notifications_record)\n do_get_last_fetch_time(:sys_notifications_record, user, timekey)\n end\n\n defp get_record_lasttime_key(:sys_notifications_record) do\n \"last_fetch_time\"\n end\n\n defp get_record_lasttime_key(read) do\n case read do\n true -> \"last_fetch_read_time\"\n false -> \"last_fetch_unread_time\"\n end\n end\n\n defp do_get_last_fetch_time(record_key, %User{id: user_id}, timekey) do\n long_long_ago = Timex.shift(Timex.now(), years: -10)\n\n with {:ok, record} <- Record |> ORM.find_by(user_id: user_id) do\n record\n |> has_valid_value(record_key)\n |> case do\n false ->\n {:ok, long_long_ago}\n\n true ->\n record\n |> Map.get(record_key)\n |> Map.get(timekey, to_string(long_long_ago))\n |> NaiveDateTime.from_iso8601()\n end\n else\n {:error, _} ->\n {:ok, long_long_ago}\n end\n end\n\n defp delete_items(_queryable, {:ok, %{entries: []}}), do: {:ok, \"\"}\n\n defp delete_items(queryable, {:ok, %{entries: entries}}) do\n # delete_all only support queryable and where syntax\n # TODO: move logic to queue job\n\n first_id = entries |> List.first() |> Map.get(:id)\n last_id = entries |> List.last() |> Map.get(:id)\n\n min_id = Enum.min([first_id, last_id])\n max_id = Enum.max([first_id, last_id])\n\n queryable\n |> where([m], m.id >= ^min_id and m.id <= ^max_id)\n |> Repo.delete_all()\n end\n\n defp do_mark_read_all(queryable, %User{} = user) do\n query =\n queryable\n |> where([m], m.to_user_id == ^user.id)\n\n try do\n Repo.update_all(\n query,\n set: [read: true]\n )\n\n {:ok, %{status: true}}\n rescue\n _ -> {:error, %{status: false}}\n end\n end\n\n defp has_valid_value(map, key) when is_map(map) do\n Map.has_key?(map, key) and not is_nil(Map.get(map, key))\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,67,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,59,null,null],"name":"lib/groupher_server/cms/community_editor.ex","source":"defmodule GroupherServer.CMS.CommunityEditor do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias Helper.Certification\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Community\n\n @required_fields ~w(user_id community_id title)a\n\n @type t :: %CommunityEditor{}\n\n schema \"communities_editors\" do\n field(:title, :string)\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:community, Community, foreign_key: :community_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%CommunityEditor{} = community_editor, attrs) do\n community_editor\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> validate_inclusion(:title, Certification.editor_titles(:cms))\n |> foreign_key_constraint(:community_id)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user_id, name: :communities_editors_user_id_community_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,2,null,1,1,null,null,null,null,null,null,null,7,3,3,null,null,null,null,3,null,null,null,null,null,null,10,null,null,null,6,3,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/accounts/delegates/customization.ex","source":"defmodule GroupherServer.Accounts.Delegate.Customization do\n import Ecto.Query, warn: false\n\n alias GroupherServer.Accounts\n alias GroupherServer.Accounts.{User, Customization}\n alias Helper.ORM\n # ...\n # TODO: Constants\n\n @doc \"\"\"\n add custom setting to user\n \"\"\"\n # for map_size\n # see https://stackoverflow.com/questions/33248816/pattern-match-function-against-empty-map\n def add_custom_setting(%User{} = _user, map) when map_size(map) == 0 do\n {:error, \"AccountCustomization: invalid option or not purchased\"}\n end\n\n def add_custom_setting(%User{} = user, map) when is_map(map) do\n valid? = map |> Map.keys() |> Enum.all?(&can_set?(user, &1, :boolean))\n\n case valid? do\n true ->\n attrs = Map.merge(%{user_id: user.id}, map)\n Customization |> ORM.upsert_by([user_id: user.id], attrs)\n\n false ->\n {:error, \"AccountCustomization: invalid option or not purchased\"}\n end\n end\n\n def add_custom_setting(%User{} = user, key, value \\\\ true) do\n with {:ok, key} <- can_set?(user, key) do\n attrs = Map.put(%{user_id: user.id}, key, value)\n Customization |> ORM.upsert_by([user_id: user.id], attrs)\n end\n end\n\n defp can_set?(%User{} = user, key, :boolean) do\n case can_set?(%User{} = user, key) do\n {:ok, _} -> true\n {:error, _} -> false\n end\n end\n\n def can_set?(%User{} = user, key) do\n cond do\n key in valid_custom_items(:free) ->\n {:ok, key}\n\n key in valid_custom_items(:advance) ->\n Accounts.has_purchased?(user, key)\n\n true ->\n {:error, \"AccountCustomization: invalid option\"}\n end\n end\n\n @doc \"\"\"\n # theme -- user can set a default theme\n # sidebar_layout -- user can arrange subscribed community index\n \"\"\"\n def valid_custom_items(:free) do\n [:theme, :sidebar_layout]\n end\n\n @doc \"\"\"\n # :brainwash_free -- ads free\n # ::community_chart -- user can access comunity charts\n \"\"\"\n def valid_custom_items(:advance) do\n # NOTE: :brainwash_free aka. \"ads_free\"\n # use brainwash to avoid brower-block-plugins\n [:brainwash_free, :community_chart]\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,7,null,0,null,null,4,null,4,4,null,null,0,0,null,null,4,null,null],"name":"lib/groupher_server_web/middleware/statistics/make_contribute.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.Statistics.MakeContribute do\n @behaviour Absinthe.Middleware\n # google: must appear in the GROUP BY clause or be used in an aggregate function\n alias GroupherServer.Statistics\n alias GroupherServer.CMS.Community\n alias GroupherServer.Accounts.User\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(%{value: nil, errors: _} = resolution, _), do: resolution\n\n def call(%{value: value, context: %{cur_user: cur_user}} = resolution, for: threads) do\n case is_list(threads) do\n true ->\n if :user in threads, do: Statistics.make_contribute(%User{id: cur_user.id})\n if :community in threads, do: Statistics.make_contribute(%Community{id: value.id})\n\n false ->\n if :user == threads, do: Statistics.make_contribute(%User{id: cur_user.id})\n if :community == threads, do: Statistics.make_contribute(%Community{id: value.id})\n end\n\n resolution\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,172,312,null,312,2,null,310,null,null,null,null,null,null,0,null,null,null,279,null,null,null,null,null,null,null,null,139,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,0,null,0,null,null,null,0,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/helper/nested_filter.ex","source":"defmodule Helper.NestedFilter do\n @moduledoc \"\"\"\n Documentation for NestedFilter.\n see: https://github.com/treble37/nested_filter\n \"\"\"\n @type key :: any\n @type val :: any\n @type keys_to_select :: list\n @type predicate :: (key, val -> boolean)\n\n # @spec drop_by(struct, predicate) :: struct\n def drop_by(%_{} = struct, _), do: struct\n\n # @spec drop_by(map, predicate) :: map\n def drop_by(map, predicate) when is_map(map) do\n map\n |> Enum.reduce(%{}, fn {key, val}, acc ->\n cleaned_val = drop_by(val, predicate)\n\n if predicate.(key, cleaned_val) do\n acc\n else\n Map.put(acc, key, cleaned_val)\n end\n end)\n end\n\n # @spec drop_by(list, predicate) :: list\n def drop_by(list, predicate) when is_list(list) do\n Enum.map(list, &drop_by(&1, predicate))\n end\n\n def drop_by(elem, _) do\n elem\n end\n\n @doc \"\"\"\n Take a (nested) map and filter out any keys with specified values in the\n values_to_reject list.\n \"\"\"\n # @spec drop_by_value(%{any => any}, [any]) :: %{any => any}\n def drop_by_value(map, values_to_reject) when is_map(map) do\n drop_by(map, fn _, val -> val in values_to_reject end)\n end\n\n @doc \"\"\"\n Take a (nested) map and filter out any values with specified keys in the\n keys_to_reject list.\n \"\"\"\n # @spec drop_by_key(%{any => any}, [any]) :: %{any => any}\n def drop_by_key(map, keys_to_reject) when is_map(map) do\n drop_by(map, fn key, _ -> key in keys_to_reject end)\n end\n\n # @spec take_by(map, keys_to_select) :: map\n def take_by(map, keys_to_select) when is_map(map) do\n map\n |> Enum.reduce(%{}, fn {_key, val}, acc ->\n Map.merge(acc, take_by(val, keys_to_select))\n end)\n |> Map.merge(Map.take(map, keys_to_select))\n end\n\n def take_by(_elem, _) do\n %{}\n end\n\n @doc \"\"\"\n Take a (nested) map and keep any values with specified keys in the\n keys_to_select list.\n \"\"\"\n # @spec take_by_key(%{any => any}, [any]) :: %{any => any}\n def take_by_key(map, keys_to_select) when is_map(map) do\n Map.merge(take_by(map, keys_to_select), Map.take(map, keys_to_select))\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,1,1,null,1,null,null,null,1,null,null],"name":"lib/groupher_server_web/resolvers/delivery_resolver.ex","source":"defmodule GroupherServerWeb.Resolvers.Delivery do\n @moduledoc false\n\n alias GroupherServer.Delivery\n alias GroupherServer.Accounts.User\n # alias Helper.ORM\n\n def mention_someone(_root, args, %{context: %{cur_user: cur_user}}) do\n from_user_id = cur_user.id\n to_user_id = args.user_id\n\n Delivery.mention_someone(%User{id: from_user_id}, %User{id: to_user_id}, args)\n end\n\n def publish_system_notification(_root, args, %{context: %{cur_user: _}}) do\n Delivery.publish_system_notification(args)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,96,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,47,null,null],"name":"lib/groupher_server/statistics/publish_throttle.ex","source":"defmodule GroupherServer.Statistics.PublishThrottle do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n @optional_fields ~w(user_id publish_hour publish_date hour_count date_count last_publish_time)a\n @required_fields ~w(user_id)a\n\n @type t :: %PublishThrottle{}\n schema \"publish_throttles\" do\n field(:publish_hour, :utc_datetime)\n field(:publish_date, :date)\n field(:hour_count, :integer)\n field(:date_count, :integer)\n belongs_to(:user, Accounts.User)\n\n field(:last_publish_time, :utc_datetime)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PublishThrottle{} = publish_throttle, attrs) do\n publish_throttle\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user, name: :publish_throttles_user_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,45,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/mention_mail.ex","source":"defmodule GroupherServer.Accounts.MentionMail do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id source_id source_type source_preview)a\n @optional_fields ~w(parent_id parent_type read)a\n\n @type t :: %MentionMail{}\n schema \"mention_mails\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:parent_id, :string)\n field(:parent_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%MentionMail{} = mention, attrs) do\n mention\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,139,139,139,null,null,139,null,null,139,139,null,null,139,null,null,null,139,null,null,null,null,null,null,null,null,139,null,139,null,139,null,null,null,null,null,null,null,null,5,5,5,null,null,5,null,null,5,5,null,null,5,null,null,null,5,null,null,null,null,null,null,null,null,5,5,null,5,null,5,null,null,null,null,5,null,null,null,null,null,3,null,null,null,2,null,null,null,0,null,null,null,0,null,null],"name":"lib/groupher_server/cms/delegates/article_reaction.ex","source":"defmodule GroupherServer.CMS.Delegate.ArticleReaction do\n @moduledoc \"\"\"\n reaction[favorite, star, watch ...] on article [post, job, video...]\n \"\"\"\n import Helper.Utils, only: [done: 1, done: 2]\n import GroupherServer.CMS.Helper.MatcherOld\n import Ecto.Query, warn: false\n import Helper.ErrorCode\n\n alias Helper.ORM\n alias GroupherServer.{Accounts, Repo}\n\n alias Accounts.User\n alias Ecto.Multi\n\n @doc \"\"\"\n favorite / star / watch CMS contents like post / tuts / video ...\n \"\"\"\n # when valid_reaction(thread, react) do\n def reaction(thread, react, content_id, %User{id: user_id}) do\n with {:ok, action} <- match_action(thread, react),\n {:ok, content} <- ORM.find(action.target, content_id, preload: [author: :user]),\n {:ok, user} <- ORM.find(Accounts.User, user_id) do\n Multi.new()\n |> Multi.run(:create_reaction_record, fn _ ->\n create_reaction_record(action, user, thread, content)\n end)\n |> Multi.run(:add_achievement, fn _ ->\n achiever_id = content.author.user_id\n Accounts.achieve(%User{id: achiever_id}, :add, react)\n end)\n |> Repo.transaction()\n |> reaction_result()\n end\n end\n\n defp reaction_result({:ok, %{create_reaction_record: result}}), do: result |> done()\n\n defp reaction_result({:error, :create_reaction_record, _result, _steps}),\n do: {:error, [message: \"create reaction fails\", code: ecode(:react_fails)]}\n\n defp reaction_result({:error, :add_achievement, _result, _steps}),\n do: {:error, [message: \"achieve fails\", code: ecode(:react_fails)]}\n\n defp create_reaction_record(action, %User{id: user_id}, thread, content) do\n attrs = %{} |> Map.put(\"user_id\", user_id) |> Map.put(\"#{thread}_id\", content.id)\n\n action.reactor\n |> ORM.create(attrs)\n |> done(with: content)\n end\n\n # ------\n @doc \"\"\"\n unfavorite / unstar / unwatch CMS contents like post / tuts / video ...\n \"\"\"\n # when valid_reaction(thread, react) do\n def undo_reaction(thread, react, content_id, %User{id: user_id}) do\n with {:ok, action} <- match_action(thread, react),\n {:ok, content} <- ORM.find(action.target, content_id, preload: [author: :user]),\n {:ok, user} <- ORM.find(Accounts.User, user_id) do\n Multi.new()\n |> Multi.run(:delete_reaction_record, fn _ ->\n delete_reaction_record(action, user, thread, content)\n end)\n |> Multi.run(:minus_achievement, fn _ ->\n achiever_id = content.author.user_id\n Accounts.achieve(%User{id: achiever_id}, :minus, react)\n end)\n |> Repo.transaction()\n |> undo_reaction_result()\n end\n end\n\n defp undo_reaction_result({:ok, %{delete_reaction_record: result}}), do: result |> done()\n\n defp undo_reaction_result({:error, :delete_reaction_record, _result, _steps}),\n do: {:error, [message: \"delete reaction fails\", code: ecode(:react_fails)]}\n\n defp undo_reaction_result({:error, :minus_achievement, _result, _steps}),\n do: {:error, [message: \"achieve fails\", code: ecode(:react_fails)]}\n\n defp delete_reaction_record(action, %User{id: user_id}, thread, content) do\n user_where = dynamic([u], u.user_id == ^user_id)\n reaction_where = dynamic_reaction_where(thread, content.id, user_where)\n\n query = from(f in action.reactor, where: ^reaction_where)\n\n case Repo.one(query) do\n nil ->\n {:error, \"record not found\"}\n\n record ->\n Repo.delete(record)\n {:ok, content}\n end\n end\n\n defp dynamic_reaction_where(:post, id, user_where) do\n dynamic([p], p.post_id == ^id and ^user_where)\n end\n\n defp dynamic_reaction_where(:job, id, user_where) do\n dynamic([p], p.job_id == ^id and ^user_where)\n end\n\n defp dynamic_reaction_where(:video, id, user_where) do\n dynamic([p], p.video_id == ^id and ^user_where)\n end\n\n defp dynamic_reaction_where(:repo, id, user_where) do\n dynamic([p], p.repo_id == ^id and ^user_where)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,11,null,null,null,null,null,27,null,27,27,null,null,null,0,null,null,null,null,null,null,22,22,22,22,null,null,null,20,null,null,null,2,null,null,null,null,null,0,null,null,null,20,20,null,null,null,null,20,null,null,20,null,20,20,null,null,null,22,null,22,null,7,null,null,15,null,null,null,null,20,null,null,5,null,null,15,null,null,20,null,7,7,null,null,13,null,null,null,null,null,7,null,null,null,15,null,null,null,44,0,44,null,null,null,14,30,null,null,35,null,null],"name":"lib/groupher_server_web/middleware/passport_loader.ex","source":"defmodule GroupherServerWeb.Middleware.PassportLoader do\n @behaviour Absinthe.Middleware\n import GroupherServer.CMS.Helper.MatcherOld\n import Helper.Utils\n import Helper.ErrorCode\n\n import ShortMaps\n\n alias GroupherServer.CMS\n alias Helper.ORM\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(\n %{context: %{cur_user: _}, arguments: ~m(community_id)a} = resolution,\n source: :community\n ) do\n case ORM.find(CMS.Community, community_id) do\n {:ok, community} ->\n arguments = resolution.arguments |> Map.merge(%{passport_communities: [community]})\n %{resolution | arguments: arguments}\n\n {:error, err_msg} ->\n resolution\n |> handle_absinthe_error(err_msg, ecode(:passport))\n end\n end\n\n # def call(%{context: %{cur_user: cur_user}, arguments: %{id: id}} = resolution, [source: .., base: ..]) do\n # Loader 应该使用 Map 作为参数,以方便模式匹配\n def call(%{context: %{cur_user: _}, arguments: %{id: id}} = resolution, args) do\n with {:ok, thread, react} <- parse_source(args, resolution),\n {:ok, action} <- match_action(thread, react),\n {:ok, preload} <- parse_preload(action, args),\n {:ok, content} <- ORM.find(action.reactor, id, preload: preload) do\n resolution\n |> load_owner_info(react, content)\n |> load_source(content)\n |> load_community_info(content, args)\n else\n {:error, err_msg} ->\n resolution\n |> handle_absinthe_error(err_msg, ecode(:passport))\n end\n end\n\n def call(resolution, _) do\n # TODO communiy in args\n resolution\n end\n\n def load_source(resolution, content) do\n arguments = resolution.arguments |> Map.merge(%{passport_source: content})\n %{resolution | arguments: arguments}\n end\n\n # 取得 content 里面的 conmunities 字段\n def load_community_info(resolution, content, args) do\n communities = content |> Map.get(parse_base(args))\n\n # check if communities is a List\n communities = if is_list(communities), do: communities, else: [communities]\n\n arguments = resolution.arguments |> Map.merge(%{passport_communities: communities})\n %{resolution | arguments: arguments}\n end\n\n defp parse_preload(action, args) do\n {:ok, _, react} = parse_source(args)\n\n case react == :comment do\n true ->\n {:ok, action.preload}\n\n false ->\n {:ok, [action.preload, parse_base(args)]}\n end\n end\n\n def load_owner_info(%{context: %{cur_user: cur_user}} = resolution, react, content) do\n content_author_id =\n cond do\n react == :comment ->\n content.author.id\n\n true ->\n content.author.user_id\n end\n\n case content_author_id == cur_user.id do\n true ->\n arguments = resolution.arguments |> Map.merge(%{passport_is_owner: true})\n %{resolution | arguments: arguments}\n\n _ ->\n resolution\n end\n end\n\n # typical usage is delete_comment, should load conent by thread\n defp parse_source([source: [:arg_thread, react]], %{arguments: %{thread: thread}}) do\n parse_source(source: [thread, react])\n end\n\n defp parse_source(args, _resolution) do\n parse_source(args)\n end\n\n defp parse_source(args) do\n case Keyword.has_key?(args, :source) do\n false -> {:error, \"Invalid.option: #{args}\"}\n true -> args |> Keyword.get(:source) |> match_source\n end\n end\n\n defp match_source([thread, react]), do: {:ok, thread, react}\n defp match_source(thread), do: {:ok, thread, :self}\n\n defp parse_base(args) do\n Keyword.get(args, :base) || :communities\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,1,null,null,null,null,null,null,null,null,null,null,1,1,null,null,null,null,null,0,null,null,null],"name":"lib/groupher_server/application.ex","source":"defmodule GroupherServer.Application do\n use Application\n\n # See https://hexdocs.pm/elixir/Application.html\n # for more information on OTP Applications\n def start(_type, _args) do\n import Supervisor.Spec\n\n # Define workers and child supervisors to be supervised\n children = [\n # Start the Ecto repository\n supervisor(GroupherServer.Repo, []),\n # Start the endpoint when the application starts\n supervisor(GroupherServerWeb.Endpoint, [])\n # Start your own worker by calling: GroupherServer.Worker.start_link(arg1, arg2, arg3)\n # worker(GroupherServer.Worker, [arg1, arg2, arg3]),\n ]\n\n # See https://hexdocs.pm/elixir/Supervisor.html\n # for other strategies and supported options\n opts = [strategy: :one_for_one, name: GroupherServer.Supervisor]\n Supervisor.start_link(children, opts)\n end\n\n # Tell Phoenix to update the endpoint configuration\n # whenever the application is updated.\n def config_change(changed, _new, removed) do\n GroupherServerWeb.Endpoint.config_change(changed, removed)\n :ok\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,119,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,47,null,null],"name":"lib/groupher_server/cms/job_comment.ex","source":"defmodule GroupherServer.CMS.JobComment do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n alias GroupherServer.CMS.{Job, JobCommentReply}\n\n @required_fields ~w(body author_id job_id floor)a\n @optional_fields ~w(reply_id)a\n\n @type t :: %JobComment{}\n schema \"jobs_comments\" do\n field(:body, :string)\n field(:floor, :integer)\n belongs_to(:author, Accounts.User, foreign_key: :author_id)\n belongs_to(:job, Job, foreign_key: :job_id)\n belongs_to(:reply_to, JobComment, foreign_key: :reply_id)\n # belongs_to(:reply_to, JobComment, foreign_key: :job_id)\n has_many(:replies, {\"jobs_comments_replies\", JobCommentReply})\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%JobComment{} = job_comment, attrs) do\n job_comment\n |> cast(attrs, @required_fields ++ @optional_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:job_id)\n |> foreign_key_constraint(:author_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,15,null,null,null,null,null,null,null,null,null,null,null,null,7,null,null],"name":"lib/groupher_server/cms/post_comment_reply.ex","source":"defmodule GroupherServer.CMS.PostCommentReply do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.PostComment\n\n @required_fields ~w(post_comment_id reply_id)a\n\n @type t :: %PostCommentReply{}\n schema \"posts_comments_replies\" do\n belongs_to(:post_comment, PostComment, foreign_key: :post_comment_id)\n belongs_to(:reply, PostComment, foreign_key: :reply_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostCommentReply{} = post_comment_reply, attrs) do\n post_comment_reply\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:post_comment_id)\n |> foreign_key_constraint(:reply_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/community.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Community do\n @moduledoc \"\"\"\n CMS mations for community\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_mutation_community do\n @desc \"create a global community\"\n field :create_community, :community do\n arg(:title, non_null(:string))\n arg(:desc, non_null(:string))\n arg(:raw, non_null(:string))\n arg(:logo, non_null(:string))\n # arg(:category, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->community.create\")\n\n resolve(&R.CMS.create_community/3)\n # middleware(M.Statistics.MakeContribute, for: :user)\n middleware(M.Statistics.MakeContribute, for: [:user, :community])\n end\n\n @desc \"update a community\"\n field :update_community, :community do\n arg(:id, non_null(:id))\n arg(:title, :string)\n arg(:desc, :string)\n arg(:raw, :string)\n arg(:logo, :string)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->community.update\")\n\n resolve(&R.CMS.update_community/3)\n middleware(M.Statistics.MakeContribute, for: [:user, :community])\n end\n\n @desc \"delete a global community\"\n field :delete_community, :community do\n arg(:id, non_null(:id))\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->community.delete\")\n\n resolve(&R.CMS.delete_community/3)\n end\n\n @desc \"create category\"\n field :create_category, :category do\n arg(:title, non_null(:string))\n arg(:raw, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.create\")\n\n resolve(&R.CMS.create_category/3)\n end\n\n @desc \"delete category\"\n field :delete_category, :category do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.delete\")\n\n resolve(&R.CMS.delete_category/3)\n end\n\n @desc \"update category\"\n field :update_category, :category do\n arg(:id, non_null(:id))\n arg(:title, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.update\")\n\n resolve(&R.CMS.update_category/3)\n end\n\n @desc \"create independent thread\"\n field :create_thread, :thread do\n arg(:title, non_null(:string))\n arg(:raw, non_null(:thread))\n arg(:index, :integer, default_value: 0)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->thread.create\")\n\n resolve(&R.CMS.create_thread/3)\n end\n\n @desc \"add a editor for a community\"\n field :set_editor, :user do\n arg(:community_id, non_null(:id))\n arg(:user_id, non_null(:id))\n arg(:title, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->editor.set\")\n\n resolve(&R.CMS.set_editor/3)\n end\n\n @desc \"unset a editor from a community, the user's passport also deleted\"\n field :unset_editor, :user do\n arg(:community_id, non_null(:id))\n arg(:user_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->editor.unset\")\n\n resolve(&R.CMS.unset_editor/3)\n end\n\n # TODO: remove, should remove both editor and cms->passport\n @desc \"update cms editor's title, passport is not effected\"\n field :update_cms_editor, :user do\n arg(:community_id, non_null(:id))\n arg(:user_id, non_null(:id))\n arg(:title, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->editor.update\")\n\n resolve(&R.CMS.update_editor/3)\n end\n\n @desc \"create a tag\"\n field :create_tag, :article_tag do\n arg(:title, non_null(:string))\n arg(:color, non_null(:rainbow_color_enum))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.create\")\n\n resolve(&R.CMS.create_tag/3)\n end\n\n @desc \"update a tag\"\n field :update_tag, :article_tag do\n arg(:id, non_null(:id))\n arg(:title, non_null(:string))\n # arg(:color, non_null(:rainbow_color_enum))\n arg(:color, non_null(:rainbow_color_enum))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.update\")\n\n resolve(&R.CMS.update_tag/3)\n end\n\n @desc \"delete a tag by thread\"\n field :delete_tag, :article_tag do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.delete\")\n\n resolve(&R.CMS.delete_tag/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,6,null,6,null,null,2,null,null,0,null,null],"name":"lib/groupher_server_web/middleware/put_current_user.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.PutCurrentUser do\n @behaviour Absinthe.Middleware\n\n def call(%{context: %{cur_user: cur_user}} = resolution, _) do\n arguments = resolution.arguments |> Map.merge(%{cur_user: cur_user})\n\n %{resolution | arguments: arguments}\n end\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(resolution, _) do\n resolution\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,7,null,7,1,null,null,6,null,null,null,null,12,null,12,2,null,null,10,null,null,null,null,null,null,null,4,null,null,4,null,4,null,null,4,null,null,null,null,null,1,null,null,null,null,null,null,0,null,null,null,1,null,null,1,null,1,null,1,null,null,null,4,4,4,null,4,null,4,null,null,null,null,5,4,null,null,null,null,null,null,null,null,null,null,null,null,0,null,0,null,null,0,0,0,null,0,null,0,0,null,0,null,null,0,null,null,null,null,4,4,null,null,null,null,1,1,null,null,null,null,2,2,null,null,null,3,null,null,null,null,null,null,3,null,null],"name":"lib/groupher_server/statistics/delegates/contribute.ex","source":"defmodule GroupherServer.Statistics.Delegate.Contribute do\n import Ecto.Query, warn: false\n import Helper.Utils\n import ShortMaps\n\n alias GroupherServer.Repo\n alias GroupherServer.Accounts.User\n alias GroupherServer.CMS.Community\n alias GroupherServer.Statistics.{UserContribute, CommunityContribute}\n alias Helper.{ORM, QueryBuilder}\n\n @community_contribute_days get_config(:general, :community_contribute_days)\n @user_contribute_months get_config(:general, :user_contribute_months)\n\n def make_contribute(%Community{id: id}) do\n today = Timex.today() |> Date.to_iso8601()\n\n with {:ok, contribute} <- ORM.find_by(CommunityContribute, community_id: id, date: today) do\n contribute |> inc_contribute_count(:community) |> done\n else\n {:error, _} ->\n CommunityContribute |> ORM.create(%{community_id: id, date: today, count: 1})\n end\n end\n\n def make_contribute(%User{id: id}) do\n today = Timex.today() |> Date.to_iso8601()\n\n with {:ok, contribute} <- ORM.find_by(UserContribute, user_id: id, date: today) do\n contribute |> inc_contribute_count(:user) |> done\n else\n {:error, _} ->\n UserContribute |> ORM.create(%{user_id: id, date: today, count: 1})\n end\n end\n\n @doc \"\"\"\n Returns the list of user_contribute by latest 6 months.\n \"\"\"\n def list_contributes(%User{id: id}) do\n user_id = tobe_integer(id)\n\n \"user_contributes\"\n |> where([c], c.user_id == ^user_id)\n |> QueryBuilder.recent_inserted(months: @user_contribute_months)\n |> select([c], %{date: c.date, count: c.count})\n |> Repo.all()\n |> to_contrubutes_map()\n |> done\n end\n\n def list_contributes(%Community{id: id}) do\n %Community{id: id}\n |> get_contributes()\n |> done\n end\n\n def list_contributes_digest(%Community{id: id}) do\n %Community{id: id}\n |> get_contributes()\n |> to_counts_digest(days: @community_contribute_days)\n |> done\n end\n\n defp get_contributes(%Community{id: id}) do\n community_id = tobe_integer(id)\n\n \"community_contributes\"\n |> where([c], c.community_id == ^community_id)\n |> QueryBuilder.recent_inserted(days: @community_contribute_days)\n |> select([c], %{date: c.date, count: c.count})\n |> Repo.all()\n |> to_contribute_records()\n end\n\n defp to_contrubutes_map(data) do\n end_date = Timex.today()\n start_date = Timex.shift(Timex.today(), months: -6)\n total_count = Enum.reduce(data, 0, &(&1.count + &2))\n\n records = to_contribute_records(data)\n\n ~m(start_date end_date total_count records)a\n end\n\n defp to_contribute_records(data) do\n data\n |> Enum.map(fn %{count: count, date: date} ->\n %{\n date: convert_date(date),\n count: count\n }\n end)\n end\n\n # 返回 count 数组,方便前端绘图\n # example:\n # from: [0,0,0,0,0,0]\n # to: [0,30,3,8,0,0]\n # 如果 7 天都有 count, 不用计算直接 map 返回\n defp to_counts_digest(record, days: count) do\n case length(record) == @community_contribute_days + 1 do\n true ->\n Enum.map(record, & &1.count)\n\n false ->\n today = Timex.today() |> Date.to_erl()\n return_count = abs(count) + 1\n enmpty_tuple = return_count |> repeat(0) |> List.to_tuple()\n\n results =\n Enum.reduce(record, enmpty_tuple, fn record, acc ->\n diff = Timex.diff(Timex.to_date(record.date), today, :days)\n index = diff + abs(count)\n\n put_elem(acc, index, record.count)\n end)\n\n results |> Tuple.to_list()\n end\n end\n\n defp convert_date(date) do\n {:ok, edate} = Date.from_erl(date)\n edate\n end\n\n defp inc_contribute_count(contribute, :community) do\n CommunityContribute\n |> where([c], c.community_id == ^contribute.community_id and c.date == ^contribute.date)\n |> do_inc_count(contribute)\n end\n\n defp inc_contribute_count(contribute, :user) do\n UserContribute\n |> where([c], c.user_id == ^contribute.user_id and c.date == ^contribute.date)\n |> do_inc_count(contribute)\n end\n\n defp do_inc_count(query, contribute, count \\\\ 1) do\n {1, [result]} =\n Repo.update_all(\n query,\n [inc: [count: count]],\n returning: [:count]\n )\n\n put_in(contribute.count, result.count)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,361,null,null,null,null,2,null,null,null,null,null,null,2,null,null,null,null,null,1,null,null,null,null,null,1,null,null,null,2,null,null,null,1,null,null,null,1,null,null,5,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/utils/loader.ex","source":"defmodule GroupherServer.Accounts.Helper.Loader do\n @moduledoc \"\"\"\n dataloader for accounts\n \"\"\"\n import Ecto.Query, warn: false\n\n alias Helper.QueryBuilder\n alias GroupherServer.{Accounts, CMS, Repo}\n\n alias Accounts.{UserFollower, UserFollowing}\n\n def data, do: Dataloader.Ecto.new(Repo, query: &query/2)\n\n def query({\"communities_subscribers\", CMS.CommunitySubscriber}, %{count: _}) do\n CMS.CommunitySubscriber\n |> group_by([f], f.user_id)\n |> select([f], count(f.id))\n end\n\n def query({\"communities_subscribers\", CMS.CommunitySubscriber}, %{filter: filter}) do\n CMS.CommunitySubscriber\n |> QueryBuilder.filter_pack(filter)\n |> join(:inner, [u], c in assoc(u, :community))\n |> select([u, c], c)\n end\n\n def query({\"users_followers\", UserFollower}, %{count: _}) do\n UserFollower\n |> group_by([f], f.user_id)\n |> select([f], count(f.id))\n end\n\n def query({\"users_followings\", UserFollowing}, %{count: _}) do\n UserFollowing\n |> group_by([f], f.user_id)\n |> select([f], count(f.id))\n end\n\n def query({\"users_followers\", UserFollower}, %{viewer_did: _, cur_user: cur_user}) do\n UserFollower |> where([f], f.follower_id == ^cur_user.id)\n end\n\n def query({\"posts_favorites\", CMS.PostFavorite}, %{count: _}) do\n CMS.PostFavorite |> count_cotents\n end\n\n def query({\"jobs_favorites\", CMS.JobFavorite}, %{count: _}) do\n CMS.JobFavorite |> count_cotents\n end\n\n def query(queryable, _args), do: queryable\n\n defp count_cotents(queryable) do\n queryable\n |> group_by([f], f.user_id)\n |> select([f], count(f.id))\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,5,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/cms/job_comment_reply.ex","source":"defmodule GroupherServer.CMS.JobCommentReply do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.JobComment\n\n @required_fields ~w(job_comment_id reply_id)a\n\n @type t :: %JobCommentReply{}\n schema \"jobs_comments_replies\" do\n belongs_to(:job_comment, JobComment, foreign_key: :job_comment_id)\n belongs_to(:reply, JobComment, foreign_key: :reply_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%JobCommentReply{} = job_comment_reply, attrs) do\n job_comment_reply\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:job_comment_id)\n |> foreign_key_constraint(:reply_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,60,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1,null,1,1,null,1,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/helper/certification.ex","source":"defmodule Helper.Certification do\n @moduledoc \"\"\"\n valid editors and passport details\n \"\"\"\n def editor_titles(:cms) do\n [\"chief editor\", \"post editor\"]\n end\n\n def passport_rules(cms: \"chief editor\") do\n %{\n \"post.tag.create\" => true,\n \"post.tag.edit\" => true,\n \"post.article.markDelete\" => true\n }\n end\n\n # a |> Enum.map(fn(x) -> {x, false} end) |> Map.new\n # %{\n # cms: %{\n # system: ..,\n # community: ...,\n # },\n # statistics: %{\n # ....\n # },\n # otherMoudle: %{\n\n # }\n # }\n\n @doc \"\"\"\n 基础权限,社区权限\n \"\"\"\n def all_rules(:cms) do\n %{\n general: [\n \"system_notification.publish\",\n \"stamp_passport\",\n # community\n \"editor.set\",\n \"editor.unset\",\n \"editor.update\",\n \"community.create\",\n \"community.update\",\n \"community.delete\",\n \"category.create\",\n \"category.delete\",\n \"category.update\",\n \"category.set\",\n \"category.unset\",\n \"thread.create\",\n \"post.community.mirror\",\n \"post.community.unmirror\",\n \"job.community.mirror\",\n \"job.community.unmirror\",\n \"post.pin\",\n \"post.undo_pin\",\n \"post.markDelete\",\n \"post.undo_trash\"\n ],\n community: [\n # thread\n \"thread.set\",\n \"thread.unset\",\n \"post.edit\",\n \"post.markDelete\",\n \"post.delete\",\n \"job.edit\",\n \"job.markDelete\",\n \"job.delete\",\n # post tag\n \"post.tag.create\",\n \"post.tag.update\",\n \"post.tag.delete\",\n \"post.tag.set\",\n \"post.tag.unset\",\n # job tag\n \"job.tag.create\",\n \"job.tag.update\",\n \"job.tag.delete\",\n \"job.tag.set\",\n \"job.tag.unset\"\n ]\n }\n end\n\n def all_rules(:cms, :stringify) do\n rules = all_rules(:cms)\n\n %{\n general: rules.general |> Enum.map(fn x -> {x, false} end) |> Map.new() |> Jason.encode!(),\n community:\n rules.community |> Enum.map(fn x -> {x, false} end) |> Map.new() |> Jason.encode!()\n }\n end\nend\n\n# 可以编辑某个社区 post 版块的文章, 支持 owner\n# middleware(M.Passport, claim: \"cms->c?->posts.article.edit\")\n# middleware(M.Passport, claim: \"owner;cms->c?->posts.article.edit\")\n\n# 可以添加某个社区 posts 版块的 tag 标签, 同时可支持 owner\n# middleware(M.Passport, claim: \"cms->c?->posts.tag.add\")\n# middleware(M.Passport, claim: \"cms->c?->posts.tag.edit\")\n# middleware(M.Passport, claim: \"cms->c?->posts.tag.delete\")\n# middleware(M.Passport, claim: \"cms->c?->posts.tag.markDelete\")\n# middleware(M.Passport, claim: \"owner;cms->c?->posts.tag.delete\")\n\n# 可以给某个社区 posts 版块的 posts 设置标签(setTag), 同时可支持 owner?\n# middleware(M.Passport, claim: \"c?->posts.tag.set\")\n\n# 可以某个社区的 posts 版块置顶\n# middleware(M.Passport, claim: \"cms->c?->posts.setTop\")\n\n# 可以编辑某个社区所有版块的文章\n# middleware(M.Passport, claim: \"cms->c?->posts.articles.edit\")\n# middleware(M.Passport, claim: \"cms->c?->job.articles.edit\")\n# ....全部显示声明....\n# middleware(M.Passport, claim: \"cms->c?->radar.articles.edit\")\n\n# 可以给某个社区的某个版块添加/删除管理员, 实际上就是在给其他成员分配上面的权限,同时该用户会被添加到相应的管理员中\n# middleware(M.Passport, claim: \"cms->c?->posts.managers.add\")\n# middleware(M.Passport, claim: \"cms->c?->jobs.managers.add\")\n# middleware(M.Passport, claim: \"cms->c?->videos.managers.add\")\n# middleware(M.Passport, claim: \"cms->c?->videos.managers.delete\")\n\n# 可以给社区的版块设置审核后发布\n# middleware(M.Passport, claim: \"cms->c?->settings.posts.needReview\")\n# middleware(M.Passport, claim: \"cms->c?->posts.reviewer\") # 审核员 (一开始没必要加)\n\n# 在某个社区的某个版块屏蔽某个用户\n# middleware(M.Passport, claim: \"cms->c?->viewer->block\")\n\n# 查看某个社区的总访问量\n# middleware(M.Passport, claim: \"statistics->c?->click\")\n# middleware(M.Passport, claim: \"logs->c?->posts ...\")\n\n# defguard the_fuck(value) when String.contains?(value, \"->?\")\n# classify the require of this gateway"},{"coverage":[null,null,null,null,null,null,null,null,null,null,2,null,null,30,30,30,null,null,null,null,28,28,28,null,null,null,null,25,null,25,25,null,25,null,null,null,25,25,null,25,null,null,null,null,null,null,null,null,null,58,null,null,null,null,58,null,58,null,null,null,2,null,null,null,2,null,null,null,2,2,null,null,null,null,2,null,null,null,2,null,null,null,4,4,null,null,null,null,4,null,4,null,4,null,4,null,null,null,null,null,9,8,null,null,25,null,null,null,null,17,null,null,null,null,null,null,17,null,null,null,null,null,null,47,null,null,null,null,null],"name":"lib/groupher_server/accounts/delegates/mails.ex","source":"defmodule GroupherServer.Accounts.Delegate.Mails do\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1, done: 2]\n import ShortMaps\n\n alias GroupherServer.Repo\n alias GroupherServer.Accounts.{User, MentionMail, NotificationMail, SysNotificationMail}\n alias GroupherServer.Delivery\n alias Helper.ORM\n\n def mailbox_status(%User{} = user), do: Delivery.mailbox_status(user)\n\n def fetch_mentions(%User{} = user, filter) do\n with {:ok, mentions} <- Delivery.fetch_mentions(user, filter),\n {:ok, washed_mentions} <- wash_data(MentionMail, mentions.entries) do\n MentionMail |> messages_handler(washed_mentions, user, filter)\n end\n end\n\n def fetch_notifications(%User{} = user, filter) do\n with {:ok, notifications} <- Delivery.fetch_notifications(user, filter),\n {:ok, washed_notifications} <- wash_data(NotificationMail, notifications.entries) do\n NotificationMail |> messages_handler(washed_notifications, user, filter)\n end\n end\n\n def fetch_sys_notifications(%User{} = user, %{page: page, size: size, read: read}) do\n with {:ok, sys_notifications} <-\n Delivery.fetch_sys_notifications(user, %{page: page, size: size}),\n {:ok, washed_notifications} <-\n wash_data(SysNotificationMail, user, sys_notifications.entries) do\n SysNotificationMail\n |> Repo.insert_all(washed_notifications)\n\n SysNotificationMail\n |> order_by(desc: :inserted_at)\n |> where([m], m.user_id == ^user.id)\n |> where([m], m.read == ^read)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n end\n\n defp messages_handler(queryable, washed_data, %User{id: user_id}, %{\n page: page,\n size: size,\n read: read\n }) do\n queryable\n |> Repo.insert_all(washed_data)\n\n queryable\n |> order_by(desc: :inserted_at)\n |> where([m], m.to_user_id == ^user_id)\n |> where([m], m.read == ^read)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n def mark_mail_read(%MentionMail{id: id}, %User{} = user) do\n do_mark_mail_read(MentionMail, id, user)\n end\n\n def mark_mail_read(%NotificationMail{id: id}, %User{} = user) do\n do_mark_mail_read(NotificationMail, id, user)\n end\n\n def mark_mail_read(%SysNotificationMail{id: id}, %User{} = user) do\n with {:ok, mail} <- SysNotificationMail |> ORM.find_by(id: id, user_id: user.id) do\n mail |> ORM.update(%{read: true}) |> done(:status)\n end\n end\n\n def mark_mail_read_all(%User{} = user, :mention) do\n user |> do_mark_mail_read_all(MentionMail, :mention)\n end\n\n def mark_mail_read_all(%User{} = user, :notification) do\n user |> do_mark_mail_read_all(NotificationMail, :notification)\n end\n\n defp do_mark_mail_read(queryable, id, %User{} = user) do\n with {:ok, mail} <- queryable |> ORM.find_by(id: id, to_user_id: user.id) do\n mail |> ORM.update(%{read: true}) |> done(:status)\n end\n end\n\n defp do_mark_mail_read_all(%User{} = user, mail, atom) do\n query =\n mail\n |> where([m], m.to_user_id == ^user.id)\n\n Repo.update_all(query, set: [read: true])\n\n Delivery.mark_read_all(user, atom)\n end\n\n defp wash_data(MentionMail, []), do: {:ok, []}\n defp wash_data(NotificationMail, []), do: {:ok, []}\n\n defp wash_data(MentionMail, list), do: do_wash_data(list)\n defp wash_data(NotificationMail, list), do: do_wash_data(list)\n\n defp wash_data(SysNotificationMail, user, list) do\n convert =\n list\n |> Enum.map(\n &(Map.from_struct(&1)\n |> Map.delete(:__meta__)\n |> Map.put(:user_id, user.id))\n )\n\n {:ok, convert}\n end\n\n defp do_wash_data(list) do\n convert =\n list\n |> Enum.map(\n &(Map.from_struct(&1)\n |> Map.delete(:__meta__)\n |> Map.delete(:id)\n |> Map.delete(:from_user)\n |> Map.delete(:to_user))\n )\n\n {:ok, convert}\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,19,5,0,null,26,17,12,null],"name":"lib/groupher_server/statistics/statistics.ex","source":"defmodule GroupherServer.Statistics do\n @moduledoc \"\"\"\n The Statistics context.\n \"\"\"\n\n alias GroupherServer.Statistics.Delegate.{\n Contribute,\n Throttle\n }\n\n defdelegate make_contribute(info), to: Contribute\n defdelegate list_contributes(info), to: Contribute\n defdelegate list_contributes_digest(community), to: Contribute\n\n defdelegate log_publish_action(user), to: Throttle\n defdelegate load_throttle_record(user), to: Throttle\n defdelegate mock_throttle_attr(scope, user, opt), to: Throttle\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,315,null,null,null,null,null,null,null,315,null,null,null,null,204,null,null],"name":"lib/helper/guardian.ex","source":"defmodule Helper.Guardian do\n @moduledoc \"\"\"\n This module defines some helper function used by\n encode/decode jwt\n \"\"\"\n use Guardian, otp_app: :groupher_server\n\n @token_expireation 24 * 14\n\n def subject_for_token(resource, _claims) do\n {:ok, to_string(resource.id)}\n end\n\n def resource_from_claims(claims) do\n {:ok, %{id: claims[\"sub\"]}}\n end\n\n def jwt_encode(source, args \\\\ %{}) do\n encode_and_sign(source, args, ttl: {@token_expireation, :hour})\n end\n\n # jwt_decode\n def jwt_decode(token) do\n resource_from_token(token)\n end\nend"},{"coverage":[null,null,null,null,null,null,4,null,null,2,26,null,null,29,31,null,null,62,29,null,3,4,null],"name":"lib/groupher_server/delivery/delivery.ex","source":"defmodule GroupherServer.Delivery do\n @moduledoc \"\"\"\n The Delivery context.\n \"\"\"\n alias GroupherServer.Delivery.Delegate.{Mentions, Notifications, Utils}\n\n defdelegate mailbox_status(user), to: Utils\n\n # system_notifications\n defdelegate publish_system_notification(info), to: Notifications\n defdelegate fetch_sys_notifications(user, filter), to: Notifications\n\n # mentions\n defdelegate mention_someone(from_user, to_user, info), to: Mentions\n defdelegate fetch_mentions(user, filter), to: Mentions\n\n # notifications\n defdelegate notify_someone(from_user, to_user, info), to: Notifications\n defdelegate fetch_notifications(user, filter), to: Notifications\n\n defdelegate fetch_record(user), to: Utils\n defdelegate mark_read_all(user, opt), to: Utils\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/comment.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Comment do\n @moduledoc \"\"\"\n CMS mutations for comments\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_comment_mutations do\n @desc \"create a comment\"\n field :create_comment, :comment do\n # TODO use thread and force community pass-in\n arg(:thread, :thread, default_value: :post)\n arg(:id, non_null(:id))\n arg(:body, non_null(:string))\n\n # TDOO: use a comment resolver\n middleware(M.Authorize, :login)\n # TODO: 文章作者可以删除评论,文章可以设置禁止评论\n resolve(&R.CMS.create_comment/3)\n end\n\n field :delete_comment, :comment do\n arg(:thread, :thread, default_value: :post)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n # middleware(M.PassportLoader, source: [:post, :comment])\n middleware(M.PassportLoader, source: [:arg_thread, :comment])\n # TODO: 文章可以设置禁止评论\n # middleware(M.Passport, claim: \"owner;cms->c?->post.comment.delete\")\n middleware(M.Passport, claim: \"owner\")\n # middleware(M.Authorize, :login)\n resolve(&R.CMS.delete_comment/3)\n end\n\n @desc \"reply a exsiting comment\"\n field :reply_comment, :comment do\n arg(:thread, non_null(:thread), default_value: :post)\n arg(:id, non_null(:id))\n arg(:body, non_null(:string))\n\n middleware(M.Authorize, :login)\n\n resolve(&R.CMS.reply_comment/3)\n end\n\n @desc \"like a comment\"\n field :like_comment, :comment do\n arg(:thread, non_null(:cms_comment), default_value: :post_comment)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.like_comment/3)\n end\n\n @desc \"undo like comment\"\n # field :undo_like_comment, :idlike do\n field :undo_like_comment, :comment do\n arg(:thread, non_null(:cms_comment), default_value: :post_comment)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.undo_like_comment/3)\n end\n\n field :dislike_comment, :comment do\n arg(:thread, non_null(:cms_comment), default_value: :post_comment)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.dislike_comment/3)\n end\n\n field :undo_dislike_comment, :comment do\n arg(:thread, non_null(:cms_comment), default_value: :post_comment)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.undo_dislike_comment/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server.ex","source":"defmodule GroupherServer do\n @moduledoc \"\"\"\n GroupherServer keeps the contexts that define your domain\n and business logic.\n\n Contexts are also responsible for managing your data, regardless\n if it comes from the database, an external API or others.\n \"\"\"\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,123,null,null,null,null,null,null,null,null,null,null,null,null,null,26,null,null],"name":"lib/groupher_server/delivery/record.ex","source":"defmodule GroupherServer.Delivery.Record do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id)a\n @optional_fields ~w(mentions_record notifications_record sys_notifications_record)a\n\n @type t :: %Record{}\n schema \"delivery_records\" do\n field(:mentions_record, :map)\n field(:notifications_record, :map)\n field(:sys_notifications_record, :map)\n belongs_to(:user, User)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Record{} = record, attrs) do\n record\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/gettext.ex","source":"defmodule GroupherServerWeb.Gettext do\n @moduledoc \"\"\"\n A module providing Internationalization with a gettext-based API.\n\n By using [Gettext](https://hexdocs.pm/gettext),\n your module gains a set of macros for translations, for example:\n\n import GroupherServerWeb.Gettext\n\n # Simple translation\n gettext \"Here is the string to translate\"\n\n # Plural translation\n ngettext \"Here is the string to translate\",\n \"Here are the strings to translate\",\n 3\n\n # Domain-based translation\n dgettext \"errors\", \"Here is the error message to translate\"\n\n See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.\n \"\"\"\n use Gettext, otp_app: :groupher_server\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server_web/middleware/see_me.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.SeeMe do\n @behaviour Absinthe.Middleware\n\n def call(res, _) do\n # IO.inspect(\"see me\")\n res\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,45,null,null,null,null,null,null,null,null,null,null,null,null,null,19,null,null],"name":"lib/groupher_server/cms/post_comment_like.ex","source":"defmodule GroupherServer.CMS.PostCommentLike do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.PostComment\n\n @required_fields ~w(post_comment_id user_id)a\n\n @type t :: %PostCommentLike{}\n schema \"posts_comments_likes\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:post_comment, PostComment, foreign_key: :post_comment_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostCommentLike{} = post_comment_like, attrs) do\n post_comment_like\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:post_comment_id)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user_id, name: :posts_comments_likes_user_id_post_comment_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/accounts/bill.ex","source":"defmodule GroupherServer.Accounts.Bill do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id source_type source_title price)a\n @optional_fields ~w(source_id)a\n\n @type t :: %Bill{}\n schema \"bills\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:price, :integer)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Bill{} = bill, attrs) do\n bill\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/delivery/delivery_queries.ex","source":"defmodule GroupherServerWeb.Schema.Delivery.Queries do\n @moduledoc \"\"\"\n Delivery.Queries\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :delivery_queries do\n @desc \"get mention list?\"\n field :xxxx_todo, :boolean do\n arg(:id, non_null(:id))\n\n resolve(&R.Delivery.mention_someone/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,null,null,2,null,null,null,null,null,0,null,null,null,null,null,3,null,null,null,null,null,24,24,null,null,null,null,null,null,null,null,null,4,null,null,null,null,null,null,4,4,null,null,null,4,null,null,null,null,null,null,1,1,null,null,null,1,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null,44,null,null,372,null,null,123,null,null,8,null,null,1,null,null,1,null,null,null,null,7,null,null,null,0,null,null,0,null,null,0,null,null,1,null,null,1,null,null,null,null,null,7,null,null,null,7,null,null,7,null,null,null,7,null,null,7,null,null,null,7,null,null,7,null,null,null,7,null,null,null,91,null,null,null,0,null,null,null,null,null,null,2,null,null,null,null,null,null,7,null,null,null,null,null,null,14,null,null,null,82,null,null,null,44,null,null,298,null,null,null],"name":"lib/helper/query_builder.ex","source":"defmodule Helper.QueryBuilder do\n # alias GroupherServer.Repo\n import Ecto.Query, warn: false\n\n @doc \"\"\"\n handle [3] situation:\n\n 1. basic query with filter\n 2. reaction_user's count\n 3. is viewer reacted?\n\n bewteen [THREAD] and [REACT]\n [THREAD]: cms thread, include: Post, Job, Video, Repo ...\n [REACT]; favorites, stars, watchs ...\n \"\"\"\n def members_pack(queryable, %{filter: filter}) do\n queryable |> load_inner_users(filter)\n end\n\n def members_pack(queryable, %{viewer_did: _, cur_user: cur_user}) do\n queryable |> where([f], f.user_id == ^cur_user.id)\n end\n\n def members_pack(queryable, %{count: _, type: :post}) do\n queryable\n |> group_by([f], f.post_id)\n |> select([f], count(f.id))\n end\n\n def members_pack(queryable, %{count: _, type: :community}) do\n queryable\n |> group_by([f], f.community_id)\n |> select([f], count(f.id))\n end\n\n def load_inner_users(queryable, filter) do\n queryable\n |> join(:inner, [f], u in assoc(f, :user))\n |> select([f, u], u)\n |> filter_pack(filter)\n end\n\n @doc \"\"\"\n load replies of the given comment\n \"\"\"\n def load_inner_replies(queryable, filter) do\n queryable\n |> filter_pack(filter)\n |> join(:inner, [c], r in assoc(c, :reply))\n |> select([c, r], r)\n end\n\n @doc \"\"\"\n inserted in latest x mounth\n \"\"\"\n def recent_inserted(queryable, months: count) do\n end_of_today = Timex.now() |> Timex.end_of_day()\n x_months_ago = Timex.today() |> Timex.shift(months: -count) |> Timex.to_datetime()\n\n queryable\n |> where([q], q.inserted_at >= ^x_months_ago)\n |> where([q], q.inserted_at <= ^end_of_today)\n end\n\n @doc \"\"\"\n inserted in latest x days\n \"\"\"\n def recent_inserted(queryable, days: count) do\n end_of_today = Timex.now() |> Timex.end_of_day()\n x_days_ago = Timex.today() |> Timex.shift(days: -count) |> Timex.to_datetime()\n\n queryable\n |> where([q], q.inserted_at >= ^x_days_ago)\n |> where([q], q.inserted_at <= ^end_of_today)\n end\n\n # this is strategy will cause\n # defp sort_strategy(:desc_inserted), do: [desc: :inserted_at, desc: :views]\n # defp sort_strategy(:most_views), do: [desc: :views, desc: :inserted_at]\n # defp sort_strategy(:least_views), do: [asc: :views, desc: :inserted_at]\n # defp strategy(:most_stars), do: [desc: :views, desc: :inserted_at]\n\n defp sort_by_count(queryable, field, direction) do\n queryable\n |> join(:left, [p], s in assoc(p, ^field))\n |> group_by([p], p.id)\n |> select([p], p)\n |> order_by([_, s], {^direction, fragment(\"count(?)\", s.id)})\n end\n\n def default_article_filters, do: %{pin: false, markDelete: false}\n\n def filter_pack(queryable, filter) when is_map(filter) do\n Enum.reduce(filter, queryable, fn\n {:sort, :desc_inserted}, queryable ->\n # queryable |> order_by(^sort_strategy(:desc_inserted))\n queryable |> order_by(desc: :inserted_at)\n\n {:sort, :asc_inserted}, queryable ->\n queryable |> order_by(asc: :inserted_at)\n\n {:sort, :desc_index}, queryable ->\n queryable |> order_by(desc: :index)\n\n {:sort, :asc_index}, queryable ->\n queryable |> order_by(asc: :index)\n\n {:sort, :most_views}, queryable ->\n # this will cause error in Dialyzer\n # queryable |> order_by(^sort_strategy(:most_views))\n queryable |> order_by(desc: :views, desc: :inserted_at)\n\n {:sort, :least_views}, queryable ->\n # queryable |> order_by(^sort_strategy(:least_views))\n queryable |> order_by(asc: :views, desc: :inserted_at)\n\n {:sort, :most_stars}, queryable ->\n queryable |> sort_by_count(:stars, :desc)\n\n {:sort, :least_stars}, queryable ->\n queryable |> sort_by_count(:stars, :asc)\n\n {:sort, :most_likes}, queryable ->\n queryable |> sort_by_count(:likes, :desc)\n\n {:sort, :most_dislikes}, queryable ->\n queryable |> sort_by_count(:dislikes, :desc)\n\n {:when, :today}, queryable ->\n # date = DateTime.utc_now() |> Timex.to_datetime()\n # use timezone info is server is not in the some timezone\n # Timex.now(\"America/Chicago\")\n date = Timex.now()\n\n queryable\n |> where([p], p.inserted_at >= ^Timex.beginning_of_day(date))\n |> where([p], p.inserted_at <= ^Timex.end_of_day(date))\n\n {:when, :this_week}, queryable ->\n date = Timex.now()\n\n queryable\n |> where([p], p.inserted_at >= ^Timex.beginning_of_week(date))\n |> where([p], p.inserted_at <= ^Timex.end_of_week(date))\n\n {:when, :this_month}, queryable ->\n date = Timex.now()\n\n queryable\n |> where([p], p.inserted_at >= ^Timex.beginning_of_month(date))\n |> where([p], p.inserted_at <= ^Timex.end_of_month(date))\n\n {:when, :this_year}, queryable ->\n date = Timex.now()\n\n queryable\n |> where([p], p.inserted_at >= ^Timex.beginning_of_year(date))\n |> where([p], p.inserted_at <= ^Timex.end_of_year(date))\n\n # TODO: remove\n {_, :all}, queryable ->\n queryable\n\n # TODO: use raw instead title\n {:article_tag, tag_name}, queryable ->\n from(\n q in queryable,\n join: t in assoc(q, :tags),\n where: t.title == ^tag_name\n )\n\n {:category, catetory_raw}, queryable ->\n from(\n q in queryable,\n join: t in assoc(q, :categories),\n where: t.raw == ^catetory_raw\n )\n\n {:community, community_raw}, queryable ->\n from(\n q in queryable,\n join: t in assoc(q, :communities),\n where: t.raw == ^community_raw\n )\n\n {:first, first}, queryable ->\n queryable |> limit(^first)\n\n {:pin, bool}, queryable ->\n queryable\n |> where([p], p.pin == ^bool)\n\n {:markDelete, bool}, queryable ->\n queryable\n |> where([p], p.markDelete == ^bool)\n\n {_, _}, queryable ->\n queryable\n end)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,4092,null,null,null,6600,null,null,null,65366,null,null,null,null,null,null,null,null,null,null,null,361,null,null,null,361,null,null],"name":"lib/groupher_server_web/schema.ex","source":"defmodule GroupherServerWeb.Schema do\n @moduledoc \"\"\"\n scham index\n \"\"\"\n use Absinthe.Schema\n\n alias GroupherServerWeb.Schema.{Account, CMS, Delivery, Statistics, Utils}\n alias GroupherServerWeb.Middleware, as: M\n\n import_types(Absinthe.Type.Custom)\n\n # utils\n import_types(Utils.CommonTypes)\n\n # account\n import_types(Account.Types)\n import_types(Account.Queries)\n import_types(Account.Mutations)\n\n # statistics\n import_types(Statistics.Types)\n import_types(Statistics.Queries)\n import_types(Statistics.Mutations)\n\n # delivery\n import_types(Delivery.Types)\n import_types(Delivery.Queries)\n import_types(Delivery.Mutations)\n\n # cms\n import_types(CMS.Types)\n import_types(CMS.Queries)\n import_types(CMS.Mutations.Community)\n import_types(CMS.Mutations.Operation)\n import_types(CMS.Mutations.Post)\n import_types(CMS.Mutations.Job)\n import_types(CMS.Mutations.Comment)\n\n query do\n import_fields(:account_queries)\n import_fields(:statistics_queries)\n import_fields(:delivery_queries)\n import_fields(:cms_queries)\n end\n\n mutation do\n # account\n import_fields(:account_mutations)\n # statistics\n import_fields(:statistics_mutations)\n # delivery\n import_fields(:delivery_mutations)\n # cms\n import_fields(:cms_mutation_community)\n import_fields(:cms_opertion_mutations)\n import_fields(:cms_post_mutations)\n import_fields(:cms_job_mutations)\n import_fields(:cms_comment_mutations)\n end\n\n def middleware(middleware, _field, %{identifier: :query}) do\n middleware ++ [M.GeneralError]\n end\n\n def middleware(middleware, _field, %{identifier: :mutation}) do\n middleware ++ [M.ChangesetErrors]\n end\n\n def middleware(middleware, _field, _object) do\n [ApolloTracing.Middleware.Tracing, ApolloTracing.Middleware.Caching] ++ middleware\n end\n\n def plugins do\n [Absinthe.Middleware.Dataloader | Absinthe.Plugin.defaults()]\n end\n\n def dataloader do\n alias GroupherServer.{Accounts, CMS}\n\n Dataloader.new()\n |> Dataloader.add_source(Accounts, Accounts.Helper.Loader.data())\n |> Dataloader.add_source(CMS, CMS.Helper.Loader.data())\n end\n\n def context(ctx) do\n ctx |> Map.put(:loader, dataloader())\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/post.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Post do\n @moduledoc \"\"\"\n CMS mutations for post\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_post_mutations do\n @desc \"create a user\"\n field :create_post, :post do\n arg(:title, non_null(:string))\n arg(:body, non_null(:string))\n arg(:digest, non_null(:string))\n arg(:length, non_null(:integer))\n arg(:link_addr, :string)\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PublishThrottle)\n # middleware(M.PublishThrottle, interval: 3, hour_limit: 15, day_limit: 30)\n resolve(&R.CMS.create_article/3)\n end\n\n @desc \"pin a post\"\n field :pin_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->post.pin\")\n resolve(&R.CMS.pin_post/3)\n end\n\n @desc \"unpin a post\"\n field :undo_pin_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->post.undo_pin\")\n resolve(&R.CMS.undo_pin_post/3)\n end\n\n @desc \"markDelete a post, not delete\"\n field :trash_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->post.markDelete\")\n\n resolve(&R.CMS.trash_post/3)\n end\n\n @desc \"markDelete a post, not delete\"\n field :undo_trash_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->post.undo_trash\")\n\n resolve(&R.CMS.undo_trash_post/3)\n end\n\n @desc \"delete a cms/post\"\n # TODO: if post belongs to multi communities, unset instead delete\n field :delete_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :post)\n middleware(M.Passport, claim: \"owner;cms->c?->post.delete\")\n\n resolve(&R.CMS.delete_content/3)\n end\n\n @desc \"update a cms/post\"\n field :update_post, :post do\n arg(:id, non_null(:id))\n arg(:title, :string)\n arg(:body, :string)\n arg(:digest, :string)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :post)\n middleware(M.Passport, claim: \"owner;cms->c?->post.edit\")\n\n resolve(&R.CMS.update_article/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,106,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,12,null,null],"name":"lib/groupher_server/cms/category.ex","source":"defmodule GroupherServer.CMS.Category do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community}\n # alias GroupherServer.Accounts\n # alias Helper.Certification\n\n @required_fields ~w(title raw author_id)a\n\n @type t :: %Category{}\n\n schema \"categories\" do\n field(:title, :string)\n field(:raw, :string)\n belongs_to(:author, Author)\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_categories\",\n join_keys: [category_id: :id, community_id: :id],\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Category{} = category, attrs) do\n category\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n # |> validate_inclusion(:title, Certification.editor_titles(:cms))\n # |> foreign_key_constraint(:community_id)\n # |> foreign_key_constraint(:author_id)\n |> unique_constraint(:title, name: :categories_title_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/job.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Job do\n @moduledoc \"\"\"\n CMS mutations for job\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_job_mutations do\n @desc \"create a user\"\n field :create_job, :job do\n arg(:title, non_null(:string))\n arg(:company, non_null(:string))\n arg(:company_logo, non_null(:string))\n arg(:location, non_null(:string))\n arg(:body, non_null(:string))\n arg(:digest, non_null(:string))\n arg(:length, non_null(:integer))\n arg(:community_id, non_null(:id))\n arg(:link_addr, :string)\n arg(:link_source, :string)\n\n arg(:thread, :thread, default_value: :job)\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.create_article/3)\n end\n\n @desc \"delete a job\"\n field :delete_job, :job do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :job)\n middleware(M.Passport, claim: \"owner;cms->c?->job.delete\")\n\n resolve(&R.CMS.delete_content/3)\n end\n\n @desc \"update a cms/job\"\n field :update_job, :job do\n arg(:id, non_null(:id))\n arg(:title, :string)\n arg(:body, :string)\n arg(:digest, :string)\n # ...\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :job)\n middleware(M.Passport, claim: \"owner;cms->c?->job.edit\")\n\n resolve(&R.CMS.update_article/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/account/account_mutations.ex","source":"defmodule GroupherServerWeb.Schema.Account.Mutations do\n @moduledoc \"\"\"\n accounts mutations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :account_mutations do\n # @desc \"hehehef: create a user\"\n # field :create_user, :user do\n # arg(:username, non_null(:string))\n # arg(:nickname, non_null(:string))\n # arg(:bio, non_null(:string))\n # arg(:company, non_null(:string))\n\n # resolve(&R.Accounts.create_user/3)\n # end\n\n @desc \"update user's profile\"\n field :update_profile, :user do\n arg(:profile, non_null(:user_profile_input))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.update_profile/3)\n end\n\n field :github_signin, :token_info do\n arg(:code, non_null(:string))\n # arg(:profile, non_null(:github_profile_input))\n\n middleware(M.GithubUser)\n resolve(&R.Accounts.github_signin/3)\n end\n\n @doc \"follow a user\"\n field :follow, :user do\n arg(:user_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.follow/3)\n end\n\n @doc \"undo follow to a user\"\n field :undo_follow, :user do\n arg(:user_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.undo_follow/3)\n end\n\n @desc \"mark a mention as read\"\n field :mark_mention_read, :status do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_mention_read/3)\n end\n\n @desc \"mark a all unread mention as read\"\n field :mark_mention_read_all, :status do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_mention_read_all/3)\n end\n\n @desc \"mark a notification as read\"\n field :mark_notification_read, :status do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_notification_read/3)\n end\n\n @desc \"mark a all unread notifications as read\"\n field :mark_notification_read_all, :status do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_notification_read_all/3)\n end\n\n @desc \"mark a system notification as read\"\n field :mark_sys_notification_read, :status do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_sys_notification_read/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/utils/common_types.ex","source":"defmodule GroupherServerWeb.Schema.Helper.Metrics do\n use Absinthe.Schema.Notation\n use Absinthe.Ecto, repo: GroupherServer.Repo\n\n object :status do\n field(:done, :boolean)\n field(:id, :id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,151,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,62,null,null],"name":"lib/groupher_server/delivery/notification.ex","source":"defmodule GroupherServer.Delivery.Notification do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id action source_title source_id source_preview source_type)a\n @optional_fields ~w(parent_id parent_type read)a\n\n @type t :: %Notification{}\n schema \"notifications\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n field(:action, :string)\n\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:parent_id, :string)\n field(:parent_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Notification{} = notification, attrs) do\n notification\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,12,12,12,null,12,null,null,12,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/accounts/delegates/reacted_contents.ex","source":"defmodule GroupherServer.Accounts.Delegate.ReactedContents do\n @moduledoc \"\"\"\n get contents(posts, jobs, videos ...) that user reacted (star, favorite ..)\n \"\"\"\n import GroupherServer.CMS.Helper.MatcherOld\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1]\n import ShortMaps\n\n alias Helper.{ORM, QueryBuilder}\n alias GroupherServer.Accounts.User\n\n def reacted_contents(thread, react, ~m(page size)a = filter, %User{id: user_id}) do\n with {:ok, action} <- match_action(thread, react) do\n action.reactor\n |> where([f], f.user_id == ^user_id)\n |> join(:inner, [f], p in assoc(f, ^thread))\n |> select([f, p], p)\n |> QueryBuilder.filter_pack(filter)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n end\n\n # def reacted_count(thread, react, %User{id: user_id}) do\n # with {:ok, action} <- match_action(thread, react) do\n # action.reactor\n # |> where([f], f.user_id == ^user_id)\n # |> group_by([f], f.post_id)\n # |> select([f], count(f.id))\n # end\n # end\nend"},{"coverage":[null,null,null,null,null,null,null,null,4,null,4,null,null,null,0,null,null,0,null,null],"name":"lib/groupher_server_web/middleware/force_loader.ex","source":"# this is a tmp solution for load related-users like situations\n# it turn dataloader into nomal N+1 resolver\n# NOTE: it should be replaced using \"Select-Top-N-By-Group\" solution\n\ndefmodule GroupherServerWeb.Middleware.ForceLoader do\n @behaviour Absinthe.Middleware\n\n def call(%{source: %{id: id}} = resolution, _) do\n arguments = resolution.arguments |> Map.merge(%{what_ever: id})\n\n %{resolution | arguments: arguments}\n # resolution\n end\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(resolution, _) do\n resolution\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,475,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,167,null,null],"name":"lib/groupher_server/cms/post_comment.ex","source":"defmodule GroupherServer.CMS.PostComment do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n alias GroupherServer.CMS.{\n Post,\n PostCommentDislike,\n PostCommentLike,\n PostCommentReply\n }\n\n @required_fields ~w(body author_id post_id floor)a\n @optional_fields ~w(reply_id)a\n\n @type t :: %PostComment{}\n schema \"posts_comments\" do\n field(:body, :string)\n field(:floor, :integer)\n belongs_to(:author, Accounts.User, foreign_key: :author_id)\n belongs_to(:post, Post, foreign_key: :post_id)\n belongs_to(:reply_to, PostComment, foreign_key: :reply_id)\n\n has_many(:replies, {\"posts_comments_replies\", PostCommentReply})\n has_many(:likes, {\"posts_comments_likes\", PostCommentLike})\n has_many(:dislikes, {\"posts_comments_dislikes\", PostCommentDislike})\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostComment{} = post_comment, attrs) do\n post_comment\n |> cast(attrs, @required_fields ++ @optional_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:post_id)\n |> foreign_key_constraint(:author_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,96,96,96,null,null,96,null,null,null,null,null,null,null,6,6,6,null,null,6,null,null,null,null,null,null,null,5,5,5,null,null,5,null,null,null,null,null,null,null,4,4,4,null,null,4,null,null,null,null,null,null,null,136,136,136,null,null,136,null,null,null,null,null,null,null,5,5,5,null,5,null,null,5,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,30,null,null,null,null,24,null,null,null],"name":"lib/groupher_server/accounts/delegates/achievements.ex","source":"defmodule GroupherServer.Accounts.Delegate.Achievements do\n @moduledoc \"\"\"\n user achievements related\n acheiveements formula:\n 1. create content been stared by other user + 1\n 2. create content been watched by other user + 1\n 3. create content been favorited by other user + 2\n 4. followed by other user + 3\n \"\"\"\n import Helper.Utils, only: [get_config: 2]\n import ShortMaps\n\n alias Helper.{ORM, SpecType}\n alias GroupherServer.Accounts.{Achievement, User}\n\n @favorite_weight get_config(:general, :user_achieve_favorite_weight)\n @star_weight get_config(:general, :user_achieve_star_weight)\n # @watch_weight get_config(:general, :user_achieve_watch_weight)\n @follow_weight get_config(:general, :user_achieve_follow_weight)\n\n @doc \"\"\"\n add user's achievement by add followers_count of favorite_weight\n \"\"\"\n @spec achieve(User.t(), atom, atom) :: SpecType.done()\n def achieve(%User{id: user_id}, :add, :follow) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n followers_count = achievement.followers_count + @follow_weight\n reputation = achievement.reputation + @follow_weight\n\n achievement\n |> ORM.update(~m(followers_count reputation)a)\n end\n end\n\n @doc \"\"\"\n minus user's achievement by add followers_count of favorite_weight\n \"\"\"\n def achieve(%User{id: user_id}, :minus, :follow) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n followers_count = achievement.followers_count |> safe_minus(@follow_weight)\n reputation = achievement.reputation |> safe_minus(@follow_weight)\n\n achievement\n |> ORM.update(~m(followers_count reputation)a)\n end\n end\n\n @doc \"\"\"\n add user's achievement by contents_stared_count of star_weight\n \"\"\"\n def achieve(%User{id: user_id} = _user, :add, :star) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n contents_stared_count = achievement.contents_stared_count + @star_weight\n reputation = achievement.reputation + @star_weight\n\n achievement\n |> ORM.update(~m(contents_stared_count reputation)a)\n end\n end\n\n @doc \"\"\"\n minus user's achievement by contents_stared_count of star_weight\n \"\"\"\n def achieve(%User{id: user_id} = _user, :minus, :star) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n contents_stared_count = achievement.contents_stared_count |> safe_minus(@star_weight)\n reputation = achievement.reputation |> safe_minus(@star_weight)\n\n achievement\n |> ORM.update(~m(contents_stared_count reputation)a)\n end\n end\n\n @doc \"\"\"\n minus user's achievement by contents_favorited_count of favorite_weight\n \"\"\"\n def achieve(%User{id: user_id} = _user, :add, :favorite) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n contents_favorited_count = achievement.contents_favorited_count + @favorite_weight\n reputation = achievement.reputation + @favorite_weight\n\n achievement\n |> ORM.update(~m(contents_favorited_count reputation)a)\n end\n end\n\n @doc \"\"\"\n add user's achievement by contents_favorited_count of favorite_weight\n \"\"\"\n def achieve(%User{id: user_id} = _user, :minus, :favorite) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n contents_favorited_count =\n achievement.contents_favorited_count |> safe_minus(@favorite_weight)\n\n reputation = achievement.reputation |> safe_minus(@favorite_weight)\n\n achievement\n |> ORM.update(~m(contents_favorited_count reputation)a)\n end\n end\n\n # def achieve(%User{} = _user, :+, :watch) do\n # IO.inspect(\"acheiveements add :conent_watched\")\n # end\n\n # def achieve(%User{} = _user, :+, key) do\n # IO.inspect(\"acheiveements add #{key}\")\n # end\n\n # def achieve(%User{} = _user, :-, _key) do\n # IO.inspect(\"acheiveements plus\")\n # end\n\n @spec safe_minus(non_neg_integer(), non_neg_integer()) :: non_neg_integer()\n defp safe_minus(count, unit) when is_integer(count) and is_integer(unit) and unit > 0 do\n case count <= 0 do\n true ->\n 0\n\n false ->\n count - unit\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/channels/user_socket.ex","source":"defmodule GroupherServerWeb.UserSocket do\n use Phoenix.Socket\n\n ## Channels\n # channel \"room:*\", GroupherServerWeb.RoomChannel\n\n ## Transports\n transport(:websocket, Phoenix.Transports.WebSocket)\n # transport :longpoll, Phoenix.Transports.LongPoll\n\n # Socket params are passed from the client and can\n # be used to verify and authenticate a user. After\n # verification, you can put default assigns into\n # the socket that will be set for all channels, ie\n #\n # {:ok, assign(socket, :user_id, verified_user_id)}\n #\n # To deny connection, return `:error`.\n #\n # See `Phoenix.Token` documentation for examples in\n # performing token verification on connect.\n def connect(_params, socket) do\n {:ok, socket}\n end\n\n # Socket id's are topics that allow you to identify all sockets for a given user:\n #\n # def id(socket), do: \"user_socket:#{socket.assigns.user_id}\"\n #\n # Would allow you to broadcast a \"disconnect\" event and terminate\n # all active sockets and channels for a given user:\n #\n # GroupherServerWeb.Endpoint.broadcast(\"user_socket:#{user.id}\", \"disconnect\", %{})\n #\n # Returning `nil` makes this socket anonymous.\n def id(_socket), do: nil\nend"}]} \ No newline at end of file diff --git a/lib/groupher_server/accounts/delegates/collect_folder.ex b/lib/groupher_server/accounts/delegates/collect_folder.ex index e8601ef59..6c3e42adc 100644 --- a/lib/groupher_server/accounts/delegates/collect_folder.ex +++ b/lib/groupher_server/accounts/delegates/collect_folder.ex @@ -3,7 +3,7 @@ defmodule GroupherServer.Accounts.Delegate.CollectFolder do user collect folder related """ import Ecto.Query, warn: false - import GroupherServer.CMS.Helper.Matcher2 + import GroupherServer.CMS.Helper.Matcher alias Helper.Types, as: T alias Helper.QueryBuilder diff --git a/lib/groupher_server/accounts/delegates/publish.ex b/lib/groupher_server/accounts/delegates/publish.ex index f98d6c3cc..0dc85dc55 100644 --- a/lib/groupher_server/accounts/delegates/publish.ex +++ b/lib/groupher_server/accounts/delegates/publish.ex @@ -7,7 +7,7 @@ defmodule GroupherServer.Accounts.Delegate.Publish do # import Helper.ErrorCode import ShortMaps - import GroupherServer.CMS.Helper.Matcher + import GroupherServer.CMS.Helper.MatcherOld alias Helper.{ORM, QueryBuilder} # alias GroupherServer.{Accounts, Repo} diff --git a/lib/groupher_server/cms/delegates/abuse_report.ex b/lib/groupher_server/cms/delegates/abuse_report.ex index 4cb6b4909..db1a8036e 100644 --- a/lib/groupher_server/cms/delegates/abuse_report.ex +++ b/lib/groupher_server/cms/delegates/abuse_report.ex @@ -5,7 +5,7 @@ defmodule GroupherServer.CMS.Delegate.AbuseReport do import Ecto.Query, warn: false import Helper.Utils, only: [done: 1, strip_struct: 1, get_config: 2] - import GroupherServer.CMS.Helper.Matcher2 + import GroupherServer.CMS.Helper.Matcher import ShortMaps alias Helper.ORM diff --git a/lib/groupher_server/cms/delegates/article_collect.ex b/lib/groupher_server/cms/delegates/article_collect.ex index 7e158aaa5..798e9411a 100644 --- a/lib/groupher_server/cms/delegates/article_collect.ex +++ b/lib/groupher_server/cms/delegates/article_collect.ex @@ -2,7 +2,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCollect do @moduledoc """ reaction[upvote, collect, watch ...] on article [post, job...] """ - import GroupherServer.CMS.Helper.Matcher2 + import GroupherServer.CMS.Helper.Matcher import Ecto.Query, warn: false import Helper.Utils, only: [done: 1] diff --git a/lib/groupher_server/cms/delegates/article_comment.ex b/lib/groupher_server/cms/delegates/article_comment.ex index 6fdcf9dc6..22edf5cab 100644 --- a/lib/groupher_server/cms/delegates/article_comment.ex +++ b/lib/groupher_server/cms/delegates/article_comment.ex @@ -7,7 +7,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do import Helper.ErrorCode import GroupherServer.CMS.Delegate.Helper, only: [mark_viewer_emotion_states: 3] - import GroupherServer.CMS.Helper.Matcher2 + import GroupherServer.CMS.Helper.Matcher import ShortMaps alias Helper.Types, as: T diff --git a/lib/groupher_server/cms/delegates/article_comment_action.ex b/lib/groupher_server/cms/delegates/article_comment_action.ex index 54ebc4953..1723fe925 100644 --- a/lib/groupher_server/cms/delegates/article_comment_action.ex +++ b/lib/groupher_server/cms/delegates/article_comment_action.ex @@ -9,7 +9,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCommentAction do import GroupherServer.CMS.Delegate.ArticleComment, only: [add_participator_to_article: 2, do_create_comment: 4, update_article_comments_count: 2] - import GroupherServer.CMS.Helper.Matcher2 + import GroupherServer.CMS.Helper.Matcher alias Helper.Types, as: T alias Helper.ORM diff --git a/lib/groupher_server/cms/delegates/article_community.ex b/lib/groupher_server/cms/delegates/article_community.ex index 4bc48ce52..fc4512707 100644 --- a/lib/groupher_server/cms/delegates/article_community.ex +++ b/lib/groupher_server/cms/delegates/article_community.ex @@ -2,12 +2,12 @@ defmodule GroupherServer.CMS.Delegate.ArticleCommunity do @moduledoc """ set / unset operations for Article-like resource """ - import GroupherServer.CMS.Helper.Matcher2 + import GroupherServer.CMS.Helper.Matcher import Ecto.Query, warn: false import Helper.ErrorCode import Helper.Utils, only: [strip_struct: 1, done: 1] - import GroupherServer.CMS.Helper.Matcher2 + import GroupherServer.CMS.Helper.Matcher alias Helper.Types, as: T alias Helper.ORM diff --git a/lib/groupher_server/cms/delegates/article_curd.ex b/lib/groupher_server/cms/delegates/article_curd.ex index 0f8ac5900..d7354787b 100644 --- a/lib/groupher_server/cms/delegates/article_curd.ex +++ b/lib/groupher_server/cms/delegates/article_curd.ex @@ -4,7 +4,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do """ import Ecto.Query, warn: false - import GroupherServer.CMS.Helper.Matcher2 + import GroupherServer.CMS.Helper.Matcher import Helper.Utils, only: [done: 1, pick_by: 2, integerfy: 1, strip_struct: 1, module_to_thread: 1] diff --git a/lib/groupher_server/cms/delegates/article_emotion.ex b/lib/groupher_server/cms/delegates/article_emotion.ex index 599c3a2af..a7477c5fe 100644 --- a/lib/groupher_server/cms/delegates/article_emotion.ex +++ b/lib/groupher_server/cms/delegates/article_emotion.ex @@ -3,7 +3,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleEmotion do CURD and operations for article comments """ import Ecto.Query, warn: false - import GroupherServer.CMS.Helper.Matcher2 + import GroupherServer.CMS.Helper.Matcher import GroupherServer.CMS.Delegate.Helper, only: [update_emotions_field: 4] diff --git a/lib/groupher_server/cms/delegates/article_tag.ex b/lib/groupher_server/cms/delegates/article_tag.ex index 5f8f3538e..4a10b1de9 100644 --- a/lib/groupher_server/cms/delegates/article_tag.ex +++ b/lib/groupher_server/cms/delegates/article_tag.ex @@ -3,7 +3,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleTag do community curd """ import Ecto.Query, warn: false - import GroupherServer.CMS.Helper.Matcher2 + import GroupherServer.CMS.Helper.Matcher import Helper.Validator.Guards, only: [g_is_id: 1] import Helper.Utils, only: [done: 1, map_atom_values_to_upcase_str: 1] import GroupherServer.CMS.Delegate.ArticleCURD, only: [ensure_author_exists: 1] diff --git a/lib/groupher_server/cms/delegates/article_upvote.ex b/lib/groupher_server/cms/delegates/article_upvote.ex index 247aa0986..bebc567c0 100644 --- a/lib/groupher_server/cms/delegates/article_upvote.ex +++ b/lib/groupher_server/cms/delegates/article_upvote.ex @@ -2,7 +2,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleUpvote do @moduledoc """ reaction[upvote, collect, watch ...] on article [post, job...] """ - import GroupherServer.CMS.Helper.Matcher2 + import GroupherServer.CMS.Helper.Matcher import Ecto.Query, warn: false import Helper.Utils, only: [done: 1] diff --git a/lib/groupher_server/cms/delegates/comment_curd.ex b/lib/groupher_server/cms/delegates/comment_curd.ex index ca4d4cf1d..e62dc17ca 100644 --- a/lib/groupher_server/cms/delegates/comment_curd.ex +++ b/lib/groupher_server/cms/delegates/comment_curd.ex @@ -6,7 +6,7 @@ defmodule GroupherServer.CMS.Delegate.CommentCURD do import Helper.Utils, only: [done: 1] import Helper.ErrorCode - import GroupherServer.CMS.Helper.Matcher + import GroupherServer.CMS.Helper.MatcherOld import ShortMaps alias Helper.{ORM, QueryBuilder} diff --git a/lib/groupher_server/cms/delegates/helper.ex b/lib/groupher_server/cms/delegates/helper.ex index 2a1ce15f8..1bb952c9f 100644 --- a/lib/groupher_server/cms/delegates/helper.ex +++ b/lib/groupher_server/cms/delegates/helper.ex @@ -4,7 +4,7 @@ defmodule GroupherServer.CMS.Delegate.Helper do """ import Helper.Utils, only: [get_config: 2, done: 1, strip_struct: 1] import Ecto.Query, warn: false - import GroupherServer.CMS.Helper.Matcher2 + import GroupherServer.CMS.Helper.Matcher import ShortMaps alias Helper.{ORM, QueryBuilder} diff --git a/lib/groupher_server/cms/delegates/seeds.ex b/lib/groupher_server/cms/delegates/seeds.ex index b20e648e3..562c5f403 100644 --- a/lib/groupher_server/cms/delegates/seeds.ex +++ b/lib/groupher_server/cms/delegates/seeds.ex @@ -7,7 +7,6 @@ defmodule GroupherServer.CMS.Delegate.Seeds do import Ecto.Query, warn: false @oss_endpoint "https://cps-oss.oss-cn-shanghai.aliyuncs.com" - # import GroupherServer.CMS.Helper.Matcher # import Helper.Utils, only: [done: 1, map_atom_value: 2] # import GroupherServer.CMS.Delegate.ArticleCURD, only: [ensure_author_exists: 1] import ShortMaps diff --git a/lib/groupher_server/cms/helper/matcher.ex b/lib/groupher_server/cms/helper/matcher.ex index d592c93e9..c1834cc00 100644 --- a/lib/groupher_server/cms/helper/matcher.ex +++ b/lib/groupher_server/cms/helper/matcher.ex @@ -2,74 +2,36 @@ defmodule GroupherServer.CMS.Helper.Matcher do @moduledoc """ this module defined the matches and handy guard ... """ - import Ecto.Query, warn: false - - alias GroupherServer.CMS.{ - Community, - # threads - Post, - Repo, - Job, - # viewer - # reactions - # comments - PostComment, - # commtnes reaction - PostCommentLike, - # - Community - } - - ######################################### - ## posts ... - ######################################### - def match_action(:post, :self), - do: {:ok, %{target: Post, reactor: Post, preload: :author}} - - def match_action(:post, :community), - do: {:ok, %{target: Post, reactor: Community}} - - def match_action(:post, :comment), - do: {:ok, %{target: Post, reactor: PostComment, preload: :author}} - - def match_action(:post_comment, :like), - do: {:ok, %{target: PostComment, reactor: PostCommentLike}} - - ######################################### - ## jobs ... - ######################################### - def match_action(:job, :self), - do: {:ok, %{target: Job, reactor: Job, preload: :author}} - def match_action(:job, :community), - do: {:ok, %{target: Job, reactor: Community}} - - ######################################### - ## repos ... - ######################################### - def match_action(:repo, :self), - do: {:ok, %{target: Repo, reactor: Repo, preload: :author}} - - def match_action(:repo, :community), - do: {:ok, %{target: Repo, reactor: Community}} - - # dynamic where query match - def dynamic_where(thread, id) do - case thread do - :post -> - {:ok, dynamic([p], p.post_id == ^id)} + import Ecto.Query, warn: false + import GroupherServer.CMS.Helper.MatcherMacros - :post_comment -> - {:ok, dynamic([p], p.post_comment_id == ^id)} + alias GroupherServer.{Accounts, CMS} - :job -> - {:ok, dynamic([p], p.job_id == ^id)} + alias Accounts.User + alias CMS.ArticleComment - :repo -> - {:ok, dynamic([p], p.repo_id == ^id)} + def match(:account) do + {:ok, + %{ + model: User, + foreign_key: :account_id, + preload: :account, + default_meta: Accounts.Embeds.UserMeta.default_meta() + }} + end - _ -> - {:error, 'where is not match'} - end + def match(:article_comment) do + {:ok, + %{ + model: ArticleComment, + foreign_key: :article_comment_id, + preload: :article_comment, + default_meta: CMS.Embeds.ArticleCommentMeta.default_meta() + }} end + + thread_matches() + thread_query_matches() + comment_article_matches() end diff --git a/lib/groupher_server/cms/helper/matcher2.ex b/lib/groupher_server/cms/helper/matcher_macros.ex similarity index 70% rename from lib/groupher_server/cms/helper/matcher2.ex rename to lib/groupher_server/cms/helper/matcher_macros.ex index a25b72b19..5614934ac 100644 --- a/lib/groupher_server/cms/helper/matcher2.ex +++ b/lib/groupher_server/cms/helper/matcher_macros.ex @@ -1,11 +1,11 @@ -defmodule GroupherServer.CMS.Helper.Matcher2.Macros do +defmodule GroupherServer.CMS.Helper.MatcherMacros do @moduledoc """ generate match functions """ import Helper.Utils, only: [get_config: 2] alias GroupherServer.CMS - alias CMS.{ArticleComment, Community, Embeds} + alias CMS.{ArticleComment, Embeds} @article_threads get_config(:article, :article_threads) @@ -92,41 +92,3 @@ defmodule GroupherServer.CMS.Helper.Matcher2.Macros do end) end end - -defmodule GroupherServer.CMS.Helper.Matcher2 do - @moduledoc """ - this module defined the matches and handy guard ... - """ - - import Ecto.Query, warn: false - import GroupherServer.CMS.Helper.Matcher2.Macros - - alias GroupherServer.{Accounts, CMS} - - alias Accounts.User - alias CMS.{ArticleComment} - - def match(:account) do - {:ok, - %{ - model: User, - foreign_key: :account_id, - preload: :account, - default_meta: Accounts.Embeds.UserMeta.default_meta() - }} - end - - def match(:article_comment) do - {:ok, - %{ - model: ArticleComment, - foreign_key: :article_comment_id, - preload: :article_comment, - default_meta: CMS.Embeds.ArticleCommentMeta.default_meta() - }} - end - - thread_matches() - thread_query_matches() - comment_article_matches() -end diff --git a/lib/groupher_server/cms/helper/matcher_old.ex b/lib/groupher_server/cms/helper/matcher_old.ex new file mode 100644 index 000000000..2db7c21bb --- /dev/null +++ b/lib/groupher_server/cms/helper/matcher_old.ex @@ -0,0 +1,75 @@ +defmodule GroupherServer.CMS.Helper.MatcherOld do + @moduledoc """ + this module defined the matches and handy guard ... + """ + import Ecto.Query, warn: false + + alias GroupherServer.CMS.{ + Community, + # threads + Post, + Repo, + Job, + # viewer + # reactions + # comments + PostComment, + # commtnes reaction + PostCommentLike, + # + Community + } + + ######################################### + ## posts ... + ######################################### + def match_action(:post, :self), + do: {:ok, %{target: Post, reactor: Post, preload: :author}} + + def match_action(:post, :community), + do: {:ok, %{target: Post, reactor: Community}} + + def match_action(:post, :comment), + do: {:ok, %{target: Post, reactor: PostComment, preload: :author}} + + def match_action(:post_comment, :like), + do: {:ok, %{target: PostComment, reactor: PostCommentLike}} + + ######################################### + ## jobs ... + ######################################### + def match_action(:job, :self), + do: {:ok, %{target: Job, reactor: Job, preload: :author}} + + def match_action(:job, :community), + do: {:ok, %{target: Job, reactor: Community}} + + ######################################### + ## repos ... + ######################################### + def match_action(:repo, :self), + do: {:ok, %{target: Repo, reactor: Repo, preload: :author}} + + def match_action(:repo, :community), + do: {:ok, %{target: Repo, reactor: Community}} + + # dynamic where query match + def dynamic_where(thread, id) do + case thread do + :post -> + {:ok, dynamic([p], p.post_id == ^id)} + + :post_comment -> + {:ok, dynamic([p], p.post_comment_id == ^id)} + + :job -> + {:ok, dynamic([p], p.job_id == ^id)} + + :repo -> + {:ok, dynamic([p], p.repo_id == ^id)} + + _ -> + {:error, 'where is not match'} + end + end +end diff --git a/lib/groupher_server_web/middleware/passport_loader.ex b/lib/groupher_server_web/middleware/passport_loader.ex index 7d5b5aad1..dfe985490 100644 --- a/lib/groupher_server_web/middleware/passport_loader.ex +++ b/lib/groupher_server_web/middleware/passport_loader.ex @@ -4,7 +4,7 @@ defmodule GroupherServerWeb.Middleware.PassportLoader do """ @behaviour Absinthe.Middleware - import GroupherServer.CMS.Helper.Matcher + import GroupherServer.CMS.Helper.MatcherOld import Helper.Utils import Helper.ErrorCode import ShortMaps From f1ce81dab163b4c4829072e9794cee919957e6d9 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Thu, 20 May 2021 10:33:14 +0800 Subject: [PATCH 03/23] refactor(community): wip --- cover/excoveralls.json | 2 +- lib/groupher_server/cms/cms.ex | 2 + lib/groupher_server/cms/community.ex | 4 ++ .../cms/delegates/article_comment.ex | 14 +++--- .../cms/delegates/community_curd.ex | 40 ++++++++++++++--- .../cms/embeds/community_meta.ex | 43 ++++++++++++++++++ .../{account_misc.ex => account_metrics.ex} | 2 +- .../schema/account/account_types.ex | 2 +- .../cms/{cms_misc.ex => cms_metrics.ex} | 2 +- .../schema/cms/cms_types.ex | 3 +- .../20210519142252_add_meta_to_community.exs | 9 ++++ .../cms/community/community_meta_test.exs | 44 +++++++++++++++++++ .../query/cms/cms_test.exs | 4 +- .../cms/paged_articles/paged_jobs_test.exs | 2 - .../cms/paged_articles/paged_repos_test.exs | 2 - 15 files changed, 151 insertions(+), 24 deletions(-) create mode 100644 lib/groupher_server/cms/embeds/community_meta.ex rename lib/groupher_server_web/schema/account/{account_misc.ex => account_metrics.ex} (98%) rename lib/groupher_server_web/schema/cms/{cms_misc.ex => cms_metrics.ex} (99%) create mode 100644 priv/repo/migrations/20210519142252_add_meta_to_community.exs create mode 100644 test/groupher_server/cms/community/community_meta_test.exs diff --git a/cover/excoveralls.json b/cover/excoveralls.json index 560df5dbf..341b46ed5 100644 --- a/cover/excoveralls.json +++ b/cover/excoveralls.json @@ -1 +1 @@ -{"source_files":[{"coverage":[null,null,null,null,null,null,null,null,null,8,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null,null,null,null],"name":"lib/groupher_server/accounts/github_user.ex","source":"defmodule GroupherServer.Accounts.GithubUser do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @type t :: %GithubUser{}\n schema \"github_users\" do\n belongs_to(:user, User)\n\n field(:github_id, :string)\n field(:login, :string)\n field(:avatar_url, :string)\n field(:url, :string)\n field(:html_url, :string)\n field(:name, :string)\n field(:company, :string)\n field(:blog, :string)\n field(:location, :string)\n field(:email, :string)\n field(:bio, :string)\n field(:public_repos, :integer)\n field(:public_gists, :integer)\n field(:followers, :integer)\n field(:following, :integer)\n field(:access_token, :string)\n field(:node_id, :string)\n\n timestamps(type: :utc_datetime)\n end\n\n # @required_fields ~w(github_id login name avatar_url)a\n @required_fields ~w(github_id login avatar_url user_id access_token node_id)a\n @optional_fields ~w(blog company email bio followers following location html_url public_repos public_gists)a\n\n @doc false\n def changeset(%GithubUser{} = github_user, attrs) do\n # |> cast(attrs, [:username, :nickname, :bio, :company])\n # |> validate_required([:username])\n # |> cast(attrs, @required_fields, @optional_fields)\n github_user\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:github_id)\n |> unique_constraint(:node_id)\n |> foreign_key_constraint(:user_id)\n\n # |> validate_length(:username, max: 20)\n # |> unique_constraint(:username)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,2,null,1,1,null,null,null,null,null,null,null,1,1,1,null,null,null,null,3,1,1,null,null,null,null,null,null,null,null,null,null,4,null,null,null,null,null,null,5,null,5,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/accounts/delegates/billing.ex","source":"defmodule GroupherServer.Accounts.Delegate.Billing do\n @moduledoc \"\"\"\n user billings related\n \"\"\"\n import Ecto.Query, warn: false\n\n alias Helper.ORM\n alias GroupherServer.Accounts.{Purchase, User}\n\n # ...\n def purchase_service(%User{} = _user, map) when map_size(map) == 0 do\n {:error, \"AccountPurchase: invalid option or not purchased\"}\n end\n\n def purchase_service(%User{} = user, map) when is_map(map) do\n valid? = map |> Map.keys() |> Enum.all?(&can_purchase?(user, &1, :boolean))\n\n case valid? do\n true ->\n attrs = Map.merge(%{user_id: user.id}, map)\n Purchase |> ORM.upsert_by([user_id: user.id], attrs)\n\n false ->\n {:error, \"AccountCustomization: invalid option or not purchased\"}\n end\n end\n\n def purchase_service(%User{} = user, key, value \\\\ true) do\n with {:ok, key} <- can_purchase?(user, key) do\n attrs = Map.put(%{user_id: user.id}, key, value)\n Purchase |> ORM.upsert_by([user_id: user.id], attrs)\n end\n end\n\n def has_purchased?(%User{} = user, key) do\n with {:ok, purchase} <- Purchase |> ORM.find_by(user_id: user.id),\n value <- purchase |> Map.get(key) do\n case value do\n true -> {:ok, key}\n false -> {:error, \"AccountPurchase: not purchase\"}\n end\n else\n nil -> {:error, \"AccountPurchase: not purchase\"}\n _ -> {:error, \"AccountPurchase: not purchase\"}\n end\n end\n\n defp can_purchase?(%User{} = user, key, :boolean) do\n case can_purchase?(%User{} = user, key) do\n {:ok, _} -> true\n {:error, _} -> false\n end\n end\n\n defp can_purchase?(%User{} = _user, key) do\n valid_service_options = valid_service()\n\n case key in valid_service_options do\n true -> {:ok, key}\n false -> {:error, \"AccountPurchase: purchase invalid service\"}\n end\n end\n\n defp valid_service do\n [:brainwash_free, :community_chart]\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,29,null,null,null,null,null,null,null,null,null,null,null,null,10,null,null],"name":"lib/groupher_server/statistics/user_contribute.ex","source":"defmodule GroupherServer.Statistics.UserContribute do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n @type t :: %UserContribute{}\n schema \"user_contributes\" do\n field(:count, :integer)\n field(:date, :date)\n belongs_to(:user, Accounts.User)\n\n timestamps()\n end\n\n @doc false\n def changeset(%UserContribute{} = user_contribute, attrs) do\n user_contribute\n |> cast(attrs, [:date, :count, :user_id])\n |> validate_required([:date, :count, :user_id])\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,77,84,null,null,6,null,3,22,2,3,2,0,null,4,4,4,null,null,null],"name":"lib/helper/error_code.ex","source":"defmodule Helper.ErrorCode do\n @moduledoc \"\"\"\n error code map for all site\n \"\"\"\n @default_base 4000\n @account_base 4300\n @changeset_base 4100\n @throttle_base 4200\n\n # account error code\n def ecode(:account_login), do: @account_base + 1\n def ecode(:passport), do: @account_base + 2\n # ...\n # changeset error code\n def ecode(:changeset), do: @changeset_base + 2\n # ...\n def ecode(:custom), do: @default_base + 1\n def ecode(:pagination), do: @default_base + 2\n def ecode(:not_exsit), do: @default_base + 3\n def ecode(:already_did), do: @default_base + 4\n def ecode(:self_conflict), do: @default_base + 5\n def ecode(:react_fails), do: @default_base + 6\n # throttle\n def ecode(:throttle_inverval), do: @throttle_base + 1\n def ecode(:throttle_hour), do: @throttle_base + 2\n def ecode(:throttle_day), do: @throttle_base + 3\n def ecode, do: @default_base\n # def ecode(_), do: @default_base\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,102,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,6,null,null,null,null],"name":"lib/groupher_server/cms/thread.ex","source":"defmodule GroupherServer.CMS.Thread do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n @optional_fields ~w(logo index)a\n @required_fields ~w(title raw)a\n\n @type t :: %Thread{}\n schema \"threads\" do\n field(:title, :string)\n field(:raw, :string)\n field(:logo, :string)\n field(:index, :integer)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Thread{} = thread, attrs) do\n thread\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> validate_length(:title, min: 2, max: 20)\n |> validate_length(:raw, min: 2, max: 20)\n |> unique_constraint(:title)\n\n # |> unique_constraint(:raw)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,29,null,null,29,29,29,29,null,null,null,null,29,null,null,null,null,null,null,31,null,null],"name":"lib/groupher_server/delivery/delegates/mentions.ex","source":"defmodule GroupherServer.Delivery.Delegate.Mentions do\n @moduledoc \"\"\"\n The Delivery context.\n \"\"\"\n import Helper.Utils, only: [done: 2]\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.Delivery.Mention\n alias Helper.ORM\n\n alias GroupherServer.Delivery.Delegate.Utils\n\n def mention_someone(%User{id: from_user_id}, %User{id: to_user_id}, info) do\n attrs = %{\n from_user_id: from_user_id,\n to_user_id: to_user_id,\n source_id: info.source_id,\n source_title: info.source_title,\n source_type: info.source_type,\n source_preview: info.source_preview\n }\n\n Mention\n |> ORM.create(attrs)\n |> done(:status)\n end\n\n @doc \"\"\"\n fetch mentions from Delivery stop\n \"\"\"\n def fetch_mentions(%User{} = user, %{page: _, size: _, read: _} = filter) do\n Utils.fetch_messages(user, Mention, filter)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,0,null,0,0,null,null,null,null,null],"name":"test/support/channel_case.ex","source":"defmodule GroupherServerWeb.ChannelCase do\n @moduledoc \"\"\"\n This module defines the test case to be used by\n channel tests.\n\n Such tests rely on `Phoenix.ChannelTest` and also\n import other functionality to make it easier\n to build common datastructures and query the data layer.\n\n Finally, if the test case interacts with the database,\n it cannot be async. For this reason, every test runs\n inside a transaction which is reset at the beginning\n of the test unless the test case is marked as async.\n \"\"\"\n\n use ExUnit.CaseTemplate\n\n using do\n quote do\n # Import conveniences for testing with channels\n use Phoenix.ChannelTest\n\n # The default endpoint for testing\n @endpoint GroupherServerWeb.Endpoint\n end\n end\n\n setup tags do\n :ok = Ecto.Adapters.SQL.Sandbox.checkout(GroupherServer.Repo)\n\n unless tags[:async] do\n Ecto.Adapters.SQL.Sandbox.mode(GroupherServer.Repo, {:shared, self()})\n end\n\n :ok\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,0,null,null,361,null,null,361,null,null,null,null,null,null,null,null,null,null,361,203,203,null,158,null,null,null,null,203,203,null,203,null,null,null,null,null,null,null,null,null,null,203,null,null,null,null,null,null],"name":"lib/groupher_server_web/context.ex","source":"# a plug for router ...\n\ndefmodule GroupherServerWeb.Context do\n @behaviour Plug\n\n import Plug.Conn\n # import Ecto.Query, only: [first: 1]\n\n alias GroupherServer.{Accounts, CMS}\n alias Helper.{Guardian, ORM}\n\n def init(opts), do: opts\n\n def call(conn, _) do\n context = build_context(conn)\n # put_private(conn, :absinthe, %{context: context})\n # TODO: use https://github.com/absinthe-graphql/absinthe/pull/497/files\n Absinthe.Plug.put_options(conn, context: context)\n end\n\n @doc \"\"\"\n Return the current user context based on the authorization header.\n\n Important: Note that at the current time this is just a stub, always\n returning the first user (marked as an admin), provided any\n authorization header is sent.\n \"\"\"\n def build_context(conn) do\n with [\"Bearer \" <> token] <- get_req_header(conn, \"authorization\"),\n {:ok, cur_user} <- authorize(token) do\n %{cur_user: cur_user}\n else\n _ -> %{}\n end\n end\n\n defp authorize(token) do\n with {:ok, claims, _info} <- Guardian.jwt_decode(token) do\n case ORM.find(Accounts.User, claims.id) do\n {:ok, user} ->\n check_passport(user)\n\n {:error, _} ->\n {:error,\n \"user is not exsit, try revoke token, or if you in dev env run the seeds first.\"}\n end\n end\n end\n\n # TODO gather role info from CMS or other context\n defp check_passport(%Accounts.User{} = user) do\n with {:ok, cms_passport} <- CMS.get_passport(%Accounts.User{id: user.id}) do\n {:ok, Map.put(user, :cur_passport, %{\"cms\" => cms_passport})}\n else\n {:error, _} -> {:ok, user}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1,null,7,null,9,3,null,6,null,7,1,2,1,null,null,null,8,2,null,59,1,null,8,1,null,76,2,null,null,44,30,9,null,null,139,5,null,null,null,28,null,10,1,null,5,1,null,null,197,5,11,3,9,null,null,null,20,2,null,14,2,null,null,80,4,211,3,0,null],"name":"lib/groupher_server/cms/cms.ex","source":"defmodule GroupherServer.CMS do\n @moduledoc \"\"\"\n this module defined basic method to handle [CMS] content [CURD] ..\n [CMS]: post, job, ...\n [CURD]: create, update, delete ...\n \"\"\"\n alias GroupherServer.CMS.Delegate.{\n ArticleCURD,\n ArticleCommunity,\n ArticleReaction,\n CommentCURD,\n CommentReaction,\n CommunityCURD,\n CommunityOperation,\n PassportCURD\n }\n\n # do not pattern match in delegating func, do it on one delegating inside\n # see https://github.com/elixir-lang/elixir/issues/5306\n\n # Community CURD: editors, thread, tag\n # >> editor ..\n defdelegate update_editor(user, community, title), to: CommunityCURD\n # >> subscribers / editors\n defdelegate community_members(type, community, filters), to: CommunityCURD\n # >> category\n defdelegate create_category(category_attrs, user), to: CommunityCURD\n defdelegate update_category(category_attrs), to: CommunityCURD\n # >> thread\n defdelegate create_thread(attrs), to: CommunityCURD\n # >> tag\n defdelegate create_tag(thread, attrs, user), to: CommunityCURD\n defdelegate update_tag(attrs), to: CommunityCURD\n defdelegate get_tags(community, thread), to: CommunityCURD\n defdelegate get_tags(filter), to: CommunityCURD\n\n # CommunityOperation\n # >> category\n defdelegate set_category(community, category), to: CommunityOperation\n defdelegate unset_category(community, category), to: CommunityOperation\n # >> editor\n defdelegate set_editor(community, title, user), to: CommunityOperation\n defdelegate unset_editor(community, user), to: CommunityOperation\n # >> thread\n defdelegate set_thread(community, thread), to: CommunityOperation\n defdelegate unset_thread(community, thread), to: CommunityOperation\n # >> subscribe / unsubscribe\n defdelegate subscribe_community(community, user), to: CommunityOperation\n defdelegate unsubscribe_community(community, user), to: CommunityOperation\n\n # ArticleCURD\n defdelegate paged_contents(queryable, filter), to: ArticleCURD\n defdelegate create_article(community, thread, attrs, user), to: ArticleCURD\n defdelegate reaction_users(thread, react, id, filters), to: ArticleCURD\n\n # ArticleReaction\n defdelegate reaction(thread, react, content_id, user), to: ArticleReaction\n defdelegate undo_reaction(thread, react, content_id, user), to: ArticleReaction\n\n # ArticleCommunity\n # >> set flag on article, like: pin / unpin article\n defdelegate set_flag(queryable, id, attrs, user), to: ArticleCommunity\n # >> tag: set / unset\n defdelegate set_tag(community, thread, tag, content_id), to: ArticleCommunity\n defdelegate unset_tag(thread, tag, content_id), to: ArticleCommunity\n # >> community: set / unset\n defdelegate mirror_article(community, thread, content_id), to: ArticleCommunity\n defdelegate unmirror_article(community, thread, content_id), to: ArticleCommunity\n\n # Comment CURD\n defdelegate create_comment(thread, content_id, body, user), to: CommentCURD\n defdelegate delete_comment(thread, content_id), to: CommentCURD\n defdelegate paged_comments(thread, content_id, filters), to: CommentCURD\n defdelegate paged_replies(thread, comment, user), to: CommentCURD\n defdelegate reply_comment(thread, comment, body, user), to: CommentCURD\n\n # Comment Reaction\n # >> like / undo like\n defdelegate like_comment(thread, comment, user), to: CommentReaction\n defdelegate undo_like_comment(thread, comment, user), to: CommentReaction\n # >> dislike / undo dislike\n defdelegate dislike_comment(thread, comment, user), to: CommentReaction\n defdelegate undo_dislike_comment(thread, comment, user), to: CommentReaction\n\n # Passport CURD\n defdelegate stamp_passport(rules, user), to: PassportCURD\n defdelegate erase_passport(rules, user), to: PassportCURD\n defdelegate get_passport(user), to: PassportCURD\n defdelegate paged_passports(community, key), to: PassportCURD\n defdelegate delete_passport(user), to: PassportCURD\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,115,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,6,null,null,null,null],"name":"lib/groupher_server/cms/tag.ex","source":"defmodule GroupherServer.CMS.Tag do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, Job, Post, Video}\n\n @required_fields ~w(thread title color author_id community_id)a\n\n @type t :: %Tag{}\n schema \"tags\" do\n field(:title, :string)\n field(:color, :string)\n field(:thread, :string)\n belongs_to(:community, Community)\n belongs_to(:author, Author)\n\n many_to_many(\n :posts,\n Post,\n join_through: \"posts_tags\",\n join_keys: [post_id: :id, tag_id: :id],\n # :delete_all will only remove data from the join source\n on_delete: :delete_all\n # on_replace: :delete\n )\n\n many_to_many(\n :videos,\n Video,\n join_through: \"videos_tags\",\n join_keys: [video_id: :id, tag_id: :id]\n )\n\n many_to_many(\n :jobs,\n Job,\n join_through: \"jobs_tags\",\n join_keys: [job_id: :id, tag_id: :id]\n )\n\n timestamps(type: :utc_datetime)\n end\n\n def changeset(%Tag{} = tag, attrs) do\n tag\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> foreign_key_constraint(:community_id)\n |> unique_constraint(:tag_duplicate, name: :tags_community_id_thread_title_index)\n\n # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,107,null,null,null,null,null,null,null,null,null,null,null,null,null,97,null,null],"name":"lib/groupher_server/accounts/user_follower.ex","source":"defmodule GroupherServer.Accounts.UserFollower do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id follower_id)a\n\n @type t :: %UserFollower{}\n schema \"users_followers\" do\n belongs_to(:user, User, foreign_key: :user_id)\n belongs_to(:follower, User, foreign_key: :follower_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%UserFollower{} = user_follower, attrs) do\n user_follower\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> foreign_key_constraint(:follower_id)\n |> unique_constraint(:user_id, name: :users_followers_user_id_follower_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,108,null,null,null,223,null,null,null,null,null,null,null,253,null,253,null,null,null,null,null,null,null,null,null,1407,null,null,null,null,null,null,null,null,1232,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,103,null,null,null,null,null,null,null,0,null,null,null,null,null,null,17,17,null,null,null,null,17,null,17,null,null,null,null,17,null,null,null,null,null,null,32,null,null,5,4,null,null,null,null,19,18,null,null,null,null,252,null,null,null,null,180,null,null,null,null,null,null,null,null,null,310,310,null,null,null,null,null,28,3,null,null,31,null,31,31,null,null,null,null,null,null,null,1,null,null,1,null,null,null,null,32,null,null,1,1,null,null,31,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,977,null,null,null,null,null,null,null,null,null,209,209,null,null,null,205,null,null],"name":"lib/helper/orm.ex","source":"defmodule Helper.ORM do\n @moduledoc \"\"\"\n General CORD functions\n \"\"\"\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1, done: 3, add: 1]\n import Helper.ErrorHandler\n import ShortMaps\n\n alias Helper.{QueryBuilder, SpecType}\n alias GroupherServer.Repo\n\n @doc \"\"\"\n a wrap for paginate request\n \"\"\"\n def paginater(queryable, page: page, size: size) do\n queryable |> Repo.paginate(page: page, page_size: size)\n end\n\n def paginater(queryable, ~m(page size)a) do\n queryable |> Repo.paginate(page: page, page_size: size)\n end\n\n @doc \"\"\"\n wrap Repo.get with preload and result/errer format handle\n \"\"\"\n def find(queryable, id, preload: preload) do\n queryable\n |> preload(^preload)\n |> Repo.get(id)\n |> done(queryable, id)\n end\n\n @doc \"\"\"\n simular to Repo.get/3, with standard result/error handle\n \"\"\"\n @spec find(Ecto.Queryable.t(), SpecType.id()) :: {:ok, any()} | {:error, String.t()}\n def find(queryable, id) do\n queryable\n |> Repo.get(id)\n |> done(queryable, id)\n end\n\n @doc \"\"\"\n simular to Repo.get_by/3, with standard result/error handle\n \"\"\"\n def find_by(queryable, clauses) do\n queryable\n |> Repo.get_by(clauses)\n |> case do\n nil ->\n {:error, not_found_formater(queryable, clauses)}\n\n result ->\n {:ok, result}\n end\n end\n\n @doc \"\"\"\n return pageinated Data required by filter\n \"\"\"\n # TODO: find content not in markDelete by default\n def find_all(queryable, %{page: page, size: size} = filter) do\n queryable\n |> QueryBuilder.filter_pack(filter)\n |> paginater(page: page, size: size)\n |> done()\n end\n\n @doc \"\"\"\n return Data required by filter\n \"\"\"\n # TODO: find content not in markDelete by default\n def find_all(queryable, filter) do\n queryable |> QueryBuilder.filter_pack(filter) |> Repo.all() |> done()\n end\n\n @doc \"\"\"\n Require queryable has a views fields to count the views of the queryable Modal\n \"\"\"\n def read(queryable, id, inc: :views) do\n with {:ok, result} <- find(queryable, id) do\n result |> inc_views_count(queryable) |> done()\n end\n end\n\n defp inc_views_count(content, queryable) do\n {1, [result]} =\n Repo.update_all(\n from(p in queryable, where: p.id == ^content.id),\n [inc: [views: 1]],\n returning: [:views]\n )\n\n put_in(content.views, result.views)\n end\n\n @doc \"\"\"\n NOTICE: this should be use together with Authorize/OwnerCheck etc Middleware\n DO NOT use it directly\n \"\"\"\n def delete(content), do: Repo.delete(content)\n\n def find_delete(queryable, id) do\n with {:ok, content} <- find(queryable, id) do\n delete(content)\n end\n end\n\n def findby_delete(queryable, clauses) do\n with {:ok, content} <- find_by(queryable, clauses) do\n delete(content)\n end\n end\n\n def findby_or_insert(queryable, clauses, attrs) do\n case queryable |> find_by(clauses) do\n {:ok, content} ->\n {:ok, content}\n\n {:error, _} ->\n queryable |> create(attrs)\n end\n end\n\n @doc \"\"\"\n NOTE: this should be use together with passport_loader etc Middleware\n DO NOT use it directly\n \"\"\"\n def update(content, attrs) do\n content\n |> content.__struct__.changeset(attrs)\n |> Repo.update()\n end\n\n @doc \"\"\"\n find and update sourc\n \"\"\"\n def find_update(queryable, id, attrs), do: do_find_update(queryable, id, attrs)\n def find_update(queryable, %{id: id} = attrs), do: do_find_update(queryable, id, attrs)\n\n defp do_find_update(queryable, id, attrs) do\n with {:ok, content} <- find(queryable, id) do\n content\n |> content.__struct__.changeset(attrs)\n |> Repo.update()\n end\n end\n\n @doc \"\"\"\n find then update\n \"\"\"\n def update_by(source, clauses, attrs) do\n with {:ok, content} <- find_by(source, clauses) do\n content\n |> Ecto.Changeset.change(attrs)\n |> Repo.update()\n end\n end\n\n def upsert_by(queryable, clauses, attrs) do\n case queryable |> find_by(clauses) do\n {:ok, content} ->\n content\n |> content.__struct__.changeset(attrs)\n |> Repo.update()\n\n {:error, _} ->\n queryable |> create(attrs)\n end\n end\n\n @doc \"\"\"\n see https://elixirforum.com/t/ecto-inc-dec-update-one-helpers/5564\n \"\"\"\n # def update_one(queryable, where, changes) do\n # query |> Ecto.Query.where(^where) |> Repo.update_all(set: changes)\n # end\n\n # def inc(queryable, where, changes) do\n # query |> Ecto.Query.where(^where) |> Repo.update_all(inc: changes)\n # end\n\n def create(model, attrs) do\n model\n |> struct\n |> model.changeset(attrs)\n |> Repo.insert()\n end\n\n @doc \"\"\"\n return the total count of a Modal based on id column\n also support filters\n \"\"\"\n def count(queryable, filter \\\\ %{}) do\n queryable\n |> QueryBuilder.filter_pack(filter)\n |> select([f], count(f.id))\n |> Repo.one()\n end\n\n def next_count(queryable) do\n queryable |> count() |> add()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,20,null,20,null,null,null,628,null,628,null,null,null,null,628,null,628,null,null],"name":"lib/helper/error_handler.ex","source":"defmodule Helper.ErrorHandler do\n @moduledoc \"\"\"\n This module defines some helper function used by\n handle/format changset errors\n \"\"\"\n alias GroupherServerWeb.Gettext, as: Translator\n\n def not_found_formater(queryable, id) when is_integer(id) or is_binary(id) do\n model = queryable |> to_string |> String.split(\".\") |> List.last()\n\n Translator |> Gettext.dgettext(\"404\", \"#{model}(%{id}) not found\", id: id)\n end\n\n def not_found_formater(queryable, clauses) do\n model = queryable |> to_string |> String.split(\".\") |> List.last()\n\n detail =\n clauses\n |> Enum.into(%{})\n |> Map.values()\n |> List.first()\n |> to_string\n\n Translator |> Gettext.dgettext(\"404\", \"#{model}(%{name}) not found\", name: detail)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,0,null,null,17,null,null,null,58,null,null,0,null],"name":"lib/groupher_server_web/middleware/covert_to_int.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.ConvertToInt do\n @behaviour Absinthe.Middleware\n # google: must appear in the GROUP BY clause or be used in an aggregate function\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(%{value: [value]} = resolution, _) do\n %{resolution | value: value}\n end\n\n def call(%{value: []} = resolution, _) do\n %{resolution | value: 0}\n end\n\n def call(resolution, _), do: resolution\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,44,null,null,null,44,null,null,null,null,44,44,38,38,null,38,null,35,null,null,3,3,null,3,3,null,null,null,null,3,null,null,null,6,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,30,30,30,26,26,null,26,26,null,26,26,null,null,null,null,null,null,null,null,null,null,null,null,null,9,9,null,9,null,null,null,9,null,null,null,null,null,null,null,46,null,null,null,null,46,null,null,null,null,null,7,null,null],"name":"lib/groupher_server/cms/delegates/article_curd.ex","source":"defmodule GroupherServer.CMS.Delegate.ArticleCURD do\n import Ecto.Query, warn: false\n import GroupherServer.CMS.Helper.MatcherOld\n import Helper.Utils, only: [done: 1]\n import ShortMaps\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.{Repo, CMS, Statistics}\n alias GroupherServer.CMS.Delegate.ArticleCommunity\n alias Helper.{ORM, QueryBuilder}\n\n alias CMS.{Author, Community}\n\n @doc \"\"\"\n get paged post / job ...\n \"\"\"\n def paged_contents(queryable, filter) do\n normal_content_fr = filter |> Map.merge(QueryBuilder.default_article_filters())\n\n queryable\n |> ORM.find_all(normal_content_fr)\n |> add_pin_contents_ifneed(queryable, filter)\n end\n\n # only first page need pin contents\n defp add_pin_contents_ifneed(contents, queryable, filter) do\n with {:ok, normal_contents} <- contents,\n true <- 1 == Map.get(normal_contents, :page_number) do\n pin_content_fr = filter |> Map.merge(%{pin: true})\n {:ok, pined_content} = queryable |> ORM.find_all(pin_content_fr)\n\n case pined_content |> Map.get(:total_count) do\n 0 ->\n contents\n\n _ ->\n pind_entries = pined_content |> Map.get(:entries)\n normal_entries = normal_contents |> Map.get(:entries)\n\n normal_count = normal_contents |> Map.get(:total_count)\n pind_count = pined_content |> Map.get(:total_count)\n\n normal_contents\n |> Map.put(:entries, pind_entries ++ normal_entries)\n |> Map.put(:total_count, pind_count + normal_count)\n |> done\n end\n else\n _error ->\n contents\n end\n end\n\n @doc \"\"\"\n Creates a content(post/job ...), and set community.\n\n ## Examples\n\n iex> create_post(%{field: value})\n {:ok, %Post{}}\n\n iex> create_post(%{field: bad_value})\n {:error, %Ecto.Changeset{}}\n\n \"\"\"\n def create_article(%Community{id: community_id}, thread, attrs, %User{id: user_id}) do\n with {:ok, author} <- ensure_author_exists(%User{id: user_id}),\n {:ok, action} <- match_action(thread, :community),\n {:ok, community} <- ORM.find(Community, community_id),\n {:ok, content} <-\n action.target\n |> struct()\n |> action.target.changeset(attrs)\n |> Ecto.Changeset.put_change(:author_id, author.id)\n |> Repo.insert() do\n Statistics.log_publish_action(%User{id: user_id})\n ArticleCommunity.mirror_article(community, thread, content.id)\n end\n end\n\n @doc \"\"\"\n get CMS contents\n post's favorites/stars/comments ...\n ...\n jobs's favorites/stars/comments ...\n\n with or without page info\n \"\"\"\n def reaction_users(thread, react, id, %{page: page, size: size} = filters) do\n # when valid_reaction(thread, react) do\n with {:ok, action} <- match_action(thread, react),\n {:ok, where} <- dynamic_where(thread, id) do\n # common_filter(action.reactor)\n action.reactor\n |> where(^where)\n |> QueryBuilder.load_inner_users(filters)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n end\n\n def ensure_author_exists(%User{} = user) do\n # unique_constraint: avoid race conditions, make sure user_id unique\n # foreign_key_constraint: check foreign key: user_id exsit or not\n # see alos no_assoc_constraint in https://hexdocs.pm/ecto/Ecto.Changeset.html\n %Author{user_id: user.id}\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.unique_constraint(:user_id)\n |> Ecto.Changeset.foreign_key_constraint(:user_id)\n |> Repo.insert()\n |> handle_existing_author()\n end\n\n defp handle_existing_author({:ok, author}), do: {:ok, author}\n\n defp handle_existing_author({:error, changeset}) do\n ORM.find_by(Author, user_id: changeset.data.user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/accounts/user_bill.ex","source":"defmodule GroupherServer.Accounts.UserBill do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.{Bill, User}\n\n @required_fields ~w(user_id bill_id)a\n\n @type t :: %UserBill{}\n schema \"users_bills\" do\n belongs_to(:user, User, foreign_key: :user_id)\n belongs_to(:bill, Bill, foreign_key: :bill_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%UserBill{} = user_bill, attrs) do\n user_bill\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> foreign_key_constraint(:bill_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,0,null,0,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,0,0,null,null,null,null],"name":"test/support/data_case.ex","source":"defmodule GroupherServer.DataCase do\n @moduledoc \"\"\"\n This module defines the setup for tests requiring\n access to the application's data layer.\n\n You may define functions here to be used as helpers in\n your tests.\n\n Finally, if the test case interacts with the database,\n it cannot be async. For this reason, every test runs\n inside a transaction which is reset at the beginning\n of the test unless the test case is marked as async.\n \"\"\"\n\n use ExUnit.CaseTemplate\n\n using do\n quote do\n alias GroupherServer.Repo\n\n import Ecto\n import Ecto.Changeset\n import Ecto.Query\n import GroupherServer.DataCase\n end\n end\n\n setup tags do\n :ok = Ecto.Adapters.SQL.Sandbox.checkout(GroupherServer.Repo)\n\n unless tags[:async] do\n Ecto.Adapters.SQL.Sandbox.mode(GroupherServer.Repo, {:shared, self()})\n end\n\n :ok\n end\n\n @doc \"\"\"\n A helper that transform changeset errors to a map of messages.\n\n assert {:error, changeset} = Accounts.create_user(%{password: \"short\"})\n assert \"password is too short\" in errors_on(changeset).password\n assert %{password: [\"password is too short\"]} = errors_on(changeset)\n\n \"\"\"\n def errors_on(changeset) do\n Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->\n Enum.reduce(opts, message, fn {key, value}, acc ->\n String.replace(acc, \"%{#{key}}\", to_string(value))\n end)\n end)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/account/account_queries.ex","source":"defmodule GroupherServerWeb.Schema.Account.Queries do\n @moduledoc \"\"\"\n accounts GraphQL queries\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :account_queries do\n @desc \"get all users\"\n field :paged_users, non_null(:paged_users) do\n arg(:filter, non_null(:paged_users_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.users/3)\n end\n\n @desc \"get user by id\"\n field :user, :user do\n arg(:id, non_null(:id))\n\n resolve(&R.Accounts.user/3)\n end\n\n @desc \"get login-user's info\"\n field :account, :user do\n middleware(M.Authorize, :login)\n\n resolve(&R.Accounts.account/3)\n end\n\n @desc \"anyone can get anyone's subscribed communities\"\n field :subscribed_communities, :paged_communities do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.subscribed_communities/3)\n end\n\n @desc \"get user's follower\"\n field :paged_followers, :paged_users do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.paged_followers/3)\n end\n\n @desc \"get user's follower\"\n field :paged_followings, :paged_users do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.paged_followings/3)\n end\n\n @desc \"get favorited posts\"\n field :favorited_posts, :paged_posts do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.favorited_posts/3)\n end\n\n @desc \"get favorited jobs\"\n field :favorited_jobs, :paged_jobs do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.favorited_jobs/3)\n end\n\n @desc \"get all passport rules include system and community etc ...\"\n field :all_passport_rules_string, :rules do\n middleware(M.Authorize, :login)\n\n resolve(&R.Accounts.get_all_rules/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/logs/user_activity.ex","source":"defmodule GroupherServer.Logs.UserActivity do\n @moduledoc false\n # alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id source_title source_id source_type)a\n # @optional_fields ~w(source_type)a\n\n schema \"user_activity_logs\" do\n belongs_to(:user, User)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(user_activity, attrs) do\n user_activity\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,42,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/notification.mail.ex","source":"defmodule GroupherServer.Accounts.NotificationMail do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id action source_id source_type source_preview)a\n @optional_fields ~w(parent_id parent_type read)a\n\n @type t :: %NotificationMail{}\n schema \"notification_mails\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n field(:action, :string)\n\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:parent_id, :string)\n field(:parent_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%NotificationMail{} = notication_mail, attrs) do\n notication_mail\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,51,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"test/support/test_tools.ex","source":"defmodule GroupherServer.TestTools do\n @moduledoc \"\"\"\n helper for reduce import mudules in test files\n \"\"\"\n use ExUnit.CaseTemplate\n\n using do\n quote do\n use GroupherServerWeb.ConnCase, async: true\n\n import GroupherServer.Factory\n import GroupherServer.Test.ConnSimulator\n import GroupherServer.Test.AssertHelper\n import Ecto.Query, warn: false\n import Helper.ErrorCode\n\n import ShortMaps\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,8,null,null,null,null,null,null,null,2,null,2,null,null,null,null,null,null,null,8,8,null,null,null,null,null,null,null,1,null,1,null,null,null,null,null,null,null,null,null,null,null,null,null,59,59,null,null,59,null,null,null,null,null,null,1,1,1,null,null,null,null,59,null,null,null,null,null,null,null,null,null,null,null,null,76,75,null,null,null,null,2,null,1,null,null,null],"name":"lib/groupher_server/cms/delegates/community_operation.ex","source":"defmodule GroupherServer.CMS.Delegate.CommunityOperation do\n @moduledoc \"\"\"\n community operations, like: set/unset category/thread/editor...\n \"\"\"\n import ShortMaps\n\n alias Ecto.Multi\n alias Helper.{Certification, ORM}\n alias GroupherServer.Accounts.User\n alias GroupherServer.CMS.Delegate.PassportCURD\n alias GroupherServer.Repo\n\n alias GroupherServer.CMS.{\n Category,\n Community,\n CommunityCategory,\n CommunityEditor,\n CommunitySubscriber,\n CommunityThread,\n Thread\n }\n\n @doc \"\"\"\n set a category to community\n \"\"\"\n def set_category(%Community{id: community_id}, %Category{id: category_id}) do\n with {:ok, community_category} <-\n CommunityCategory |> ORM.create(~m(community_id category_id)a) do\n Community |> ORM.find(community_category.community_id)\n end\n end\n\n @doc \"\"\"\n unset a category to community\n \"\"\"\n def unset_category(%Community{id: community_id}, %Category{id: category_id}) do\n with {:ok, community_category} <-\n CommunityCategory |> ORM.findby_delete!(~m(community_id category_id)a) do\n Community |> ORM.find(community_category.community_id)\n end\n end\n\n @doc \"\"\"\n set to thread to a community\n \"\"\"\n def set_thread(%Community{id: community_id}, %Thread{id: thread_id}) do\n with {:ok, community_thread} <- CommunityThread |> ORM.create(~m(community_id thread_id)a) do\n Community |> ORM.find(community_thread.community_id)\n end\n end\n\n @doc \"\"\"\n unset to thread to a community\n \"\"\"\n def unset_thread(%Community{id: community_id}, %Thread{id: thread_id}) do\n with {:ok, community_thread} <-\n CommunityThread |> ORM.findby_delete!(~m(community_id thread_id)a) do\n Community |> ORM.find(community_thread.community_id)\n end\n end\n\n @doc \"\"\"\n set a community editor\n \"\"\"\n def set_editor(%Community{id: community_id}, title, %User{id: user_id}) do\n Multi.new()\n |> Multi.insert(\n :insert_editor,\n CommunityEditor.changeset(%CommunityEditor{}, ~m(user_id community_id title)a)\n )\n |> Multi.run(:stamp_passport, fn _ ->\n rules = Certification.passport_rules(cms: title)\n PassportCURD.stamp_passport(rules, %User{id: user_id})\n end)\n |> Repo.transaction()\n |> set_editor_result()\n end\n\n @doc \"\"\"\n unset a community editor\n \"\"\"\n def unset_editor(%Community{id: community_id}, %User{id: user_id}) do\n with {:ok, _} <- ORM.findby_delete!(CommunityEditor, ~m(user_id community_id)a),\n {:ok, _} <- PassportCURD.delete_passport(%User{id: user_id}) do\n User |> ORM.find(user_id)\n end\n end\n\n defp set_editor_result({:ok, %{insert_editor: editor}}) do\n User |> ORM.find(editor.user_id)\n end\n\n defp set_editor_result({:error, :stamp_passport, _result, _steps}),\n do: {:error, \"stamp passport error\"}\n\n defp set_editor_result({:error, :insert_editor, _result, _steps}),\n do: {:error, \"insert editor error\"}\n\n @doc \"\"\"\n subscribe a community. (ONLY community, post etc use watch )\n \"\"\"\n def subscribe_community(%Community{id: community_id}, %User{id: user_id}) do\n with {:ok, record} <- CommunitySubscriber |> ORM.create(~m(user_id community_id)a) do\n Community |> ORM.find(record.community_id)\n end\n end\n\n def unsubscribe_community(%Community{id: community_id}, %User{id: user_id}) do\n with {:ok, record} <-\n CommunitySubscriber |> ORM.findby_delete!(community_id: community_id, user_id: user_id) do\n Community |> ORM.find(record.community_id)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null],"name":"lib/groupher_server_web/schema/account/account_misc.ex","source":"defmodule GroupherServerWeb.Schema.Account.Misc do\n use Absinthe.Schema.Notation\n\n import GroupherServerWeb.Schema.Helper.Fields\n # import Helper.Utils, only: [get_config: 2]\n # @page_size get_config(:general, :page_size)\n\n @desc \"article_filter doc\"\n input_object :paged_users_filter do\n pagination_args()\n # field(:when, :when_enum)\n # field(:sort, :sort_enum)\n # field(:article_tag, :string, default_value: :all)\n # field(:community, :string)\n end\n\n input_object :github_profile_input do\n # is github_id in db table\n field(:id, non_null(:string))\n field(:login, non_null(:string))\n field(:avatar_url, non_null(:string))\n field(:url, :string)\n field(:html_url, :string)\n field(:name, :string)\n field(:company, :string)\n field(:blog, :string)\n field(:location, :string)\n field(:email, :string)\n field(:bio, :string)\n field(:public_repos, :integer)\n field(:public_gists, :integer)\n end\n\n input_object :user_profile_input do\n field(:nickname, :string)\n field(:bio, :string)\n field(:sex, :string)\n field(:education, :string)\n field(:location, :string)\n field(:company, :string)\n field(:email, :string)\n field(:qq, :string)\n field(:weibo, :string)\n field(:weichat, :string)\n end\n\n # see: https://github.com/absinthe-graphql/absinthe/issues/206\n # https://github.com/absinthe-graphql/absinthe/wiki/Scalar-Recipes\n scalar :json, name: \"Json\" do\n description(\"\"\"\n The `Json` scalar type represents arbitrary json string data, represented as UTF-8\n character sequences. The Json type is most often used to represent a free-form\n human-readable json string.\n \"\"\")\n\n serialize(&encode/1)\n parse(&decode/1)\n end\n\n @spec decode(Absinthe.Blueprint.Input.String.t()) :: {:ok, term()} | :error\n @spec decode(Absinthe.Blueprint.Input.Null.t()) :: {:ok, nil}\n defp decode(%Absinthe.Blueprint.Input.String{value: value}) do\n case Jason.decode(value) do\n {:ok, result} -> {:ok, result}\n _ -> :error\n end\n end\n\n defp decode(%Absinthe.Blueprint.Input.Null{}) do\n {:ok, nil}\n end\n\n defp decode(_) do\n :error\n end\n\n defp encode(value), do: value\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,18,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,null,null,null,null],"name":"lib/groupher_server/cms/community_category.ex","source":"defmodule GroupherServer.CMS.CommunityCategory do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Category, Community}\n\n @type t :: %CommunityCategory{}\n\n schema \"communities_categories\" do\n belongs_to(:community, Community, foreign_key: :community_id)\n belongs_to(:category, Category, foreign_key: :category_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @required_fields ~w(community_id category_id)a\n\n @doc false\n def changeset(%CommunityCategory{} = community_category, attrs) do\n community_category\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:community_id)\n |> foreign_key_constraint(:category_id)\n |> unique_constraint(\n :community_id,\n name: :communities_categories_community_id_category_id_index\n )\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,0,null,0,null,null,168,null],"name":"lib/groupher_server_web/middleware/general_error.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.GeneralError do\n @behaviour Absinthe.Middleware\n\n def call(%{errors: [List = errors]} = resolution, _) do\n message = [%{message: errors}]\n\n %{resolution | value: [], errors: message}\n end\n\n def call(resolution, _), do: resolution\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,11,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/cms/post_star.ex","source":"defmodule GroupherServer.CMS.PostStar do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Post\n\n @required_fields ~w(user_id post_id)a\n\n @type t :: %PostStar{}\n schema \"posts_stars\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:post, Post, foreign_key: :post_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostStar{} = post_star, attrs) do\n # |> unique_constraint(:user_id, name: :favorites_user_id_article_id_index)\n post_star\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:user_id, name: :posts_stars_user_id_post_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,197,197,196,196,null,196,196,null,null,null,null,196,null,196,null,null,null,153,43,null,null,null,null,null,5,5,5,null,5,5,null,null,null,null,null,null,null,null,null,null,null,null,11,11,null,11,null,null,null,11,null,null,null,null,3,3,3,null,3,null,3,null,null,null,null,9,9,9,null,9,null,null,null,null,null,null,9,9,null,null,null,null,7,null,2,null,null,null,7,7,null,7,7,null,7,null,null,null,null,null,2,2,null,2,null,2,null,null,null,null,null,196,null,null,null,196,null,null,null,null,9,null,null,null,9,null,null,162,45,null,7,2,null],"name":"lib/groupher_server/cms/delegates/comment_curd.ex","source":"defmodule GroupherServer.CMS.Delegate.CommentCURD do\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1]\n import GroupherServer.CMS.Helper.MatcherOld\n import ShortMaps\n\n alias GroupherServer.{Repo, Accounts}\n alias Helper.{ORM, QueryBuilder}\n alias GroupherServer.CMS.{PostCommentReply, JobCommentReply}\n\n @doc \"\"\"\n Creates a comment for psot, job ...\n \"\"\"\n def create_comment(thread, content_id, body, %Accounts.User{id: user_id}) do\n with {:ok, action} <- match_action(thread, :comment),\n {:ok, content} <- ORM.find(action.target, content_id),\n {:ok, user} <- ORM.find(Accounts.User, user_id) do\n next_floor = get_next_floor(thread, action.reactor, content.id)\n\n attrs = %{\n author_id: user.id,\n body: body,\n floor: next_floor\n }\n\n attrs = merge_comment_attrs(thread, attrs, content.id)\n\n action.reactor |> ORM.create(attrs)\n end\n end\n\n defp merge_comment_attrs(:post, attrs, id), do: attrs |> Map.merge(%{post_id: id})\n defp merge_comment_attrs(:job, attrs, id), do: attrs |> Map.merge(%{job_id: id})\n\n @doc \"\"\"\n Delete the comment and increase all the floor after this comment\n \"\"\"\n def delete_comment(thread, content_id) do\n with {:ok, action} <- match_action(thread, :comment),\n {:ok, comment} <- ORM.find(action.reactor, content_id) do\n case ORM.delete(comment) do\n {:ok, comment} ->\n Repo.update_all(\n from(p in action.reactor, where: p.id > ^comment.id),\n inc: [floor: -1]\n )\n\n {:ok, comment}\n\n {:error, error} ->\n {:error, error}\n end\n end\n end\n\n def paged_comments(thread, content_id, %{page: page, size: size} = filters) do\n with {:ok, action} <- match_action(thread, :comment) do\n dynamic = dynamic_comment_where(thread, content_id)\n\n action.reactor\n |> where(^dynamic)\n |> QueryBuilder.filter_pack(filters)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n end\n\n def paged_replies(thread, comment_id, %Accounts.User{id: user_id}) do\n with {:ok, action} <- match_action(thread, :comment) do\n action.reactor\n |> where([c], c.author_id == ^user_id)\n |> join(:inner, [c], r in assoc(c, :reply_to))\n |> where([c, r], r.id == ^comment_id)\n |> Repo.all()\n |> done()\n end\n end\n\n def reply_comment(thread, comment_id, body, %Accounts.User{id: user_id}) do\n with {:ok, action} <- match_action(thread, :comment),\n {:ok, comment} <- ORM.find(action.reactor, comment_id) do\n next_floor = get_next_floor(thread, action.reactor, comment)\n\n attrs = %{\n author_id: user_id,\n body: body,\n reply_to: comment,\n floor: next_floor\n }\n\n attrs = merge_reply_attrs(thread, attrs, comment)\n brige_reply(thread, action.reactor, comment, attrs)\n end\n end\n\n defp merge_reply_attrs(:post, attrs, comment),\n do: attrs |> Map.merge(%{post_id: comment.post_id})\n\n defp merge_reply_attrs(:job, attrs, comment), do: attrs |> Map.merge(%{job_id: comment.job_id})\n\n defp brige_reply(:post, queryable, comment, attrs) do\n # TODO: use Multi task to refactor\n with {:ok, reply} <- ORM.create(queryable, attrs) do\n ORM.update(reply, %{reply_id: comment.id})\n\n {:ok, _} =\n PostCommentReply |> ORM.create(%{post_comment_id: comment.id, reply_id: reply.id})\n\n queryable |> ORM.find(reply.id)\n end\n end\n\n defp brige_reply(:job, queryable, comment, attrs) do\n # TODO: use Multi task to refactor\n with {:ok, reply} <- ORM.create(queryable, attrs) do\n ORM.update(reply, %{reply_id: comment.id})\n\n {:ok, _} = JobCommentReply |> ORM.create(%{job_comment_id: comment.id, reply_id: reply.id})\n\n queryable |> ORM.find(reply.id)\n end\n end\n\n # for create comment\n defp get_next_floor(thread, queryable, id) when is_integer(id) do\n dynamic = dynamic_comment_where(thread, id)\n\n queryable\n |> where(^dynamic)\n |> ORM.next_count()\n end\n\n # for reply comment\n defp get_next_floor(thread, queryable, comment) do\n dynamic = dynamic_reply_where(thread, comment)\n\n queryable\n |> where(^dynamic)\n |> ORM.next_count()\n end\n\n defp dynamic_comment_where(:post, id), do: dynamic([c], c.post_id == ^id)\n defp dynamic_comment_where(:job, id), do: dynamic([c], c.job_id == ^id)\n\n defp dynamic_reply_where(:post, comment), do: dynamic([c], c.post_id == ^comment.post_id)\n defp dynamic_reply_where(:job, comment), do: dynamic([c], c.job_id == ^comment.job_id)\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,2,2,null,null,null,null,2,null,null,null,62,null,null,62,62,62,62,62,null,null,62,null,null,null,null,null,null,29,null,null,null,26,null,null],"name":"lib/groupher_server/delivery/delegates/notifications.ex","source":"defmodule GroupherServer.Delivery.Delegate.Notifications do\n @moduledoc \"\"\"\n The Delivery context.\n \"\"\"\n import Helper.Utils, only: [done: 2]\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.Delivery.{Notification, SysNotification}\n alias Helper.ORM\n\n alias GroupherServer.Delivery.Delegate.Utils\n\n # TODO: audience\n def publish_system_notification(info) do\n attrs = %{\n source_id: info.source_id,\n source_title: info.source_title,\n source_type: info |> Map.get(:source_type, \"\"),\n source_preview: info |> Map.get(:source_preview, \"\")\n }\n\n SysNotification |> ORM.create(attrs) |> done(:status)\n end\n\n def notify_someone(%User{id: from_user_id}, %User{id: to_user_id}, info) do\n attrs = %{\n from_user_id: from_user_id,\n to_user_id: to_user_id,\n action: info.action,\n source_id: info.source_id,\n source_title: info.source_title,\n source_type: info.source_type,\n source_preview: info.source_preview\n }\n\n Notification |> ORM.create(attrs)\n end\n\n @doc \"\"\"\n fetch notifications from Delivery\n \"\"\"\n def fetch_notifications(%User{} = user, %{page: _, size: _, read: _} = filter) do\n Utils.fetch_messages(user, Notification, filter)\n end\n\n def fetch_sys_notifications(%User{} = user, %{page: _, size: _} = filter) do\n Utils.fetch_messages(:sys_notification, user, filter)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,1722,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,34,null,null,null,null,null],"name":"lib/groupher_server/cms/post.ex","source":"defmodule GroupherServer.CMS.Post do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, PostComment, PostFavorite, PostStar, Tag}\n\n @required_fields ~w(title body digest length)a\n @optional_fields ~w(link_addr pin markDelete)\n\n @type t :: %Post{}\n schema \"cms_posts\" do\n field(:body, :string)\n field(:title, :string)\n field(:digest, :string)\n field(:link_addr, :string)\n field(:length, :integer)\n field(:views, :integer, default: 0)\n\n field(:pin, :boolean, default_value: false)\n field(:markDelete, :boolean, default_value: false)\n belongs_to(:author, Author)\n\n # TODO\n # 相关文章\n # has_may(:related_post, ...)\n\n has_many(:comments, {\"posts_comments\", PostComment})\n has_many(:favorites, {\"posts_favorites\", PostFavorite})\n has_many(:stars, {\"posts_stars\", PostStar})\n # The keys are inflected from the schema names!\n # see https://hexdocs.pm/ecto/Ecto.Schema.html\n many_to_many(\n :tags,\n Tag,\n join_through: \"posts_tags\",\n join_keys: [post_id: :id, tag_id: :id],\n # :delete_all will only remove data from the join source\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_posts\",\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Post{} = post, attrs) do\n post\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n\n # |> foreign_key_constraint(:posts_tags, name: :posts_tags_tag_id_fkey)\n # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,0,null,null,null,null,null,null,0,0,null,0,null,null,0,null,null,0,0,null,null,null,null,0,null,null,null,0,0,null,0,0,null,0,null,null,null,null,null,null,null,null,null,null,null,null,0,0,null,null,null,null,0,0,0,null,null,null,null],"name":"lib/helper/oauth2/github.ex","source":"defmodule Helper.OAuth2.Github do\n use Tesla, only: [:get, :post]\n import Helper.Utils, only: [get_config: 2]\n\n # see Tesla intro: https://medium.com/@teamon/introducing-tesla-the-flexible-http-client-for-elixir-95b699656d88\n @timeout_limit 5000\n @client_id get_config(:github_oauth, :client_id)\n @client_secret get_config(:github_oauth, :client_secret)\n @redirect_uri \"http://www.coderplanets.com\"\n\n # wired only this style works\n plug(Tesla.Middleware.BaseUrl, \"https://github.com/login/oauth\")\n # plug(Tesla.Middleware.BaseUrl, \"https://www.github.com/login/oauth\")\n # plug(Tesla.Middleware.BaseUrl, \"https://api.github.com/login/oauth\")\n plug(Tesla.Middleware.Headers, %{\n \"User-Agent\" => \"groupher server\"\n # \"Accept\" => \"application/json\"\n # \"Accept\" => \"application/json;application/vnd.github.jean-grey-preview+json\"\n })\n\n plug(Tesla.Middleware.Retry, delay: 200, max_retries: 2)\n plug(Tesla.Middleware.Timeout, timeout: @timeout_limit)\n plug(Tesla.Middleware.JSON)\n plug(Tesla.Middleware.FormUrlencoded)\n\n def user_profile(code) do\n # body = \"client_id=#{@client_id}&client_secret=#{@client_secret}&code=#{code}&redirect_uri=#{@redirect_uri}\"\n # post(\"access_token?#{body}\",%{})\n headers = %{\"Accept\" => \"application/json\"}\n\n query = [\n code: code,\n client_id: @client_id,\n client_secret: @client_secret,\n redirect_uri: @redirect_uri\n ]\n\n try do\n case post(\"/access_token\", %{}, query: query, headers: headers) do\n %{status: 200, body: %{\"error\" => error, \"error_description\" => description}} ->\n {:error, \"#{error}: #{description}\"}\n\n %{status: 200, body: %{\"access_token\" => access_token, \"token_type\" => \"bearer\"}} ->\n user_info(access_token)\n end\n rescue\n e ->\n e |> handle_tesla_error\n end\n end\n\n def user_info(access_token) do\n url = \"https://api.github.com/user\"\n # this special header is too get node_id\n # see: https://developer.github.com/v3/\n\n headers = %{\"Accept\" => \"application/vnd.github.jean-grey-preview+json\"}\n query = [access_token: access_token]\n\n try do\n case get(url, query: query, headers: headers) do\n %{status: 200, body: body} ->\n body = body |> Map.merge(%{\"access_token\" => access_token})\n {:ok, body}\n\n %{status: 401, body: body} ->\n {:error, \"OAuth2 Github: \" <> body[\"message\"]}\n\n %{status: 403, body: body} ->\n {:error, \"OAuth2 Github: \" <> body}\n\n _ ->\n {:error, \"OAuth2 Github: unhandle error\"}\n end\n rescue\n e ->\n e |> handle_tesla_error\n end\n end\n\n defp handle_tesla_error(error) do\n case error do\n %{reason: :timeout} -> {:error, \"OAuth2 Github: timeout in #{@timeout_limit} msec\"}\n %{reason: reason} -> {:error, \"OAuth2 Github: #{reason}\"}\n _ -> {:error, \"unhandle error #{inspect(error)}\"}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/cms_misc.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Misc do\n use Absinthe.Schema.Notation\n\n import GroupherServerWeb.Schema.Helper.Fields\n\n alias GroupherServer.CMS\n\n @default_inner_page_size 5\n\n enum :comment_replies_type do\n value(:comment_replies_type)\n end\n\n enum :post_type do\n value(:post)\n end\n\n enum :community_type do\n value(:community)\n end\n\n enum :favorite_action do\n value(:favorite)\n end\n\n enum :count_type do\n value(:count)\n end\n\n enum :viewer_did_type do\n value(:viewer_did)\n end\n\n enum :star_action do\n value(:star)\n end\n\n enum :comment_action do\n value(:comment)\n end\n\n enum :unique_type do\n value(true)\n value(false)\n end\n\n enum :cms_action do\n value(:favorite)\n value(:star)\n value(:watch)\n end\n\n enum :thread do\n value(:post)\n value(:job)\n value(:video)\n value(:repo)\n value(:wiki)\n end\n\n enum :cms_comment do\n value(:post_comment)\n end\n\n enum :order_enum do\n value(:asc)\n value(:desc)\n end\n\n enum :when_enum do\n value(:today)\n value(:this_week)\n value(:this_month)\n value(:this_year)\n end\n\n enum :comment_sort_enum do\n value(:asc_inserted)\n value(:desc_inserted)\n value(:most_likes)\n value(:most_dislikes)\n end\n\n enum :thread_sort_enum do\n value(:asc_index)\n value(:desc_index)\n value(:asc_inserted)\n value(:desc_inserted)\n end\n\n enum :sort_enum do\n value(:most_views)\n value(:most_updated)\n value(:most_favorites)\n value(:most_stars)\n value(:most_watched)\n value(:most_comments)\n value(:least_views)\n value(:least_updated)\n value(:least_favorites)\n value(:least_stars)\n value(:least_watched)\n value(:least_comments)\n value(:recent_updated)\n end\n\n enum :rainbow_color_enum do\n value(:red)\n value(:orange)\n value(:yellow)\n value(:green)\n value(:cyan)\n value(:blue)\n value(:purple)\n end\n\n @desc \"inline members-like filter for dataloader usage\"\n input_object :members_filter do\n field(:first, :integer, default_value: @default_inner_page_size)\n end\n\n input_object :comments_filter do\n pagination_args()\n field(:sort, :comment_sort_enum, default_value: :asc_inserted)\n end\n\n input_object :communities_filter do\n @desc \"limit of records (default 20), if first > 30, only return 30 at most\"\n pagination_args()\n field(:sort, :sort_enum)\n field(:category, :string)\n end\n\n input_object :threads_filter do\n pagination_args()\n field(:sort, :thread_sort_enum)\n end\n\n input_object :paged_filter do\n @desc \"limit of records (default 20), if first > 30, only return 30 at most\"\n pagination_args()\n field(:sort, :sort_enum)\n end\n\n @desc \"article_filter doc\"\n input_object :article_filter do\n @desc \"limit of records (default 20), if first > 30, only return 30 at most\"\n field(:first, :integer)\n\n @desc \"Matching a tag\"\n field(:article_tag, :string, default_value: :all)\n # field(:sort, :sort_input)\n field(:when, :when_enum)\n field(:sort, :sort_enum)\n # @desc \"Matching a tag\"\n # @desc \"Added to the menu after this date\"\n # field(:added_after, :datetime)\n end\n\n @desc \"article_filter doc\"\n input_object :paged_article_filter do\n @desc \"limit of records (default 20), if first > 30, only return 30 at most\"\n pagination_args()\n\n field(:when, :when_enum)\n field(:sort, :sort_enum)\n field(:article_tag, :string, default_value: :all)\n field(:community, :string)\n\n # @desc \"Matching a name\"\n # field(:order, :order_enum, default_value: :desc)\n\n # @desc \"Matching a tag\"\n # field(:article_tag, :string, default_value: :all)\n end\n\n @doc \"\"\"\n only used for reaction result, like: favorite/star/watch ...\n \"\"\"\n interface :article do\n field(:id, :id)\n field(:title, :string)\n\n resolve_type(fn\n %CMS.Post{}, _ -> :post\n _, _ -> nil\n end)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,3,2,0,null,null,252,null,null,99,4,3,3,null,null,12,null,null,30,null,null,28,25,null,null,2,4,6,null,null,0,4,3,null,null,6,4,null],"name":"lib/groupher_server/accounts/accounts.ex","source":"defmodule GroupherServer.Accounts do\n @moduledoc false\n\n alias GroupherServer.Accounts.Delegate.{\n Achievements,\n Billing,\n Customization,\n Fans,\n Mails,\n Profile,\n ReactedContents\n }\n\n # profile\n defdelegate update_profile(user, attrs), to: Profile\n defdelegate github_signin(github_user), to: Profile\n defdelegate default_subscribed_communities(filter), to: Profile\n defdelegate subscribed_communities(user, filter), to: Profile\n\n # achievement\n defdelegate achieve(user, operation, key), to: Achievements\n\n # fans\n defdelegate follow(user, follower), to: Fans\n defdelegate undo_follow(user, follower), to: Fans\n defdelegate fetch_followers(user, filter), to: Fans\n defdelegate fetch_followings(user, filter), to: Fans\n\n # reacted contents\n defdelegate reacted_contents(thread, react, filter, user), to: ReactedContents\n\n # mentions\n defdelegate fetch_mentions(user, filter), to: Mails\n\n # notifications\n defdelegate fetch_notifications(user, filter), to: Mails\n defdelegate fetch_sys_notifications(user, filter), to: Mails\n\n # common message\n defdelegate mailbox_status(user), to: Mails\n defdelegate mark_mail_read_all(user, opt), to: Mails\n defdelegate mark_mail_read(mail, user), to: Mails\n\n # purchase\n defdelegate purchase_service(user, key, value), to: Billing\n defdelegate purchase_service(user, key), to: Billing\n defdelegate has_purchased?(user, key), to: Billing\n\n # customization\n defdelegate add_custom_setting(user, key, value), to: Customization\n defdelegate add_custom_setting(user, key), to: Customization\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,18,null,null,null,10,null,null,null,31,31,31,null,null,31,31,null,null,null,null,null,1,1,1,null,null,1,1,null,null,null,null,null,null,null,null,10,10,10,10,null,null,null,8,8,null,null,null,null,null,null,null,null,1,1,1,null,null,1,1,null,null,null,null,null,null,10,10,null,10,null,10,null,null,10,null,null,null],"name":"lib/groupher_server/cms/delegates/article_operation.ex","source":"defmodule GroupherServer.CMS.Delegate.ArticleCommunity do\n @moduledoc \"\"\"\n set / unset operations for Article-like resource\n \"\"\"\n import GroupherServer.CMS.Helper.MatcherOld\n import Ecto.Query, warn: false\n import Helper.ErrorCode\n\n alias Helper.ORM\n alias GroupherServer.Accounts.User\n alias GroupherServer.CMS.{Community, Tag}\n alias GroupherServer.Repo\n\n @doc \"\"\"\n pin / unpin, markDelete / untrash articles\n \"\"\"\n def set_flag(queryable, id, %{pin: _} = attrs, %User{} = _user) do\n queryable |> ORM.find_update(id, attrs)\n end\n\n def set_flag(queryable, id, %{markDelete: _} = attrs, %User{} = _user) do\n queryable |> ORM.find_update(id, attrs)\n end\n\n def mirror_article(%Community{id: community_id}, thread, content_id) when valid_thread(thread) do\n with {:ok, action} <- match_action(thread, :community),\n {:ok, content} <- ORM.find(action.target, content_id, preload: :communities),\n {:ok, community} <- ORM.find(action.reactor, community_id) do\n content\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.put_assoc(:communities, content.communities ++ [community])\n |> Repo.update()\n end\n end\n\n def unmirror_article(%Community{id: community_id}, thread, content_id)\n when valid_thread(thread) do\n with {:ok, action} <- match_action(thread, :community),\n {:ok, content} <- ORM.find(action.target, content_id, preload: :communities),\n {:ok, community} <- ORM.find(action.reactor, community_id) do\n content\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.put_assoc(:communities, content.communities -- [community])\n |> Repo.update()\n end\n end\n\n @doc \"\"\"\n set tag for post / tuts / videos ...\n \"\"\"\n # check community first\n def set_tag(%Community{id: communitId}, thread, %Tag{id: tag_id}, content_id) do\n with {:ok, action} <- match_action(thread, :article_tag),\n {:ok, content} <- ORM.find(action.target, content_id, preload: :tags),\n {:ok, tag} <- ORM.find(action.reactor, tag_id) do\n case tag_in_community_thread?(%Community{id: communitId}, thread, tag) do\n true ->\n content\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.put_assoc(:tags, content.tags ++ [tag])\n |> Repo.update()\n\n _ ->\n {:error, message: \"Tag,Community,Thread not match\", code: ecode(:custom)}\n end\n end\n end\n\n def unset_tag(thread, %Tag{id: tag_id}, content_id) when valid_thread(thread) do\n with {:ok, action} <- match_action(thread, :article_tag),\n {:ok, content} <- ORM.find(action.target, content_id, preload: :tags),\n {:ok, tag} <- ORM.find(action.reactor, tag_id) do\n content\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.put_assoc(:tags, content.tags -- [tag])\n |> Repo.update()\n end\n end\n\n # make sure the reuest tag is in the current community thread\n # example: you can't set a other thread tag to this thread's article\n defp tag_in_community_thread?(%Community{id: communityId}, thread, tag) do\n with {:ok, community} <- ORM.find(Community, communityId) do\n matched_tags =\n Tag\n |> where([t], t.community_id == ^community.id)\n # |> where([t], t.thread == ^(to_string(thread) |> String.upcase()))\n |> where([t], t.thread == ^to_string(thread))\n |> Repo.all()\n\n tag in matched_tags\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1,0,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/endpoint.ex","source":"defmodule GroupherServerWeb.Endpoint do\n use Phoenix.Endpoint, otp_app: :groupher_server\n\n socket(\"/socket\", GroupherServerWeb.UserSocket)\n\n plug(Plug.RequestId)\n plug(Plug.Logger)\n\n plug(\n Plug.Parsers,\n parsers: [:urlencoded, :multipart, :json],\n pass: [\"*/*\"],\n json_decoder: Jason\n )\n\n plug(Plug.MethodOverride)\n plug(Plug.Head)\n\n # plug(:inspect_conn)\n\n plug(\n Corsica,\n # log: [rejected: :error],\n log: [rejected: :debug],\n origins: \"*\",\n allow_headers: [\n \"authorization\",\n \"content-type\",\n \"special\",\n \"accept\",\n \"origin\",\n \"x-requested-with\"\n ],\n allow_credentials: true\n )\n\n plug(GroupherServerWeb.Router)\n\n @doc \"\"\"\n Callback invoked for dynamically configuring the endpoint.\n\n It receives the endpoint configuration and checks if\n configuration should be loaded from the system environment.\n \"\"\"\n def init(_key, config) do\n if config[:load_from_system_env] do\n port = System.get_env(\"PORT\") || raise \"expected the PORT environment variable to be set\"\n {:ok, Keyword.put(config, :http, [:inet6, port: port])}\n else\n {:ok, config}\n end\n end\n\n # defp inspect_conn(conn, _), do: IO.inspect(conn)\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/cms/repo_builder.ex","source":"defmodule GroupherServer.CMS.RepoBuilder do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n @required_fields ~w(nickname avatar link)a\n @optional_fields ~w(bio)\n\n @type t :: %RepoBuilder{}\n schema \"cms_repo_users\" do\n field(:nickname, :string)\n field(:avatar, :string)\n field(:link, :string)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%RepoBuilder{} = repo_builder, attrs) do\n repo_builder\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/account/account_types.ex","source":"defmodule GroupherServerWeb.Schema.Account.Types do\n use Helper.GqlSchemaSuite\n\n import GroupherServerWeb.Schema.Helper.Fields\n import Absinthe.Resolution.Helpers\n\n alias GroupherServer.Accounts\n alias GroupherServerWeb.Schema\n\n import_types(Schema.Account.Misc)\n\n object :user do\n field(:id, :id)\n field(:nickname, :string)\n field(:avatar, :string)\n field(:bio, :string)\n field(:sex, :string)\n field(:email, :string)\n field(:location, :string)\n field(:education, :string)\n field(:company, :string)\n field(:qq, :string)\n field(:weibo, :string)\n field(:weichat, :string)\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n field(:from_github, :boolean)\n field(:github_profile, :github_profile, resolve: dataloader(Accounts, :github_profile))\n field(:achievement, :achievement, resolve: dataloader(Accounts, :achievement))\n\n field(:cms_passport_string, :string) do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.get_passport_string/3)\n end\n\n field(:cms_passport, :json) do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.get_passport/3)\n end\n\n field :subscribed_communities, list_of(:community) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(Accounts, :subscribed_communities))\n end\n\n field :subscribed_communities_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :subscribed_communities))\n middleware(M.ConvertToInt)\n end\n\n field :followers_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :followers))\n middleware(M.ConvertToInt)\n end\n\n field :followings_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :followings))\n middleware(M.ConvertToInt)\n end\n\n field :viewer_has_followed, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n middleware(M.PutCurrentUser)\n resolve(dataloader(Accounts, :followers))\n middleware(M.ViewerDidConvert)\n end\n\n field :favorited_posts, :paged_posts do\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.favorited_posts/3)\n end\n\n field :favorited_jobs, :paged_jobs do\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.favorited_jobs/3)\n end\n\n field :favorited_posts_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :favorited_posts))\n middleware(M.ConvertToInt)\n end\n\n field :favorited_jobs_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :favorited_jobs))\n middleware(M.ConvertToInt)\n end\n\n field :contributes, :contribute_map do\n resolve(&R.Statistics.list_contributes/3)\n end\n\n # TODO, for msg-bell UI\n # field :has_messges,\n # 1. has_mentions ?\n # 2. has_system_messages ?\n # 3. has_notifications ?\n # 4. has_watches ?\n\n field :mail_box, :mail_box_status do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.get_mail_box_status/3)\n end\n\n field :mentions, :paged_mentions do\n arg(:filter, :messages_filter)\n\n middleware(M.Authorize, :login)\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.fetch_mentions/3)\n end\n\n field :notifications, :paged_notifications do\n arg(:filter, :messages_filter)\n\n middleware(M.Authorize, :login)\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.fetch_notifications/3)\n end\n\n field :sys_notifications, :paged_sys_notifications do\n arg(:filter, :messages_filter)\n\n middleware(M.Authorize, :login)\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.fetch_sys_notifications/3)\n end\n end\n\n object :github_profile do\n field(:id, :id)\n field(:github_id, :string)\n # field(:user, :user, resolve: dataloader(Accounts, :user))\n field(:login, :string)\n field(:avatar_url, :string)\n field(:url, :string)\n field(:html_url, :string)\n field(:name, :string)\n field(:company, :string)\n field(:blog, :string)\n field(:location, :string)\n field(:email, :string)\n field(:bio, :string)\n field(:public_repos, :integer)\n field(:public_gists, :integer)\n end\n\n object :achievement do\n field(:reputation, :integer)\n field(:followers_count, :integer)\n field(:contents_stared_count, :integer)\n field(:contents_favorited_count, :integer)\n field(:contents_watched_count, :integer)\n end\n\n object :token_info do\n field(:token, :string)\n field(:user, :user)\n end\n\n object :rules do\n field(:cms, :json)\n end\n\n object :paged_users do\n field(:entries, list_of(:user))\n pagination_fields()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,39,null,39,39,null,null,null,null,null,152,null,152,152,null,null,null,null,null,10,null,10,10,null,null,null,null,null,26,26,26,null,null,null,12,12,12,null,null,null,2,null,null,null,null,null,null,null,null,null,null,null,98,null,null,null,null,null,null,null,null,null,null,9,null,null,null,null,null,null,87,null,null,null,null,null,null,87,null,87,null,null,null,null,null,null,null,null,null,null,null,null,151,null,null,null,null,null,null,null,0,null,null,null,null,null,null,3,null,null,null,null,null,null,13,null,null,null,null,13,null,13,null,null,null,null,null,null,null,6,6,null,null,null,null],"name":"test/support/assert_helper.ex","source":"defmodule GroupherServer.Test.AssertHelper do\n @moduledoc \"\"\"\n This module defines some helper function used by\n tests that require check from graphql response\n \"\"\"\n\n import Phoenix.ConnTest\n import Helper.Utils, only: [map_key_stringify: 1, get_config: 2]\n\n @endpoint GroupherServerWeb.Endpoint\n\n @page_size get_config(:general, :page_size)\n @inner_page_size get_config(:general, :inner_page_size)\n\n @doc \"\"\"\n used for non exsit id\n \"\"\"\n def non_exsit_id, do: 15_982_398_614\n def inner_page_size, do: @inner_page_size\n def page_size, do: @page_size\n\n def is_valid_kv?(obj, key, :list) when is_map(obj) do\n obj = map_key_stringify(obj)\n\n case Map.has_key?(obj, key) do\n true -> obj |> Map.get(key) |> is_list\n _ -> false\n end\n end\n\n def is_valid_kv?(obj, key, :int) when is_map(obj) do\n obj = map_key_stringify(obj)\n\n case Map.has_key?(obj, key) do\n true -> obj |> Map.get(key) |> is_integer\n _ -> false\n end\n end\n\n def is_valid_kv?(obj, key, :string) when is_map(obj) and is_binary(key) do\n obj = map_key_stringify(obj)\n\n case Map.has_key?(obj, key) do\n true -> String.length(Map.get(obj, key)) != 0\n _ -> false\n end\n end\n\n def is_valid_pagination?(obj) when is_map(obj) do\n is_valid_kv?(obj, \"entries\", :list) and is_valid_kv?(obj, \"totalPages\", :int) and\n is_valid_kv?(obj, \"totalCount\", :int) and is_valid_kv?(obj, \"pageSize\", :int) and\n is_valid_kv?(obj, \"pageNumber\", :int)\n end\n\n def is_valid_pagination?(obj, :raw) when is_map(obj) do\n is_valid_kv?(obj, \"entries\", :list) and is_valid_kv?(obj, \"total_pages\", :int) and\n is_valid_kv?(obj, \"total_count\", :int) and is_valid_kv?(obj, \"page_size\", :int) and\n is_valid_kv?(obj, \"page_number\", :int)\n end\n\n def has_boolen_value?(obj, key) do\n obj |> Map.get(key) |> is_boolean\n end\n\n @doc \"\"\"\n simulate the Graphiql murate operation\n \"\"\"\n def mutation_result(conn, query, variables, key) do\n conn\n |> post(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n # |> IO.inspect(label: \"debug\")\n |> Map.get(\"data\")\n |> Map.get(key)\n end\n\n @doc \"\"\"\n check if Graphiql murate get error\n \"\"\"\n def mutation_get_error?(conn, query, variables) do\n conn\n |> post(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n # |> IO.inspect(label: \"debug\")\n |> Map.has_key?(\"errors\")\n end\n\n @doc \"\"\"\n Graphiql murate error with code equal check\n \"\"\"\n def mutation_get_error?(conn, query, variables, code) when is_integer(code) do\n resp =\n conn\n |> post(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n\n # |> IO.inspect(label: \"debug\")\n\n case resp |> Map.has_key?(\"errors\") do\n true ->\n code == resp[\"errors\"] |> List.first() |> Map.get(\"code\")\n\n false ->\n false\n end\n end\n\n def query_result(conn, query, variables, key) do\n conn\n |> get(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n # |> IO.inspect(label: \"debug\")\n |> Map.get(\"data\")\n |> Map.get(key)\n end\n\n def query_result(conn, query, key) do\n conn\n |> get(\"/graphiql\", query: query, variables: %{})\n |> json_response(200)\n |> Map.get(\"data\")\n |> Map.get(key)\n end\n\n def query_get_error?(conn, query, variables) do\n conn\n |> get(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n |> Map.has_key?(\"errors\")\n end\n\n @doc \"\"\"\n check if Graphiql murate get error\n \"\"\"\n def query_get_error?(conn, query, variables, code) when is_integer(code) do\n resp =\n conn\n |> get(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n\n case resp |> Map.has_key?(\"errors\") do\n true ->\n code == resp[\"errors\"] |> List.first() |> Map.get(\"code\")\n\n false ->\n false\n end\n end\n\n def firstn_and_last(values, 3) do\n [value_1 | [value_2 | [value_3 | _]]] = values\n value_x = values |> List.last()\n\n [value_1, value_2, value_3, value_x]\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/utils/helper.ex","source":"defmodule GroupherServerWeb.Schema.Helper.Fields do\n import Helper.Utils, only: [get_config: 2]\n @page_size get_config(:general, :page_size)\n # @default_inner_page_size 5\n\n # see: https://github.com/absinthe-graphql/absinthe/issues/363\n defmacro pagination_args() do\n quote do\n field(:page, :integer, default_value: 1)\n field(:size, :integer, default_value: unquote(@page_size))\n end\n end\n\n defmacro pagination_fields() do\n quote do\n field(:total_count, :integer)\n field(:page_size, :integer)\n field(:total_pages, :integer)\n field(:page_number, :integer)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,14,1,null,null,24,null,null,null,1,null,null,null,0,null,null,null,5,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,2,null,null,null,2,null,null,null,2,null,null,null,2,null,null,null,null,2,null,null,null,null,20,null,null,null,1,null,null,null,1,null,null,null,null,20,null,null,null,20,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,0,null,null,null,null,1,null,null,null,null,0,null,null,null,1,null,null,null,2,null,null,null,2,null,null,null,null,null,null,null,null,null,1,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/resolvers/accounts_resolver.ex","source":"defmodule GroupherServerWeb.Resolvers.Accounts do\n @moduledoc \"\"\"\n accounts resolvers\n \"\"\"\n import ShortMaps\n\n alias Helper.{Certification, ORM}\n alias GroupherServer.{Accounts, CMS}\n\n alias Accounts.{MentionMail, NotificationMail, SysNotificationMail, User}\n\n def user(_root, %{id: id}, _info), do: User |> ORM.find(id)\n def users(_root, ~m(filter)a, _info), do: User |> ORM.find_all(filter)\n\n def account(_root, _args, %{context: %{cur_user: cur_user}}) do\n User |> ORM.find(cur_user.id)\n end\n\n def update_profile(_root, %{profile: profile}, %{context: %{cur_user: cur_user}}) do\n Accounts.update_profile(%User{id: cur_user.id}, profile)\n end\n\n def github_signin(_root, %{github_user: github_user}, _info) do\n Accounts.github_signin(github_user)\n end\n\n def follow(_root, ~m(user_id)a, %{context: %{cur_user: cur_user}}) do\n Accounts.follow(cur_user, %User{id: user_id})\n end\n\n def undo_follow(_root, ~m(user_id)a, %{context: %{cur_user: cur_user}}) do\n Accounts.undo_follow(cur_user, %User{id: user_id})\n end\n\n def paged_followers(_root, ~m(user_id filter)a, _info) do\n Accounts.fetch_followers(%User{id: user_id}, filter)\n end\n\n def paged_followers(_root, ~m(filter)a, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_followers(cur_user, filter)\n end\n\n def paged_followings(_root, ~m(user_id filter)a, _info) do\n Accounts.fetch_followings(%User{id: user_id}, filter)\n end\n\n def paged_followings(_root, ~m(filter)a, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_followings(cur_user, filter)\n end\n\n # for check other users query\n def favorited_posts(_root, ~m(user_id filter)a, _info) do\n Accounts.reacted_contents(:post, :favorite, filter, %User{id: user_id})\n end\n\n def favorited_posts(_root, ~m(filter)a, %{context: %{cur_user: cur_user}}) do\n Accounts.reacted_contents(:post, :favorite, filter, cur_user)\n end\n\n def favorited_jobs(_root, ~m(user_id filter)a, _info) do\n Accounts.reacted_contents(:job, :favorite, filter, %User{id: user_id})\n end\n\n def favorited_jobs(_root, ~m(filter)a, %{context: %{cur_user: cur_user}}) do\n Accounts.reacted_contents(:job, :favorite, filter, cur_user)\n end\n\n # TODO: refactor\n def get_mail_box_status(_root, _args, %{context: %{cur_user: cur_user}}) do\n Accounts.mailbox_status(cur_user)\n end\n\n # mentions\n def fetch_mentions(_root, %{filter: filter}, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_mentions(cur_user, filter)\n end\n\n def mark_mention_read(_root, %{id: id}, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read(%MentionMail{id: id}, cur_user)\n end\n\n def mark_mention_read_all(_root, _args, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read_all(cur_user, :mention)\n end\n\n # notification\n def fetch_notifications(_root, %{filter: filter}, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_notifications(cur_user, filter)\n end\n\n def fetch_sys_notifications(_root, %{filter: filter}, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_sys_notifications(cur_user, filter)\n end\n\n def mark_notification_read(_root, %{id: id}, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read(%NotificationMail{id: id}, cur_user)\n end\n\n def mark_notification_read_all(_root, _args, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read_all(cur_user, :notification)\n end\n\n def mark_sys_notification_read(_root, %{id: id}, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read(%SysNotificationMail{id: id}, cur_user)\n end\n\n # for user self's\n def subscribed_communities(_root, %{filter: filter}, %{cur_user: cur_user}) do\n Accounts.subscribed_communities(%User{id: cur_user.id}, filter)\n end\n\n #\n def subscribed_communities(_root, %{user_id: \"\", filter: filter}, _info) do\n Accounts.default_subscribed_communities(filter)\n end\n\n # for check other users subscribed_communities\n def subscribed_communities(_root, %{user_id: user_id, filter: filter}, _info) do\n Accounts.subscribed_communities(%User{id: user_id}, filter)\n end\n\n def subscribed_communities(_root, %{filter: filter}, _info) do\n Accounts.default_subscribed_communities(filter)\n end\n\n def get_passport(root, _args, %{context: %{cur_user: _}}) do\n CMS.get_passport(%User{id: root.id})\n end\n\n def get_passport_string(root, _args, %{context: %{cur_user: _}}) do\n case CMS.get_passport(%User{id: root.id}) do\n {:ok, passport} ->\n {:ok, Jason.encode!(passport)}\n\n {:error, _} ->\n {:ok, nil}\n end\n end\n\n def get_all_rules(_root, _args, %{context: %{cur_user: _}}) do\n cms_rules = Certification.all_rules(:cms, :stringify)\n\n {:ok,\n %{\n cms: cms_rules\n }}\n end\n\n # def create_user(_root, args, %{context: %{cur_user: %{root: true}}}) do\n # Accounts.create_user2(args)\n # end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/logs/logs.ex","source":"defmodule GroupherServer.Logs do\n @moduledoc \"\"\"\n The Logs context.\n \"\"\"\n\n # import Ecto.Query, warn: false\n # alias GroupherServer.Repo\n\n # alias GroupherServer.Logs.UserActivity\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/statistics/statistics_types.ex","source":"defmodule GroupherServerWeb.Schema.Statistics.Types do\n use Absinthe.Schema.Notation\n use Absinthe.Ecto, repo: GroupherServer.Repo\n\n # import Absinthe.Resolution.Helpers\n\n # alias GroupherServer.Accounts\n\n object :user_contribute do\n field(:count, :integer)\n field(:date, :date)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,5,5,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,null,1,null,1,null,null,null,2,null,null,null,null,null,null,null,2,2,null,null,null,null,null,null,null,0,null,0,null,null,0,null,null,null,null,null,2,null,null,2,null,null,2,null,null,2,null,null,null,null,null,null,null,null,3,null,null,null,null,null,2,null,null,null,null,null,null,null,null,null,2,null,null,null,null,2,null,2,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/delegates/profile.ex","source":"defmodule GroupherServer.Accounts.Delegate.Profile do\n @moduledoc \"\"\"\n accounts profile\n \"\"\"\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1, get_config: 2]\n import ShortMaps\n\n alias Helper.{Guardian, ORM, QueryBuilder}\n alias GroupherServer.Accounts.{GithubUser, User}\n alias GroupherServer.{CMS, Repo}\n\n alias Ecto.Multi\n\n @default_subscribed_communities get_config(:general, :default_subscribed_communities)\n\n def update_profile(%User{id: id}, attrs \\\\ %{}) do\n with {:ok, user} <- ORM.find(User, id) do\n case user.id === id do\n true -> user |> ORM.update(attrs)\n false -> {:error, \"Error: not qualified\"}\n end\n end\n end\n\n @doc \"\"\"\n github_signin steps:\n ------------------\n step 0: get access_token is enough, even profile is not need?\n step 1: check is access_token valid or not, think use a Middleware\n step 2.1: if access_token's github_id exsit, then login\n step 2.2: if access_token's github_id not exsit, then signup\n step 3: return groupher token\n \"\"\"\n def github_signin(github_user) do\n case ORM.find_by(GithubUser, github_id: to_string(github_user[\"id\"])) do\n {:ok, g_user} ->\n {:ok, user} = ORM.find(User, g_user.user_id)\n # IO.inspect label: \"send back from db\"\n token_info(user)\n\n {:error, _} ->\n # IO.inspect label: \"register then send\"\n register_github_user(github_user)\n end\n end\n\n @doc \"\"\"\n get default subscribed communities for unlogin user\n \"\"\"\n def default_subscribed_communities(%{page: _, size: _} = filter) do\n filter = Map.merge(filter, %{size: @default_subscribed_communities})\n CMS.Community |> ORM.find_all(filter)\n end\n\n @doc \"\"\"\n get users subscribed communities\n \"\"\"\n def subscribed_communities(%User{id: id}, %{page: page, size: size} = filter) do\n CMS.CommunitySubscriber\n |> where([c], c.user_id == ^id)\n |> join(:inner, [c], cc in assoc(c, :community))\n |> select([c, cc], cc)\n |> QueryBuilder.filter_pack(filter)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n defp register_github_user(github_profile) do\n Multi.new()\n |> Multi.run(:create_user, fn _ ->\n create_user(github_profile, :github)\n end)\n |> Multi.run(:create_profile, fn %{create_user: user} ->\n create_profile(user, github_profile, :github)\n end)\n |> Repo.transaction()\n |> register_github_result()\n end\n\n defp register_github_result({:ok, %{create_user: user}}), do: token_info(user)\n\n defp register_github_result({:error, :create_user, _result, _steps}),\n do: {:error, \"Accounts create_user internal error\"}\n\n defp register_github_result({:error, :create_profile, _result, _steps}),\n do: {:error, \"Accounts create_profile internal error\"}\n\n defp token_info(%User{} = user) do\n with {:ok, token, _info} <- Guardian.jwt_encode(user) do\n {:ok, %{token: token, user: user}}\n end\n end\n\n defp create_user(user, :github) do\n user = %User{\n nickname: user[\"login\"],\n avatar: user[\"avatar_url\"],\n bio: user[\"bio\"],\n location: user[\"location\"],\n email: user[\"email\"],\n company: user[\"company\"],\n from_github: true\n }\n\n Repo.insert(user)\n end\n\n defp create_profile(user, github_profile, :github) do\n # attrs = github_user |> Map.merge(%{github_id: github_user.id, user_id: 1}) |> Map.delete(:id)\n attrs =\n github_profile\n |> Map.merge(%{\"github_id\" => to_string(github_profile[\"id\"]), \"user_id\" => user.id})\n # |> Map.merge(%{\"github_id\" => github_profile[\"id\"], \"user_id\" => user.id})\n |> Map.delete(\"id\")\n\n %GithubUser{}\n |> GithubUser.changeset(attrs)\n |> Repo.insert()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,null,null,190,null,null,null,null,3,3,null,3,null,0,0,0,null,null,null,3,3,3,null,null,null,null,null,null,3,null,0,null,null,null,3,null,0,null,null,null,null,null,null,3,null,null,null,null,null],"name":"lib/groupher_server_web/middleware/changeset_errors.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.ChangesetErrors do\n @behaviour Absinthe.Middleware\n import Helper.Utils, only: [handle_absinthe_error: 3]\n import Helper.ErrorCode\n\n alias GroupherServerWeb.Gettext, as: Translator\n\n def call(%{errors: [%Ecto.Changeset{} = changeset]} = resolution, _) do\n # IO.inspect changeset, label: \"Changeset error\"\n # IO.inspect transform_errors(changeset), label: \"transform_errors\"\n resolution\n |> handle_absinthe_error(transform_errors(changeset), ecode(:changeset))\n end\n\n def call(resolution, _), do: resolution\n\n defp transform_errors(changeset) do\n changeset\n |> Ecto.Changeset.traverse_errors(&format_error/1)\n |> Enum.map(fn {key, err_msg_list} ->\n err_msg = err_msg_list |> List.first()\n\n cond do\n Map.has_key?(err_msg, :count) ->\n %{\n key: Translator |> Gettext.dgettext(\"fields\", \"#{key}\"),\n message: Translator |> Gettext.dgettext(\"errors\", err_msg.raw, count: err_msg.count)\n }\n\n true ->\n %{\n key: Translator |> Gettext.dgettext(\"fields\", \"#{key}\"),\n message: Translator |> Gettext.dgettext(\"errors\", err_msg.msg)\n }\n end\n end)\n end\n\n defp format_error({msg, opts}) do\n err_string =\n Enum.reduce(opts, msg, fn {key, value}, acc ->\n String.replace(acc, \"%{#{key}}\", to_string(value))\n end)\n\n # TODO handle: number type\n cond do\n String.contains?(msg, \"%{count}\") ->\n %{\n msg: err_string,\n count: Keyword.get(opts, :count),\n raw: msg\n }\n\n true ->\n %{\n msg: err_string\n }\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/delivery/delivery_types.ex","source":"defmodule GroupherServerWeb.Schema.Delivery.Types do\n use Absinthe.Schema.Notation\n use Absinthe.Ecto, repo: GroupherServer.Repo\n\n import GroupherServerWeb.Schema.Helper.Fields\n import Helper.Utils, only: [get_config: 2]\n\n @page_size get_config(:general, :page_size)\n\n object :mail_box_status do\n field(:has_mail, :boolean)\n field(:total_count, :integer)\n field(:mention_count, :integer)\n field(:notification_count, :integer)\n end\n\n object :mention do\n field(:id, :id)\n field(:from_user_id, :id)\n field(:to_user_id, :id)\n\n field(:source_title, :string)\n field(:read, :boolean)\n end\n\n object :sys_notification do\n field(:id, :id)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_preview, :string)\n field(:source_type, :string)\n\n field(:read, :boolean)\n end\n\n object :notification do\n field(:id, :id)\n field(:from_user_id, :id)\n field(:to_user_id, :id)\n field(:action, :string)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_preview, :string)\n field(:source_type, :string)\n field(:read, :boolean)\n end\n\n object :sys_notification do\n field(:id, :id)\n field(:user_id, :id)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_preview, :string)\n field(:source_type, :string)\n field(:read, :boolean)\n end\n\n object :paged_mentions do\n field(:entries, list_of(:mention))\n pagination_fields()\n end\n\n object :paged_notifications do\n field(:entries, list_of(:notification))\n pagination_fields()\n end\n\n object :paged_sys_notifications do\n field(:entries, list_of(:sys_notification))\n pagination_fields()\n end\n\n input_object :messages_filter do\n field(:read, :boolean, default_value: false)\n\n field(:page, :integer, default_value: 1)\n field(:size, :integer, default_value: @page_size)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,26,null,3,null,null,4,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,7,null,null,null,null,null,null,null,null,null,20,null,null,null,null,null,null,null,null,null,8,null,null,null,null,null,null,37,null,null,null,null,21,null,null,null,null,72,null,18,null,54,7,null,47,10,null,37,37,null,null,null,0,null,null,null,null,37,37,null,37,null,24,null,null,null,13,null,null,null,null,7,7,null,7,null,null,null,null,7,null,6,null,null,null,1,null,null,null,null,18,18,null,18,null,18,null,null,null,null,null,18,null,16,null,null,null,2,null,null,null,null,10,10,null,10,null,null,15,15,null,null,null,10,null,7,null,null,null,3,null,null,null],"name":"lib/groupher_server_web/middleware/passport.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\n# RBAC vs CBAC\n# https://stackoverflow.com/questions/22814023/role-based-access-control-rbac-vs-claims-based-access-control-cbac-in-asp-n\n\n# 本中间件会隐式的加载 community 的 rules 信息,并应用该 rules 信息\ndefmodule GroupherServerWeb.Middleware.Passport do\n @moduledoc \"\"\"\n c? -> community / communities\n t? -> thread, could be post / job / tut / video ...\n \"\"\"\n @behaviour Absinthe.Middleware\n\n import Helper.Utils\n import Helper.ErrorCode\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(%{arguments: %{passport_is_owner: true}} = resolution, claim: \"owner\"), do: resolution\n\n def call(%{arguments: %{passport_is_owner: true}} = resolution, claim: \"owner;\" <> _rest),\n do: resolution\n\n def call(\n %{\n context: %{cur_user: %{cur_passport: _}},\n arguments: %{community: _, thread: _}\n } = resolution,\n claim: \"cms->c?->t?.\" <> _rest = claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(\n %{\n context: %{cur_user: %{cur_passport: _}},\n arguments: %{thread: _}\n } = resolution,\n claim: \"cms->t?.\" <> _rest = claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(\n %{\n context: %{cur_user: %{cur_passport: _}},\n arguments: %{passport_communities: _}\n } = resolution,\n claim: \"cms->c?->\" <> _rest = claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(\n %{\n context: %{cur_user: %{cur_passport: _}},\n arguments: %{passport_communities: _}\n } = resolution,\n claim: \"owner;\" <> claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(\n %{context: %{cur_user: %{cur_passport: _}}} = resolution,\n claim: \"cms->\" <> _rest = claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(resolution, _) do\n resolution\n |> handle_absinthe_error(\"PassportError: your passport not qualified.\", ecode(:passport))\n end\n\n defp check_passport_stamp(resolution, claim) do\n # TODO: refactor\n cond do\n claim |> String.starts_with?(\"cms->c?->t?.\") ->\n resolution |> cp_check(claim)\n\n claim |> String.starts_with?(\"cms->t?.\") ->\n resolution |> p_check(claim)\n\n claim |> String.starts_with?(\"cms->c?->\") ->\n resolution |> c_check(claim)\n\n claim |> String.starts_with?(\"cms->\") ->\n resolution |> do_check(claim)\n\n true ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\n\n defp do_check(resolution, claim) do\n cur_passport = resolution.context.cur_user.cur_passport\n path = claim |> String.split(\"->\")\n\n case get_in(cur_passport, path) do\n true ->\n resolution\n\n nil ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\n\n defp p_check(resolution, claim) do\n cur_passport = resolution.context.cur_user.cur_passport\n thread = resolution.arguments.thread |> to_string\n\n path =\n claim\n |> String.replace(\"t?\", thread)\n |> String.split(\"->\")\n\n case get_in(cur_passport, path) do\n true ->\n resolution\n\n nil ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\n\n defp cp_check(resolution, claim) do\n cur_passport = resolution.context.cur_user.cur_passport\n community_title = resolution.arguments.passport_communities |> List.first() |> Map.get(:title)\n\n thread = resolution.arguments.thread |> to_string\n\n path =\n claim\n |> String.replace(\"c?\", community_title)\n |> String.replace(\"t?\", thread)\n |> String.split(\"->\")\n\n case get_in(cur_passport, path) do\n true ->\n resolution\n\n nil ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\n\n defp c_check(resolution, claim) do\n cur_passport = resolution.context.cur_user.cur_passport\n communities = resolution.arguments.passport_communities\n\n result =\n communities\n |> Enum.filter(fn community ->\n path = claim |> String.replace(\"c?\", community.title) |> String.split(\"->\")\n get_in(cur_passport, path) == true\n end)\n |> length\n\n case result > 0 do\n true ->\n resolution\n\n false ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,148,null,null,null,null,null,null,null,null,null,null,null,73,null,null],"name":"lib/groupher_server/cms/post_favorite.ex","source":"defmodule GroupherServer.CMS.PostFavorite do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Post\n\n @required_fields ~w(user_id post_id)a\n\n @type t :: %PostFavorite{}\n schema \"posts_favorites\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:post, Post, foreign_key: :post_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostFavorite{} = post_favorite, attrs) do\n post_favorite\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:user_id, name: :posts_favorites_user_id_post_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,2806,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/cms/author.ex","source":"defmodule GroupherServer.CMS.Author do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Post\n\n @type t :: %Author{}\n\n schema \"cms_authors\" do\n field(:role, :string)\n # field(:user_id, :id)\n has_many(:posts, Post)\n # user_id filed in own-table\n belongs_to(:user, Accounts.User)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Author{} = author, attrs) do\n # |> foreign_key_constraint(:user_id)\n author\n |> cast(attrs, [:role])\n |> validate_required([:role])\n |> unique_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,104,null,null,null,null,null,null,null,null,null,null,null,null,null,97,null,null],"name":"lib/groupher_server/accounts/user_following.ex","source":"defmodule GroupherServer.Accounts.UserFollowing do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id following_id)a\n\n @type t :: %UserFollowing{}\n schema \"users_followings\" do\n belongs_to(:user, User, foreign_key: :user_id)\n belongs_to(:following, User, foreign_key: :following_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%UserFollowing{} = user_following, attrs) do\n user_following\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> foreign_key_constraint(:following_id)\n |> unique_constraint(:user_id, name: :users_followers_user_id_following_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,361,null,null,null,null,0,0,null,null,null,null,null,null,null,0,null,null,null,null,0,null,null,null,0,null,null,null,null,null,0,null,0,null,null,null,0,null,null,null,null,null,0,0,null,null,null,null,null,0,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,0,null,null,null,0,0,null,null,0,null,null,null,null,null,null,null,12,null,null,null,3,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,0,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,2,null,null,null,1,null,null,null,6,null,null,null,2,null,null,null,null,null,null,1,null,null,null,null,2,null,null,null,null,null,0,null,null,null,null,null,4,null,null,null,2,null,null,null,null,1,null,null,null,null,null,4,null,null,null,null,0,null,null,null,null,1,null,null,null,null,null,null,1,null,null,null,null,2,null,null,null,null,null,0,null,null,null,null,null,null,null,null,26,null,null],"name":"lib/groupher_server/cms/utils/loader.ex","source":"defmodule GroupherServer.CMS.Helper.Loader do\n @moduledoc \"\"\"\n dataloader for cms context\n \"\"\"\n import Ecto.Query, warn: false\n\n alias Helper.QueryBuilder\n alias GroupherServer.Repo\n # alias GroupherServer.Accounts\n alias GroupherServer.CMS.{\n Author,\n CommunityEditor,\n CommunitySubscriber,\n CommunityThread,\n JobCommentReply,\n Post,\n PostComment,\n PostCommentDislike,\n PostCommentLike,\n PostCommentReply,\n PostFavorite,\n PostStar\n # job comment\n # JobComment,\n }\n\n def data, do: Dataloader.Ecto.new(Repo, query: &query/2, run_batch: &run_batch/5)\n\n # Big thanks: https://elixirforum.com/t/grouping-error-in-absinthe-dadaloader/13671/2\n # see also: https://github.com/absinthe-graphql/dataloader/issues/25\n def run_batch(Post, post_query, :posts_count, community_ids, repo_opts) do\n query =\n from(\n p in post_query,\n join: c in assoc(p, :communities),\n where: c.id in ^community_ids,\n group_by: c.id,\n select: {c.id, [count(p.id)]}\n )\n\n results =\n query\n |> Repo.all(repo_opts)\n |> Map.new()\n\n for id <- community_ids, do: Map.get(results, id, [0])\n end\n\n def run_batch(PostComment, comment_query, :cp_count, post_ids, repo_opts) do\n results =\n comment_query\n |> join(:inner, [c], a in assoc(c, :author))\n # |> distinct([c, a], a.id)\n |> group_by([c, a], a.id)\n |> group_by([c, a], c.post_id)\n |> select([c, a], {c.post_id, count(a.id)})\n |> Repo.all(repo_opts)\n |> Enum.group_by(fn {x, _} -> x end)\n |> Enum.map(fn {x, y} -> {x, [length(y)]} end)\n |> Map.new()\n\n for id <- post_ids, do: Map.get(results, id, [0])\n end\n\n def run_batch(PostComment, comment_query, :cp_users, post_ids, repo_opts) do\n # IO.inspect(comment_query, label: \"# run_batch # comment_query\")\n\n sq =\n from(\n pc in comment_query,\n join: a in assoc(pc, :author),\n select: %{id: a.id, row_number: fragment(\"row_number() OVER (PARTITION BY author_id)\")}\n )\n\n query =\n from(\n pc in comment_query,\n join: s in subquery(sq),\n on: s.id == pc.author_id,\n where: s.row_number == 10,\n select: {pc.post_id, s.id}\n )\n\n # query = comment_query\n # |> join(:inner, [c], a in assoc(c, :author))\n # |> distinct([c, a], c.post_id)\n # |> join(:inner_lateral, [c, a], u in fragment(\"SELECT * FROM users AS us WHERE us.id = ? LIMIT 1\", a.id))\n # |> join(:inner_lateral, [c, a], u in fragment(\"SELECT * FROM users AS us WHERE us.id > ? LIMIT 1\", 100))\n # |> select([c, a, u], {c.post_id, u.id, u.nickname})\n\n results =\n query\n # |> IO.inspect(label: \"before\")\n |> Repo.all(repo_opts)\n # |> IO.inspect(label: \"geting fuck\")\n |> bat_man()\n\n # results =\n # comment_query\n # |> join(:inner, [c], a in assoc(c, :author))\n # |> group_by([c, a], a.id)\n # |> group_by([c, a], c.post_id)\n # |> select([c, a], {c.post_id, a})\n # ---------\n # |> join(:inner, [c], s in subquery(sq), on: s.id == c.post_id)\n # |> join(:inner, [c], a in subquery(isubquery), c.post_id == 106)\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT * FROM users AS u WHERE u.id = ? LIMIT 3\", c.post_id))\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT * FROM users WHERE users.id > ? LIMIT 3\", 100))\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT * FROM posts_comments JOIN users ON users.id = ? LIMIT 2\", c.author_id))\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT * FROM posts_comments AS pc WHERE pc.author_id = ? LIMIT 2\", 185))\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT ROW_NUMBER() OVER (PARTITION BY ?) FROM posts_comments AS pc GROUP BY pc.post_id\", c.post_id))\n # |> distinct([c, a], c.post_id)\n # |> join(:inner_lateral, [c, a], x in fragment(\"SELECT * FROM posts_comments JOIN users ON users.id = posts_comments.author_id WHERE post_id = ? LIMIT 2\", c.post_id))\n # |> join(:inner_lateral, [c, a], x in fragment(\"SELECT * FROM posts_comments JOIN users ON users.id = posts_comments.author_id LIMIT 3\"))\n # |> select([c,a,x], {c.post_id, x.author_id})\n # |> select([c,a,x], {c.post_id, a.id})\n # |> where([c, a], a.row_number < 3)\n # |> join(:inner, [c], a in assoc(c, :author))\n # |> join(:inner, [c], a in subquery(isubquery))\n # |> group_by([c, a, x], x.author_id)\n # |> distinct([c, a], a.author_id)\n # |> select([c, a], {c.post_id, a.author_id})\n # |> select([c, a], {c.post_id, fragment(\"max(?) OVER (PARTITION BY ?)\", a.id, a.id)})\n # |> select([c, a], %{post_id: c.post_id, user: fragment(\"max(?) OVER (PARTITION BY ?)\", a.id, a.id)})\n # |> select([c, a], fragment(\"SELECT ROW_NUMBER() OVER (PARTITION BY ?) FROM cms_authors AS r , \", a.id))\n # |> join([c], c in subquery(sq), on: c.post_id == bq.id)\n # |> having([c, a], count(\"*\") < 10)\n # |> having([c, a], a.id < 180)\n # |> limit(3)\n # |> order_by([p, s], desc: fragment(\"count(?)\", s.id))\n # |> distinct([c, a], a.id)\n # |> Repo.all(repo_opts)\n # |> IO.inspect(label: \"get fuck\")\n # |> bat_man()\n\n for id <- post_ids, do: Map.get(results, id, [])\n end\n\n # TODO: use meta-programing to extract all query below\n # --------------------\n def bat_man(data) do\n # TODO refactor later\n data\n |> Enum.group_by(fn {x, _} -> x end)\n |> Enum.map(fn {x, y} ->\n {x,\n Enum.reduce(y, [], fn kv, acc ->\n {_, v} = kv\n acc ++ [v]\n end)}\n end)\n |> Map.new()\n end\n\n def query(Author, _args) do\n # you cannot use preload with select together\n # https://stackoverflow.com/questions/43010352/ecto-select-relations-from-preload\n # see also\n # https://github.com/elixir-ecto/ecto/issues/1145\n from(a in Author, join: u in assoc(a, :user), select: u)\n end\n\n def query({\"communities_threads\", CommunityThread}, _info) do\n from(\n ct in CommunityThread,\n join: t in assoc(ct, :thread),\n order_by: [asc: t.index],\n select: t\n )\n end\n\n @doc \"\"\"\n get unique participators join in comments\n \"\"\"\n def query({\"posts_comments\", PostComment}, %{filter: filter, unique: true}) do\n # def query({\"posts_comments\", PostComment}, %{unique: true}) do\n PostComment\n # |> QueryBuilder.members_pack(args)\n |> QueryBuilder.filter_pack(filter)\n |> join(:inner, [c], a in assoc(c, :author))\n |> distinct([c, a], a.id)\n |> select([c, a], a)\n end\n\n def query({\"posts_comments\", PostComment}, %{count: _, unique: true}) do\n # TODO: not very familar with SQL, but it has to be 2 group_by to work, check later\n # and the expect count should be the length of reault\n PostComment\n |> join(:inner, [c], a in assoc(c, :author))\n |> distinct([c, a], a.id)\n |> group_by([c, a], a.id)\n |> group_by([c, a], c.post_id)\n |> select([c, a], count(c.id))\n end\n\n def query({\"posts_comments\", PostComment}, %{count: _}) do\n PostComment\n |> group_by([c], c.post_id)\n |> select([c], count(c.id))\n end\n\n # def query({\"posts_comments\", PostComment}, %{filter: %{first: first}} = filter) do\n def query({\"posts_comments\", PostComment}, %{filter: filter}) do\n PostComment\n # |> limit(3)\n |> QueryBuilder.filter_pack(filter)\n end\n\n @doc \"\"\"\n handle query:\n 1. bacic filter of pagi,when,sort ...\n 2. count of the reactions\n 3. check is viewer reacted\n \"\"\"\n def query({\"posts_favorites\", PostFavorite}, args) do\n PostFavorite |> QueryBuilder.members_pack(args)\n end\n\n def query({\"posts_stars\", PostStar}, args) do\n PostStar |> QueryBuilder.members_pack(args)\n end\n\n def query({\"communities_subscribers\", CommunitySubscriber}, args) do\n CommunitySubscriber |> QueryBuilder.members_pack(args)\n end\n\n def query({\"communities_editors\", CommunityEditor}, args) do\n CommunityEditor |> QueryBuilder.members_pack(args)\n end\n\n # for comments replies, likes, repliesCount, likesCount...\n def query({\"posts_comments_replies\", PostCommentReply}, %{count: _}) do\n PostCommentReply\n |> group_by([c], c.post_comment_id)\n |> select([c], count(c.id))\n end\n\n def query({\"posts_comments_replies\", PostCommentReply}, %{filter: filter}) do\n PostCommentReply\n |> QueryBuilder.load_inner_replies(filter)\n end\n\n def query({\"posts_comments_replies\", PostCommentReply}, %{reply_to: _}) do\n PostCommentReply\n |> join(:inner, [c], r in assoc(c, :post_comment))\n |> select([c, r], r)\n end\n\n def query({\"posts_comments_likes\", PostCommentLike}, %{count: _}) do\n PostCommentLike\n |> group_by([f], f.post_comment_id)\n |> select([f], count(f.id))\n end\n\n def query({\"posts_comments_likes\", PostCommentLike}, %{viewer_did: _, cur_user: cur_user}) do\n PostCommentLike |> where([f], f.user_id == ^cur_user.id)\n end\n\n def query({\"posts_comments_likes\", PostCommentLike}, %{filter: _filter} = args) do\n PostCommentLike\n |> QueryBuilder.members_pack(args)\n end\n\n def query({\"posts_comments_dislikes\", PostCommentDislike}, %{count: _}) do\n PostCommentDislike\n |> group_by([f], f.post_comment_id)\n |> select([f], count(f.id))\n end\n\n # component dislikes\n def query({\"posts_comments_dislikes\", PostCommentDislike}, %{viewer_did: _, cur_user: cur_user}) do\n PostCommentDislike |> where([f], f.user_id == ^cur_user.id)\n end\n\n def query({\"posts_comments_dislikes\", PostCommentDislike}, %{filter: _filter} = args) do\n PostCommentDislike\n |> QueryBuilder.members_pack(args)\n end\n\n # ---- job comments ------\n def query({\"jobs_comments_replies\", JobCommentReply}, %{count: _}) do\n JobCommentReply\n |> group_by([c], c.job_comment_id)\n |> select([c], count(c.id))\n end\n\n def query({\"jobs_comments_replies\", JobCommentReply}, %{filter: filter}) do\n JobCommentReply\n |> QueryBuilder.load_inner_replies(filter)\n end\n\n def query({\"jobs_comments_replies\", JobCommentReply}, %{reply_to: _}) do\n JobCommentReply\n |> join(:inner, [c], r in assoc(c, :job_comment))\n |> select([c, r], r)\n end\n\n # ---- job ------\n\n # default loader\n def query(queryable, _args) do\n # IO.inspect(queryable, label: \"default loader\")\n # IO.inspect(args, label: \"default args\")\n queryable\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,153,null,null,null,null,null,null,null,null,null,null,null,null,null,76,null,null],"name":"lib/groupher_server/cms/community_subscriber.ex","source":"defmodule GroupherServer.CMS.CommunitySubscriber do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Community\n\n @required_fields ~w(user_id community_id)a\n\n @type t :: %CommunitySubscriber{}\n schema \"communities_subscribers\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:community, Community, foreign_key: :community_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%CommunitySubscriber{} = community_subscriber, attrs) do\n community_subscriber\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:community_id)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user_id, name: :communities_subscribers_user_id_community_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,20,null,null,null,2,null,null,null,14,null,null,null,2,null,null,null,null,34,34,null,34,null,1,null,null,33,null,33,null,null,null,null,null,4,4,4,4,null,null,null],"name":"lib/groupher_server/cms/delegates/comment_reaction.ex","source":"defmodule GroupherServer.CMS.Delegate.CommentReaction do\n import GroupherServer.CMS.Helper.MatcherOld\n\n alias GroupherServer.Accounts\n alias Helper.ORM\n\n def like_comment(thread, comment_id, %Accounts.User{id: user_id}) do\n feel_comment(thread, comment_id, user_id, :like)\n end\n\n def undo_like_comment(thread, comment_id, %Accounts.User{id: user_id}) do\n undo_feel_comment(thread, comment_id, user_id, :like)\n end\n\n def dislike_comment(thread, comment_id, %Accounts.User{id: user_id}) do\n feel_comment(thread, comment_id, user_id, :dislike)\n end\n\n def undo_dislike_comment(thread, comment_id, %Accounts.User{id: user_id}) do\n undo_feel_comment(thread, comment_id, user_id, :dislike)\n end\n\n defp feel_comment(thread, comment_id, user_id, feeling)\n when valid_feeling(feeling) do\n with {:ok, action} <- match_action(thread, feeling) do\n clause = %{post_comment_id: comment_id, user_id: user_id}\n\n case ORM.find_by(action.reactor, clause) do\n {:ok, _} ->\n {:error, \"user has #{to_string(feeling)}d this comment\"}\n\n {:error, _} ->\n action.reactor |> ORM.create(clause)\n\n ORM.find(action.target, comment_id)\n end\n end\n end\n\n defp undo_feel_comment(thread, comment_id, user_id, feeling) do\n with {:ok, action} <- match_action(thread, feeling) do\n clause = %{post_comment_id: comment_id, user_id: user_id}\n ORM.findby_delete!(action.reactor, clause)\n ORM.find(action.target, comment_id)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/helper/spec_type.ex","source":"defmodule Helper.Types do\n @moduledoc \"\"\"\n custom @types\n \"\"\"\n\n @typedoc \"\"\"\n Type GraphQL flavor the error format\n \"\"\"\n @type gq_error :: {:error, [message: String.t(), code: non_neg_integer()]}\n\n @typedoc \"\"\"\n general response conventions\n \"\"\"\n @type done :: {:ok, map} | {:error, map}\n\n @type id :: non_neg_integer() | String.t()\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/statistics/statistics_queries.ex","source":"defmodule GroupherServerWeb.Schema.Statistics.Queries do\n @moduledoc \"\"\"\n Statistics.Queries\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :statistics_queries do\n @desc \"list of user contribute in last 6 month\"\n field :user_contributes, list_of(:user_contribute) do\n arg(:id, non_null(:id))\n\n resolve(&R.Statistics.list_contributes/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,5273,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,null,null,null,null,null],"name":"lib/groupher_server/cms/community.ex","source":"defmodule GroupherServer.CMS.Community do\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.CMS.{\n Category,\n Post,\n Video,\n Repo,\n Job,\n CommunityThread,\n CommunitySubscriber,\n CommunityEditor\n }\n\n alias GroupherServer.Accounts\n\n @required_fields ~w(title desc user_id logo raw)a\n # @required_fields ~w(title desc user_id)a\n @optional_fields ~w(label)a\n\n schema \"communities\" do\n field(:title, :string)\n field(:desc, :string)\n field(:logo, :string)\n # field(:category, :string)\n field(:label, :string)\n field(:raw, :string)\n\n belongs_to(:author, Accounts.User, foreign_key: :user_id)\n\n has_many(:threads, {\"communities_threads\", CommunityThread})\n has_many(:subscribers, {\"communities_subscribers\", CommunitySubscriber})\n has_many(:editors, {\"communities_editors\", CommunityEditor})\n\n many_to_many(\n :categories,\n Category,\n join_through: \"communities_categories\",\n join_keys: [community_id: :id, category_id: :id],\n # :delete_all will only remove data from the join source\n on_delete: :delete_all\n # on_replace: :delete\n )\n\n many_to_many(\n :posts,\n Post,\n join_through: \"communities_posts\",\n join_keys: [community_id: :id, post_id: :id]\n )\n\n many_to_many(\n :videos,\n Video,\n join_through: \"communities_videos\",\n join_keys: [community_id: :id, video_id: :id]\n )\n\n many_to_many(\n :repos,\n Repo,\n join_through: \"communities_repos\",\n join_keys: [community_id: :id, repo_id: :id]\n )\n\n many_to_many(\n :jobs,\n Job,\n join_through: \"communities_jobs\",\n join_keys: [community_id: :id, job_id: :id]\n )\n\n # posts_managers\n # jobs_managers\n # tuts_managers\n # videos_managers\n #\n # posts_block_list ...\n # videos_block_list ...\n timestamps(type: :utc_datetime)\n end\n\n def changeset(%Community{} = community, attrs) do\n # |> cast_assoc(:author)\n # |> unique_constraint(:title, name: :communities_title_index)\n community\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> validate_length(:title, min: 3, max: 30)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:title, name: :communities_title_index)\n\n # |> foreign_key_constraint(:communities_author_fkey)\n # |> unique_constraint(:user_id, name: :posts_favorites_user_id_post_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,1,0,null,null,4,null,null,3,3,null,null,2,null,2,null,null,null,null,9,3,3,2,null,16,14,14,9,null,null,12,null,null,null,4,null,5,null,null,1,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,null,null,0,null,null,null,0,null,null,null,0,null,null,null,null,null,2,null,null,1,null,null,1,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,null,3,null,null,1,null,null,1,null,null,null,1,null,null,null,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,null,null,3,null,null,1,null,1,null,null,10,null,null,null,1,null,null,1,null,null,null,1,null,null,null,null,null,null,1,null,null,null,null,null,2,null,null,null,2,null,null,null,1,null,null,null,5,null,null,null,1,null,null,null,null,null,null,10,null,null,8,null,null,null,3,null,null,null,2,null,null,null,2,null,null,null,1,null,null,null,2,null,null,null,1,null,null,null,3,null,null],"name":"lib/groupher_server_web/resolvers/cms_resolver.ex","source":"defmodule GroupherServerWeb.Resolvers.CMS do\n @moduledoc false\n\n import ShortMaps\n import Ecto.Query, warn: false\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.CMS\n alias GroupherServer.CMS.{Post, Video, Repo, Job, Community, Category, Tag, Thread}\n alias Helper.ORM\n\n # #######################\n # community ..\n # #######################\n def community(_root, %{id: id}, _info), do: Community |> ORM.find(id)\n def community(_root, %{title: title}, _info), do: Community |> ORM.find_by(title: title)\n def community(_root, %{raw: raw}, _info), do: Community |> ORM.find_by(raw: raw)\n\n def community(_root, _args, _info), do: {:error, \"please provide community id or title or raw\"}\n def paged_communities(_root, ~m(filter)a, _info), do: Community |> ORM.find_all(filter)\n\n def create_community(_root, args, %{context: %{cur_user: user}}) do\n args = args |> Map.merge(%{user_id: user.id})\n Community |> ORM.create(args)\n end\n\n def update_community(_root, args, _info), do: Community |> ORM.find_update(args)\n\n def delete_community(_root, %{id: id}, _info), do: Community |> ORM.find_delete!(id)\n\n # #######################\n # community thread (post, job)\n # #######################\n def post(_root, %{id: id}, _info), do: Post |> ORM.read(id, inc: :views)\n def video(_root, %{id: id}, _info), do: Video |> ORM.read(id, inc: :views)\n def repo(_root, %{id: id}, _info), do: Repo |> ORM.read(id, inc: :views)\n def job(_root, %{id: id}, _info), do: Job |> ORM.read(id, inc: :views)\n\n def paged_posts(_root, ~m(filter)a, _info), do: Post |> CMS.paged_contents(filter)\n def paged_videos(_root, ~m(filter)a, _info), do: Video |> CMS.paged_contents(filter)\n def paged_repos(_root, ~m(filter)a, _info), do: Repo |> CMS.paged_contents(filter)\n def paged_jobs(_root, ~m(filter)a, _info), do: Job |> ORM.find_all(filter)\n\n def create_article(_root, ~m(community_id thread)a = args, %{context: %{cur_user: user}}) do\n CMS.create_article(%Community{id: community_id}, thread, args, user)\n end\n\n def update_article(_root, %{passport_source: content} = args, _info),\n do: ORM.update(content, args)\n\n def delete_content(_root, %{passport_source: content}, _info), do: ORM.delete(content)\n\n def pin_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{pin: true}, user)\n end\n\n def undo_pin_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{pin: false}, user)\n end\n\n def trash_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{markDelete: true}, user)\n end\n\n def undo_trash_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{markDelete: false}, user)\n end\n\n # #######################\n # thread reaction ..\n # #######################\n def reaction(_root, ~m(id thread action)a, %{context: %{cur_user: user}}) do\n CMS.reaction(thread, action, id, user)\n end\n\n def undo_reaction(_root, ~m(id thread action)a, %{context: %{cur_user: user}}) do\n CMS.undo_reaction(thread, action, id, user)\n end\n\n def reaction_users(_root, ~m(id action thread filter)a, _info) do\n CMS.reaction_users(thread, action, id, filter)\n end\n\n # #######################\n # category ..\n # #######################\n def paged_categories(_root, ~m(filter)a, _info), do: Category |> ORM.find_all(filter)\n\n def create_category(_root, ~m(title raw)a, %{context: %{cur_user: user}}) do\n CMS.create_category(%Category{title: title, raw: raw}, user)\n end\n\n def delete_category(_root, %{id: id}, _info), do: Category |> ORM.find_delete!(id)\n\n def update_category(_root, ~m(id title)a, %{context: %{cur_user: _}}) do\n CMS.update_category(~m(%Category id title)a)\n end\n\n def set_category(_root, ~m(community_id category_id)a, %{context: %{cur_user: _}}) do\n CMS.set_category(%Community{id: community_id}, %Category{id: category_id})\n end\n\n def unset_category(_root, ~m(community_id category_id)a, %{context: %{cur_user: _}}) do\n CMS.unset_category(%Community{id: community_id}, %Category{id: category_id})\n end\n\n # #######################\n # thread ..\n # #######################\n def paged_threads(_root, ~m(filter)a, _info), do: Thread |> ORM.find_all(filter)\n\n def create_thread(_root, ~m(title raw index)a, _info),\n do: CMS.create_thread(~m(title raw index)a)\n\n def set_thread(_root, ~m(community_id thread_id)a, _info) do\n CMS.set_thread(%Community{id: community_id}, %Thread{id: thread_id})\n end\n\n def unset_thread(_root, ~m(community_id thread_id)a, _info) do\n CMS.unset_thread(%Community{id: community_id}, %Thread{id: thread_id})\n end\n\n # #######################\n # editors ..\n # #######################\n def set_editor(_root, ~m(community_id user_id title)a, _) do\n CMS.set_editor(%Community{id: community_id}, title, %User{id: user_id})\n end\n\n def unset_editor(_root, ~m(community_id user_id)a, _) do\n CMS.unset_editor(%Community{id: community_id}, %User{id: user_id})\n end\n\n def update_editor(_root, ~m(community_id user_id title)a, _) do\n CMS.update_editor(%Community{id: community_id}, title, %User{id: user_id})\n end\n\n def community_editors(_root, ~m(id filter)a, _info) do\n CMS.community_members(:editors, %Community{id: id}, filter)\n end\n\n # #######################\n # tags ..\n # #######################\n def create_tag(_root, args, %{context: %{cur_user: user}}) do\n CMS.create_tag(args.thread, args, user)\n end\n\n def delete_tag(_root, %{id: id}, _info), do: Tag |> ORM.find_delete!(id)\n\n def update_tag(_root, args, _info), do: CMS.update_tag(args)\n\n def set_tag(_root, ~m(community_id thread id tag_id)a, _info) do\n CMS.set_tag(%Community{id: community_id}, thread, %Tag{id: tag_id}, id)\n end\n\n def unset_tag(_root, ~m(id thread tag_id)a, _info),\n do: CMS.unset_tag(thread, %Tag{id: tag_id}, id)\n\n def get_tags(_root, ~m(community_id thread)a, _info) do\n CMS.get_tags(%Community{id: community_id}, thread)\n end\n\n def get_tags(_root, ~m(community thread)a, _info) do\n CMS.get_tags(%Community{raw: community}, thread)\n end\n\n def get_tags(_root, %{thread: _thread}, _info) do\n {:error, \"community_id or community is needed\"}\n end\n\n def get_tags(_root, ~m(filter)a, _info), do: CMS.get_tags(filter)\n\n # #######################\n # community subscribe ..\n # #######################\n def subscribe_community(_root, ~m(community_id)a, %{context: %{cur_user: cur_user}}) do\n CMS.subscribe_community(%Community{id: community_id}, cur_user)\n end\n\n def unsubscribe_community(_root, ~m(community_id)a, %{context: %{cur_user: cur_user}}) do\n CMS.unsubscribe_community(%Community{id: community_id}, cur_user)\n end\n\n def community_subscribers(_root, ~m(id filter)a, _info) do\n CMS.community_members(:subscribers, %Community{id: id}, filter)\n end\n\n def mirror_article(_root, ~m(thread id community_id)a, _info) do\n CMS.mirror_article(%Community{id: community_id}, thread, id)\n end\n\n def unmirror_article(_root, ~m(thread id community_id)a, _info) do\n CMS.unmirror_article(%Community{id: community_id}, thread, id)\n end\n\n # #######################\n # comemnts ..\n # #######################\n def paged_comments(_root, ~m(id thread filter)a, _info),\n do: CMS.paged_comments(thread, id, filter)\n\n def create_comment(_root, ~m(thread id body)a, %{context: %{cur_user: user}}) do\n CMS.create_comment(thread, id, body, user)\n end\n\n def delete_comment(_root, ~m(thread id)a, _info) do\n CMS.delete_comment(thread, id)\n end\n\n def reply_comment(_root, ~m(thread id body)a, %{context: %{cur_user: user}}) do\n CMS.reply_comment(thread, id, body, user)\n end\n\n def like_comment(_root, ~m(thread id)a, %{context: %{cur_user: user}}) do\n CMS.like_comment(thread, id, user)\n end\n\n def undo_like_comment(_root, ~m(thread id)a, %{context: %{cur_user: user}}) do\n CMS.undo_like_comment(thread, id, user)\n end\n\n def dislike_comment(_root, ~m(thread id)a, %{context: %{cur_user: user}}) do\n CMS.dislike_comment(thread, id, user)\n end\n\n def undo_dislike_comment(_root, ~m(thread id)a, %{context: %{cur_user: user}}) do\n CMS.undo_dislike_comment(thread, id, user)\n end\n\n def stamp_passport(_root, ~m(user_id rules)a, %{context: %{cur_user: _user}}) do\n CMS.stamp_passport(rules, %User{id: user_id})\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,0,0,null,null,null,null,null,null,null,0,0,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,0,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/cms_types.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Types do\n @moduledoc \"\"\"\n cms types used in queries & mutations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n import GroupherServerWeb.Schema.Helper.Fields\n import Ecto.Query, warn: false\n import Absinthe.Resolution.Helpers, only: [dataloader: 2, on_load: 2]\n\n alias GroupherServer.CMS\n alias GroupherServerWeb.Schema\n\n import_types(Schema.CMS.Misc)\n\n object :idlike do\n field(:id, :id)\n end\n\n object :comment do\n field(:id, :id)\n field(:body, :string)\n field(:floor, :integer)\n field(:author, :user, resolve: dataloader(CMS, :author))\n\n field :reply_to, :comment do\n resolve(dataloader(CMS, :reply_to))\n end\n\n field :likes, list_of(:user) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :likes))\n end\n\n field :likes_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(CMS, :likes))\n middleware(M.ConvertToInt)\n end\n\n field :viewer_has_liked, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n # put current user into dataloader's args\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :likes))\n middleware(M.ViewerDidConvert)\n end\n\n field :dislikes, list_of(:user) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :dislikes))\n end\n\n field :viewer_has_disliked, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n # put current user into dataloader's args\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :dislikes))\n middleware(M.ViewerDidConvert)\n end\n\n field :dislikes_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(CMS, :dislikes))\n middleware(M.ConvertToInt)\n end\n\n field :replies, list_of(:comment) do\n arg(:filter, :members_filter)\n\n middleware(M.ForceLoader)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :replies))\n end\n\n field :replies_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(CMS, :replies))\n middleware(M.ConvertToInt)\n end\n\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :post do\n interface(:article)\n field(:id, :id)\n field(:title, :string)\n field(:digest, :string)\n field(:length, :integer)\n field(:link_addr, :string)\n field(:body, :string)\n field(:views, :integer)\n field(:pin, :boolean)\n field(:markDelete, :boolean)\n field(:tags, list_of(:article_tag), resolve: dataloader(CMS, :tags))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n\n field :comments, list_of(:comment) do\n arg(:filter, :members_filter)\n\n # middleware(M.ForceLoader)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :comments))\n end\n\n field :comments_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(CMS, :comments))\n middleware(M.ConvertToInt)\n end\n\n field :comments_participators, list_of(:user) do\n arg(:filter, :members_filter)\n arg(:unique, :unique_type, default_value: true)\n\n middleware(M.ForceLoader)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :comments))\n end\n\n field :comments_participators2, list_of(:user) do\n arg(:filter, :members_filter)\n arg(:unique, :unique_type, default_value: true)\n\n middleware(M.PageSizeProof)\n\n resolve(fn post, _args, %{context: %{loader: loader}} ->\n loader\n |> Dataloader.load(CMS, {:many, CMS.PostComment}, cp_users: post.id)\n |> on_load(fn loader ->\n {:ok, Dataloader.get(loader, CMS, {:many, CMS.PostComment}, cp_users: post.id)}\n end)\n end)\n end\n\n field :comments_participators_count, :integer do\n resolve(fn post, _args, %{context: %{loader: loader}} ->\n loader\n |> Dataloader.load(CMS, {:one, CMS.PostComment}, cp_count: post.id)\n |> on_load(fn loader ->\n {:ok, Dataloader.get(loader, CMS, {:one, CMS.PostComment}, cp_count: post.id)}\n end)\n end)\n end\n\n field :comments_participators_count_wired, :integer do\n arg(:unique, :unique_type, default_value: true)\n arg(:count, :count_type, default_value: :count)\n\n # middleware(M.ForceLoader)\n resolve(dataloader(CMS, :comments))\n # middleware(M.CountLength)\n end\n\n field :viewer_has_favorited, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n # put current user into dataloader's args\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :favorites))\n middleware(M.ViewerDidConvert)\n end\n\n field :viewer_has_starred, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :stars))\n middleware(M.ViewerDidConvert)\n end\n\n field :favorited_users, list_of(:user) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :favorites))\n end\n\n field :favorited_count, :integer do\n arg(:count, :count_type, default_value: :count)\n arg(:type, :post_type, default_value: :post)\n # middleware(M.SeeMe)\n resolve(dataloader(CMS, :favorites))\n middleware(M.ConvertToInt)\n end\n\n field :starred_count, :integer do\n arg(:count, :count_type, default_value: :count)\n arg(:type, :post_type, default_value: :post)\n\n resolve(dataloader(CMS, :stars))\n middleware(M.ConvertToInt)\n end\n\n field :starred_users, list_of(:user) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :stars))\n end\n end\n\n object :video do\n interface(:article)\n field(:id, :id)\n field(:title, :string)\n field(:desc, :string)\n field(:duration, :string)\n field(:author, :user, resolve: dataloader(CMS, :author))\n\n field(:source, :string)\n field(:link, :string)\n field(:original_author, :string)\n field(:original_author_link, :string)\n field(:views, :integer)\n\n field(:pin, :boolean)\n field(:markDelete, :boolean)\n\n # field(:tags, list_of(:article_tag), resolve: dataloader(CMS, :tags))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :repo do\n # interface(:article)\n field(:id, :id)\n field(:repo_name, :string)\n field(:desc, :string)\n field(:readme, :string)\n field(:language, :string)\n field(:author, :user, resolve: dataloader(CMS, :author))\n\n field(:repo_link, :string)\n field(:producer, :string)\n field(:producer_link, :integer)\n\n field(:repo_star_count, :integer)\n field(:repo_fork_count, :integer)\n field(:repo_watch_count, :integer)\n field(:views, :integer)\n\n field(:pin, :boolean)\n field(:markDelete, :boolean)\n\n # field(:tags, list_of(:article_tag), resolve: dataloader(CMS, :tags))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :job do\n interface(:article)\n field(:id, :id)\n field(:title, :string)\n field(:desc, :string)\n field(:company, :string)\n field(:company_logo, :string)\n field(:digest, :string)\n field(:location, :string)\n field(:length, :integer)\n field(:link_addr, :string)\n field(:body, :string)\n field(:views, :integer)\n\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:tags, list_of(:article_tag), resolve: dataloader(CMS, :tags))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :thread do\n field(:id, :id)\n field(:title, :string)\n field(:raw, :string)\n field(:index, :integer)\n end\n\n object :contribute do\n field(:date, :date)\n field(:count, :integer)\n end\n\n object :contribute_map do\n field(:start_date, :date)\n field(:end_date, :date)\n field(:total_count, :integer)\n field(:records, list_of(:contribute))\n end\n\n object :community do\n # meta(:cache, max_age: 30)\n\n field(:id, :id)\n field(:title, :string)\n field(:desc, :string)\n field(:raw, :string)\n field(:logo, :string)\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:threads, list_of(:thread), resolve: dataloader(CMS, :threads))\n field(:categories, list_of(:category), resolve: dataloader(CMS, :categories))\n\n # Big thanks: https://elixirforum.com/t/grouping-error-in-absinthe-dadaloader/13671/2\n # see also: https://github.com/absinthe-graphql/dataloader/issues/25\n field :posts_count, :integer do\n resolve(fn community, _args, %{context: %{loader: loader}} ->\n loader\n |> Dataloader.load(CMS, {:one, CMS.Post}, posts_count: community.id)\n |> on_load(fn loader ->\n {:ok, Dataloader.get(loader, CMS, {:one, CMS.Post}, posts_count: community.id)}\n end)\n end)\n end\n\n field :subscribers, list_of(:user) do\n arg(:filter, :members_filter)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :subscribers))\n end\n\n field :subscribers_count, :integer do\n arg(:count, :count_type, default_value: :count)\n arg(:type, :community_type, default_value: :community)\n resolve(dataloader(CMS, :subscribers))\n middleware(M.ConvertToInt)\n end\n\n field :viewer_has_subscribed, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :subscribers))\n middleware(M.ViewerDidConvert)\n end\n\n field :editors, list_of(:user) do\n arg(:filter, :members_filter)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :editors))\n end\n\n field :editors_count, :integer do\n arg(:count, :count_type, default_value: :count)\n arg(:type, :community_type, default_value: :community)\n resolve(dataloader(CMS, :editors))\n middleware(M.ConvertToInt)\n end\n\n field :contributes, list_of(:contribute) do\n # TODO add complex here to warning N+1 problem\n resolve(&R.Statistics.list_contributes/3)\n end\n\n field :contributes_digest, list_of(:integer) do\n # TODO add complex here to warning N+1 problem\n resolve(&R.Statistics.list_contributes_digest/3)\n end\n end\n\n object :category do\n field(:id, :id)\n field(:title, :string)\n field(:raw, :string)\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :article_tag do\n field(:id, :id)\n field(:title, :string)\n field(:color, :string)\n field(:thread, :string)\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:community, :community, resolve: dataloader(CMS, :community))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :paged_categories do\n field(:entries, list_of(:category))\n pagination_fields()\n end\n\n object :paged_posts do\n field(:entries, list_of(:post))\n pagination_fields()\n end\n\n object :paged_videos do\n field(:entries, list_of(:video))\n pagination_fields()\n end\n\n object :paged_repos do\n field(:entries, list_of(:repo))\n pagination_fields()\n end\n\n object :paged_jobs do\n field(:entries, list_of(:job))\n pagination_fields()\n end\n\n object :paged_comments do\n field(:entries, list_of(:comment))\n pagination_fields()\n end\n\n object :paged_communities do\n field(:entries, list_of(:community))\n pagination_fields()\n end\n\n object :paged_tags do\n field(:entries, list_of(:article_tag))\n pagination_fields()\n end\n\n object :paged_threads do\n field(:entries, list_of(:thread))\n pagination_fields()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/cms_queries.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Queries do\n @moduledoc \"\"\"\n CMS queries\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_queries do\n field :community, :community do\n # arg(:id, non_null(:id))\n arg(:id, :id)\n arg(:title, :string)\n arg(:raw, :string)\n resolve(&R.CMS.community/3)\n end\n\n @desc \"communities with pagination info\"\n field :paged_communities, :paged_communities do\n arg(:filter, non_null(:communities_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_communities/3)\n end\n\n @desc \"paged subscribers of a community\"\n field :community_subscribers, :paged_users do\n arg(:id, non_null(:id))\n arg(:filter, :paged_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.community_subscribers/3)\n end\n\n @desc \"paged subscribers of a community\"\n field :community_editors, :paged_users do\n arg(:id, non_null(:id))\n arg(:filter, :paged_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.community_editors/3)\n end\n\n @desc \"get all categories\"\n field :paged_categories, :paged_categories do\n arg(:filter, :paged_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_categories/3)\n end\n\n @desc \"get all the threads across all communities\"\n field :paged_threads, :paged_threads do\n arg(:filter, :threads_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_threads/3)\n end\n\n @desc \"get post by id\"\n field :post, non_null(:post) do\n arg(:id, non_null(:id))\n resolve(&R.CMS.post/3)\n end\n\n @desc \"get paged posts\"\n field :paged_posts, :paged_posts do\n arg(:filter, non_null(:paged_article_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_posts/3)\n end\n\n @desc \"get video by id\"\n field :video, non_null(:video) do\n arg(:id, non_null(:id))\n resolve(&R.CMS.video/3)\n end\n\n @desc \"get paged videos\"\n field :paged_videos, :paged_videos do\n arg(:filter, non_null(:paged_article_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_videos/3)\n end\n\n @desc \"get repo by id\"\n field :repo, non_null(:repo) do\n arg(:id, non_null(:id))\n resolve(&R.CMS.repo/3)\n end\n\n @desc \"get paged videos\"\n field :paged_repos, :paged_repos do\n arg(:filter, non_null(:paged_article_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_repos/3)\n end\n\n @desc \"get job by id\"\n field :job, non_null(:job) do\n arg(:id, non_null(:id))\n resolve(&R.CMS.job/3)\n end\n\n @desc \"get paged jobs\"\n field :paged_jobs, :paged_jobs do\n arg(:filter, non_null(:paged_article_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_jobs/3)\n end\n\n field :favorite_users, :paged_users do\n arg(:id, non_null(:id))\n arg(:type, :thread, default_value: :post)\n arg(:action, :favorite_action, default_value: :favorite)\n arg(:filter, :paged_article_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.reaction_users/3)\n end\n\n # get all tags\n field :paged_tags, :paged_tags do\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.get_tags/3)\n end\n\n # TODO: remove\n field :tags, :paged_tags do\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n # TODO: should be passport\n resolve(&R.CMS.get_tags/3)\n end\n\n # partial\n @desc \"get paged tags belongs to community_id or community\"\n field :partial_tags, list_of(:article_tag) do\n arg(:community_id, :id)\n arg(:community, :string)\n arg(:thread, :thread, default_value: :post)\n\n resolve(&R.CMS.get_tags/3)\n end\n\n @desc \"get paged comments\"\n field :paged_comments, :paged_comments do\n arg(:id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n arg(:filter, :comments_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_comments/3)\n end\n\n # comments\n field :comments, :paged_comments do\n arg(:id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n arg(:filter, :comments_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_comments/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,5,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/purchase.ex","source":"defmodule GroupherServer.Accounts.Purchase do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id)a\n @optional_fields ~w(theme community_chart brainwash_free)a\n\n @type t :: %Purchase{}\n schema \"purchases\" do\n belongs_to(:user, User)\n\n field(:theme, :boolean)\n field(:community_chart, :boolean)\n field(:brainwash_free, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Purchase{} = purchase, attrs) do\n purchase\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,1,null,null,null,0,null,null,null,0,null,null,null,0,null,null,null,3,null,null],"name":"lib/groupher_server_web/resolvers/statistics_resolver.ex","source":"defmodule GroupherServerWeb.Resolvers.Statistics do\n @moduledoc \"\"\"\n resolvers for Statistics\n \"\"\"\n alias GroupherServer.{Accounts, CMS, Statistics}\n # alias Helper.ORM\n\n # tmp for test\n def list_contributes(_root, %{id: id}, _info) do\n Statistics.list_contributes(%Accounts.User{id: id})\n end\n\n def list_contributes(%Accounts.User{id: id}, _args, _info) do\n Statistics.list_contributes(%Accounts.User{id: id})\n end\n\n def list_contributes(%CMS.Community{id: id}, _args, _info) do\n Statistics.list_contributes(%CMS.Community{id: id})\n end\n\n def list_contributes_digest(%CMS.Community{id: id}, _args, _info) do\n Statistics.list_contributes_digest(%CMS.Community{id: id})\n end\n\n def make_contrubute(_root, %{user_id: user_id}, _info) do\n Statistics.make_contribute(%Accounts.User{id: user_id})\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,99,98,null,null,null,null,null,null,null,null,null,null,95,null,null,97,null,null,null,null,null,null,null,null,null,null,null,95,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,4,4,null,null,4,null,null,4,null,null,4,null,null,4,null,null,null,null,null,null,null,null,null,null,4,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,3,3,null,null,null,null,null,null,null,null,3,3,3,null,null,null,null,null,6,null,null,6,null,null],"name":"lib/groupher_server/accounts/delegates/fans.ex","source":"defmodule GroupherServer.Accounts.Delegate.Fans do\n @moduledoc \"\"\"\n user followers / following related\n \"\"\"\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1]\n import Helper.ErrorCode\n import ShortMaps\n\n alias Helper.{ORM, QueryBuilder, SpecType}\n alias GroupherServer.{Accounts, Repo}\n\n alias GroupherServer.Accounts.{User, UserFollower, UserFollowing}\n\n alias Ecto.Multi\n\n @doc \"\"\"\n follow a user\n \"\"\"\n @spec follow(User.t(), User.t()) :: {:ok, User.t()} | SpecType.gq_error()\n def follow(%User{id: user_id}, %User{id: follower_id}) do\n with true <- to_string(user_id) !== to_string(follower_id),\n {:ok, _follow_user} <- ORM.find(User, follower_id) do\n Multi.new()\n |> Multi.insert(\n :create_follower,\n UserFollower.changeset(%UserFollower{}, ~m(user_id follower_id)a)\n )\n |> Multi.insert(\n :create_following,\n UserFollowing.changeset(%UserFollowing{}, %{user_id: user_id, following_id: follower_id})\n )\n |> Multi.run(:add_achievement, fn _ ->\n Accounts.achieve(%User{id: follower_id}, :add, :follow)\n end)\n |> Repo.transaction()\n |> follow_result()\n else\n false ->\n {:error, [message: \"can't follow yourself\", code: ecode(:self_conflict)]}\n\n {:error, error} ->\n {:error, [message: error, code: ecode(:not_exsit)]}\n end\n end\n\n @spec follow_result({:ok, map()}) :: SpecType.done()\n defp follow_result({:ok, %{create_follower: user_follower}}) do\n User |> ORM.find(user_follower.follower_id)\n end\n\n defp follow_result({:error, :create_follower, _result, _steps}) do\n {:error, [message: \"already followed\", code: ecode(:already_did)]}\n end\n\n defp follow_result({:error, :create_following, _result, _steps}) do\n {:error, [message: \"follow fails\", code: ecode(:react_fails)]}\n end\n\n defp follow_result({:error, :add_achievement, _result, _steps}) do\n {:error, [message: \"follow acieve fails\", code: ecode(:react_fails)]}\n end\n\n @doc \"\"\"\n undo a follow action to a user\n \"\"\"\n @spec undo_follow(User.t(), User.t()) :: {:ok, User.t()} | SpecType.gq_error()\n def undo_follow(%User{id: user_id}, %User{id: follower_id}) do\n with true <- to_string(user_id) !== to_string(follower_id),\n {:ok, _follow_user} <- ORM.find(User, follower_id) do\n Multi.new()\n |> Multi.run(:delete_follower, fn _ ->\n ORM.findby_delete!(UserFollower, ~m(user_id follower_id)a)\n end)\n |> Multi.run(:delete_following, fn _ ->\n ORM.findby_delete!(UserFollowing, %{user_id: user_id, following_id: follower_id})\n end)\n |> Multi.run(:minus_achievement, fn _ ->\n Accounts.achieve(%User{id: follower_id}, :minus, :follow)\n end)\n |> Repo.transaction()\n |> undo_follow_result()\n else\n false ->\n {:error, [message: \"can't undo follow yourself\", code: ecode(:self_conflict)]}\n\n {:error, error} ->\n {:error, [message: error, code: ecode(:not_exsit)]}\n end\n end\n\n defp undo_follow_result({:ok, %{delete_follower: user_follower}}) do\n User |> ORM.find(user_follower.follower_id)\n end\n\n defp undo_follow_result({:error, :delete_follower, _result, _steps}) do\n {:error, [message: \"already unfollowed\", code: ecode(:already_did)]}\n end\n\n defp undo_follow_result({:error, :delete_following, _result, _steps}) do\n {:error, [message: \"unfollow fails\", code: ecode(:react_fails)]}\n end\n\n defp undo_follow_result({:error, :minus_achievement, _result, _steps}) do\n {:error, [message: \"follow acieve fails\", code: ecode(:react_fails)]}\n end\n\n @doc \"\"\"\n get paged followers of a user\n \"\"\"\n @spec fetch_followers(User.t(), map()) :: {:ok, map()} | {:error, String.t()}\n def fetch_followers(%User{id: user_id}, filter) do\n UserFollower\n |> where([uf], uf.follower_id == ^user_id)\n |> join(:inner, [uf], u in assoc(uf, :user))\n |> load_fans(filter)\n end\n\n @doc \"\"\"\n get paged followings of a user\n \"\"\"\n @spec fetch_followings(User.t(), map()) :: {:ok, map()} | {:error, String.t()}\n def fetch_followings(%User{id: user_id}, filter) do\n UserFollowing\n |> where([uf], uf.user_id == ^user_id)\n |> join(:inner, [uf], u in assoc(uf, :following))\n |> load_fans(filter)\n end\n\n @spec load_fans(Ecto.Queryable.t(), map()) :: {:ok, map()} | {:error, String.t()}\n defp load_fans(queryable, ~m(page size)a = filter) do\n queryable\n |> select([uf, u], u)\n |> QueryBuilder.filter_pack(filter)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,18,null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,null],"name":"lib/groupher_server/cms/community_thread.ex","source":"defmodule GroupherServer.CMS.CommunityThread do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.CMS.{Community, Thread}\n\n @required_fields ~w(community_id thread_id)a\n\n @type t :: %CommunityThread{}\n schema \"communities_threads\" do\n belongs_to(:community, Community, foreign_key: :community_id)\n belongs_to(:thread, Thread, foreign_key: :thread_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%CommunityThread{} = community_thread, attrs) do\n community_thread\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:community_id)\n |> foreign_key_constraint(:thread_id)\n |> unique_constraint(:community_id, name: :communities_threads_community_id_thread_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,176,null,12,null,null,164,null,null,null,null,164,null,16,148,null,164,null,null,0,162,null,14,null,null,162,null,150,null,null,null,12,null,null,null],"name":"lib/groupher_server_web/middleware/pagesize_proof.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.PageSizeProof do\n @behaviour Absinthe.Middleware\n\n import Helper.Utils, only: [handle_absinthe_error: 3, get_config: 2]\n import Helper.ErrorCode\n\n @max_page_size get_config(:general, :page_size)\n @inner_page_size get_config(:general, :inner_page_size)\n\n # 1. if has filter:first and filter:size -> makesure it not too large\n # 2. if not has filter: marge to default first: 5\n # 3. large size should trigger error\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(resolution, _) do\n # IO.inspect resolution.arguments, label: \"resolution arguments\"\n # IO.inspect valid_size(resolution.arguments), label: \"valid_size\"\n\n case valid_size(resolution.arguments) do\n {:error, msg} ->\n resolution |> handle_absinthe_error(msg, ecode(:pagination))\n\n arguments ->\n %{resolution | arguments: sort_desc_by_default(arguments)}\n end\n end\n\n defp sort_desc_by_default(%{filter: filter} = arguments) do\n filter =\n if Map.has_key?(filter, :sort),\n do: filter,\n else: filter |> Map.merge(%{sort: :desc_inserted})\n\n arguments |> Map.merge(%{filter: filter})\n end\n\n defp valid_size(%{filter: %{first: size}} = arg), do: do_size_check(size, arg)\n defp valid_size(%{filter: %{size: size}} = arg), do: do_size_check(size, arg)\n\n defp valid_size(arg), do: arg |> Map.merge(%{filter: %{first: @inner_page_size}})\n\n defp do_size_check(size, arg) do\n case size in 1..@max_page_size do\n true ->\n arg\n\n _ ->\n {:error,\n \"SIZE_RANGE_ERROR: size shuold between 0 and #{@max_page_size}, current: #{size}\"}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/operation.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Operation do\n @moduledoc \"\"\"\n CMS mutations for cms operations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_opertion_mutations do\n @desc \"set category to a community\"\n field :set_category, :community do\n arg(:community_id, non_null(:id))\n arg(:category_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.set\")\n\n resolve(&R.CMS.set_category/3)\n end\n\n @desc \"unset category to a community\"\n field :unset_category, :community do\n arg(:community_id, non_null(:id))\n arg(:category_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.unset\")\n\n resolve(&R.CMS.unset_category/3)\n end\n\n @desc \"bind a thread to a exist community\"\n field :set_thread, :community do\n arg(:community_id, non_null(:id))\n arg(:thread_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->thread.set\")\n\n resolve(&R.CMS.set_thread/3)\n end\n\n @desc \"remove a thread from a exist community, thread content is not delete\"\n field :unset_thread, :community do\n arg(:community_id, non_null(:id))\n arg(:thread_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->thread.unset\")\n\n resolve(&R.CMS.unset_thread/3)\n end\n\n @desc \"stamp rules on user's passport\"\n field :stamp_cms_passport, :idlike do\n arg(:user_id, non_null(:id))\n arg(:rules, non_null(:json))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->community.stamp_passport\")\n\n resolve(&R.CMS.stamp_passport/3)\n end\n\n @desc \"subscribe a community so it can appear in sidebar\"\n field :subscribe_community, :community do\n arg(:community_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.subscribe_community/3)\n end\n\n @desc \"unsubscribe a community\"\n field :unsubscribe_community, :community do\n arg(:community_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.unsubscribe_community/3)\n end\n\n @desc \"set a tag within community\"\n field :set_tag, :article_tag do\n arg(:id, non_null(:id))\n arg(:tag_id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.set\")\n\n resolve(&R.CMS.set_tag/3)\n end\n\n @desc \"unset a tag within community\"\n field :unset_tag, :article_tag do\n # thread id\n arg(:id, non_null(:id))\n arg(:tag_id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.unset\")\n\n resolve(&R.CMS.unset_tag/3)\n end\n\n # TODO: use community loader\n field :mirror_article, :community do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->t?.community.mirror\")\n resolve(&R.CMS.mirror_article/3)\n end\n\n # TODO: can't not unset the oldest community\n field :unmirror_article, :community do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->t?.community.unmirror\")\n resolve(&R.CMS.unmirror_article/3)\n end\n\n field :reaction, :article do\n arg(:id, non_null(:id))\n arg(:thread, non_null(:thread))\n arg(:action, non_null(:cms_action))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.reaction/3)\n end\n\n field :undo_reaction, :article do\n arg(:id, non_null(:id))\n arg(:thread, non_null(:thread))\n arg(:action, non_null(:cms_action))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.undo_reaction/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,17,9,7,5,3,null,null,null,2,null,null,null,2,null,null,null,2,null,null,null,8,null,null,null,null,null,0,null,null,null,null,9,9,null,9,null,null,null,null,null,null,7,null,7,null,null,null,null,null,null,5,null,5,null,null,null,null,null],"name":"lib/groupher_server_web/middleware/publish_throttle.ex","source":"defmodule GroupherServerWeb.Middleware.PublishThrottle do\n @behaviour Absinthe.Middleware\n import Helper.Utils, only: [handle_absinthe_error: 3, get_config: 2]\n import Helper.ErrorCode\n\n alias GroupherServer.{Statistics, Accounts}\n\n @interval_minutes get_config(:general, :publish_throttle_interval_minutes)\n @hour_limit get_config(:general, :publish_throttle_hour_limit)\n @day_total get_config(:general, :publish_throttle_day_limit)\n\n def call(%{context: %{cur_user: cur_user}} = resolution, opt) do\n with {:ok, record} <- Statistics.load_throttle_record(%Accounts.User{id: cur_user.id}),\n {:ok, _} <- interval_check(record, opt),\n {:ok, _} <- hour_limit_check(record, opt),\n {:ok, _} <- day_limit_check(record, opt) do\n resolution\n else\n {:error, :interval_check} ->\n resolution\n |> handle_absinthe_error(\"throttle_interval\", ecode(:throttle_inverval))\n\n {:error, :hour_limit_check} ->\n resolution\n |> handle_absinthe_error(\"throttle_hour\", ecode(:throttle_hour))\n\n {:error, :day_limit_check} ->\n resolution\n |> handle_absinthe_error(\"throttle_day\", ecode(:throttle_day))\n\n {:error, _error} ->\n # publish first time ignore\n resolution\n end\n end\n\n def call(resolution, _) do\n resolution\n |> handle_absinthe_error(\"Authorize: need login\", ecode(:account_login))\n end\n\n # TODO: option: passport ..\n defp interval_check(%Statistics.PublishThrottle{last_publish_time: last_publish_time}, opt) do\n interval_opt = Keyword.get(opt, :interval) || @interval_minutes\n latest_valid_time = Timex.shift(last_publish_time, minutes: interval_opt)\n\n case Timex.before?(latest_valid_time, Timex.now()) do\n true -> {:ok, :interval_check}\n false -> {:error, :interval_check}\n end\n end\n\n defp hour_limit_check(%Statistics.PublishThrottle{hour_count: hour_count}, opt) do\n hour_count_opt = Keyword.get(opt, :hour_limit) || @hour_limit\n\n case hour_count < hour_count_opt do\n true -> {:ok, :hour_limit_check}\n false -> {:error, :hour_limit_check}\n end\n end\n\n defp day_limit_check(%Statistics.PublishThrottle{date_count: day_count}, opt) do\n day_limit_opt = Keyword.get(opt, :day_limit) || @day_total\n\n case day_count < day_limit_opt do\n true -> {:ok, :day_limit_check}\n false -> {:error, :day_limit_check}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,9771,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,null,null,null,null],"name":"lib/groupher_server/accounts/user.ex","source":"defmodule GroupherServer.Accounts.User do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.Accounts.{\n Achievement,\n Customization,\n GithubUser,\n Purchase,\n UserBill,\n UserFollower,\n UserFollowing\n }\n\n alias GroupherServer.CMS\n\n @type t :: %User{}\n schema \"users\" do\n field(:nickname, :string)\n field(:avatar, :string)\n field(:sex, :string)\n field(:bio, :string)\n field(:email, :string)\n field(:location, :string)\n field(:education, :string)\n field(:company, :string)\n field(:qq, :string)\n field(:weibo, :string)\n field(:weichat, :string)\n field(:from_github, :boolean)\n has_one(:achievement, Achievement)\n has_one(:github_profile, GithubUser)\n has_one(:cms_passport, CMS.Passport)\n\n has_many(:followers, {\"users_followers\", UserFollower})\n has_many(:followings, {\"users_followings\", UserFollowing})\n\n has_many(:subscribed_communities, {\"communities_subscribers\", CMS.CommunitySubscriber})\n has_many(:favorited_posts, {\"posts_favorites\", CMS.PostFavorite})\n has_many(:favorited_jobs, {\"jobs_favorites\", CMS.JobFavorite})\n\n field(:sponsor_member, :boolean)\n field(:paid_member, :boolean)\n field(:platinum_member, :boolean)\n\n has_many(:bills, {\"users_bills\", UserBill})\n has_one(:customization, Customization)\n has_one(:purchase, Purchase)\n\n timestamps(type: :utc_datetime)\n end\n\n @required_fields ~w(nickname avatar)a\n @optional_fields ~w(nickname bio avatar sex location email company education qq weichat weibo)a\n\n @doc false\n def changeset(%User{} = user, attrs) do\n # |> cast(attrs, [:username, :nickname, :bio, :company])\n # |> validate_required([:username])\n # |> cast(attrs, @required_fields, @optional_fields)\n user\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> validate_length(:nickname, min: 3, max: 30)\n |> validate_length(:bio, min: 3, max: 100)\n |> validate_inclusion(:sex, [\"dude\", \"girl\"])\n |> validate_format(:email, ~r/@/)\n |> validate_length(:location, min: 2, max: 30)\n |> validate_length(:company, min: 3, max: 30)\n |> validate_length(:qq, min: 8, max: 15)\n |> validate_length(:weichat, min: 3, max: 30)\n |> validate_length(:weibo, min: 3, max: 30)\n\n # |> unique_constraint(:username)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,4,null,null],"name":"lib/groupher_server/accounts/customization.ex","source":"defmodule GroupherServer.Accounts.Customization do\n @moduledoc false\n\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id)a\n @optional_fields ~w(theme sidebar_layout community_chart brainwash_free)a\n\n @type t :: %Customization{}\n schema \"customizations\" do\n belongs_to(:user, User)\n\n field(:theme, :boolean)\n field(:sidebar_layout, :map)\n field(:community_chart, :boolean)\n field(:brainwash_free, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Customization{} = customization, attrs) do\n customization\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,0,null,null,0,null,null,null,0,null,null,0,null],"name":"lib/groupher_server_web/middleware/count_length.ex","source":"# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.CountLength do\n @behaviour Absinthe.Middleware\n # google: must appear in the GROUP BY clause or be used in an aggregate function\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(%{value: []} = resolution, _) do\n %{resolution | value: 0}\n end\n\n def call(%{value: value} = resolution, _) when is_list(value) do\n %{resolution | value: length(value)}\n end\n\n def call(resolution, _), do: resolution\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,58,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/delivery/sys_notification.ex","source":"defmodule GroupherServer.Delivery.SysNotification do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n @required_fields ~w(source_title source_id source_type)a\n @optional_fields ~w(source_preview)a\n\n @type t :: %SysNotification{}\n schema \"sys_notifications\" do\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:source_preview, :string)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%SysNotification{} = sys_notification, attrs) do\n sys_notification\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,881,null,881,null,null,null,null,null,null,null,null,null,null,null,null,null,null,537,null,537,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,536,null,536,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,542,542,null,542,null,null,null,542,null,null,null,null,null,null,null,null,null,null,null,null,0,null,0,null,null,null,0,null,0,null,null,null,null,0,null,null,null,null,2628,null,null,null,0,null,null,null,59,59,null,null,null,4718,4718,null,4718,4718,null,4718,null,null,null,null,null,null,69,null,69,69,69,null,null,null,null,null,63,null,63,63,null,null,null,null,null,null,null,null,null,null,27,null,27,27,27,null,27,null,null,null,null,8433,null,8433,null,8433,null,null,null,null,null,null,1,null,1,1,1,1,1,1,null,null,null,null,null,null,null,null,null,null,null,235,209,0,27,4,3,6,5,0,0,null,null,0,null,8,1,8,1,null,null,null,null,null,null,null,null,null,854,533,533,536,0,0,2628,61,55,null,null,26,null,8224,4713,59,null,null,0,null,null,3735,null,null,null,null,null,null,null,3735,null,null,null,134,null,2617,2617,null,null,134,null,null,null,null,null,null,6,null,null,null,12,null,12,28,null,28,null,28,null,28,null,null,28,null,null,null,null,12,null,12,62,null,62,null,62,null,62,null,null,null,62,null,null,null],"name":"test/support/factory.ex","source":"defmodule GroupherServer.Factory do\n @moduledoc \"\"\"\n This module defines the mock data/func to be used by\n tests that require insert some mock data to db.\n\n for example you can db_insert(:user) to insert user into db\n \"\"\"\n import Helper.Utils, only: [done: 1]\n\n alias GroupherServer.Repo\n alias GroupherServer.{CMS, Accounts, Delivery}\n\n defp mock_meta(:post) do\n body = Faker.Lorem.sentence(%Range{first: 80, last: 120})\n\n %{\n title: Faker.Lorem.Shakespeare.king_richard_iii(),\n body: body,\n digest: String.slice(body, 1, 150),\n length: String.length(body),\n author: mock(:author),\n views: Enum.random(0..2000),\n communities: [\n mock(:community),\n mock(:community)\n ]\n }\n end\n\n defp mock_meta(:video) do\n desc = Faker.Lorem.sentence(%Range{first: 15, last: 60})\n\n %{\n title: Faker.Lorem.Shakespeare.king_richard_iii(),\n poster: Faker.Avatar.image_url(),\n desc: desc,\n duration: \"03:30\",\n duration_sec: Enum.random(300..12000),\n source: \"youtube\",\n link: \"http://www.youtube.com/video/1\",\n original_author: \"simon\",\n original_author_link: \"http://www.youtube.com/user/1\",\n author: mock(:author),\n views: Enum.random(0..2000),\n communities: [\n mock(:community),\n mock(:community)\n ]\n }\n end\n\n defp mock_meta(:repo) do\n desc = Faker.Lorem.sentence(%Range{first: 15, last: 60})\n\n %{\n repo_name: Faker.Lorem.Shakespeare.king_richard_iii(),\n desc: desc,\n readme: desc,\n language: \"javascript\",\n author: mock(:author),\n repo_link: \"http://www.github.com/mydearxym\",\n producer: \"mydearxym\",\n producer_link: \"http://www.github.com/mydearxym\",\n repo_star_count: Enum.random(0..2000),\n repo_fork_count: Enum.random(0..2000),\n repo_watch_count: Enum.random(0..2000),\n views: Enum.random(0..2000),\n communities: [\n mock(:community),\n mock(:community)\n ]\n }\n end\n\n defp mock_meta(:job) do\n body = Faker.Lorem.sentence(%Range{first: 80, last: 120})\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n title: Faker.Lorem.Shakespeare.king_richard_iii(),\n company: Faker.Company.name(),\n company_logo: Faker.Avatar.image_url(),\n location: \"location #{unique_num}\",\n body: body,\n digest: String.slice(body, 1, 150),\n length: String.length(body),\n author: mock(:author),\n views: Enum.random(0..2000),\n communities: [\n mock(:community)\n ]\n }\n end\n\n defp mock_meta(:comment) do\n body = Faker.Lorem.sentence(%Range{first: 30, last: 80})\n\n %{body: body}\n end\n\n defp mock_meta(:mention) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n from_user: mock(:user),\n to_user: mock(:user),\n source_id: \"1\",\n source_type: \"post\",\n source_preview: \"source_preview #{unique_num}.\"\n }\n end\n\n defp mock_meta(:author) do\n %{role: \"normal\", user: mock(:user)}\n end\n\n defp mock_meta(:communities_threads) do\n %{community_id: 1, thread_id: 1}\n end\n\n defp mock_meta(:thread) do\n unique_num = System.unique_integer([:positive, :monotonic])\n %{title: \"thread #{unique_num}\", raw: \"thread #{unique_num}\", index: :rand.uniform(20)}\n end\n\n defp mock_meta(:community) do\n unique_num = System.unique_integer([:positive, :monotonic])\n random_num = Enum.random(0..2000)\n\n %{\n title: \"community_#{random_num}_#{unique_num}\",\n desc: \"community desc\",\n raw: \"community_#{unique_num}\",\n logo: \"https://coderplanets.oss-cn-beijing.aliyuncs.com/icons/pl/elixir.svg\",\n author: mock(:user)\n }\n end\n\n defp mock_meta(:category) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n title: \"category#{unique_num}\",\n raw: \"category#{unique_num}\",\n author: mock(:author)\n }\n end\n\n defp mock_meta(:article_tag) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n title: \"#{Faker.Pizza.cheese()} #{unique_num}\",\n thread: \"POST\",\n color: \"YELLOW\",\n # community: Faker.Pizza.topping(),\n community: mock(:community),\n author: mock(:author)\n # user_id: 1\n }\n end\n\n defp mock_meta(:sys_notification) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n source_id: \"#{unique_num}\",\n source_title: \"#{Faker.Pizza.cheese()}\",\n source_type: \"post\",\n source_preview: \"#{Faker.Pizza.cheese()}\"\n }\n end\n\n defp mock_meta(:user) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n # username: \"#{Faker.Name.first_name()} #{unique_num}\",\n nickname: \"#{Faker.Name.first_name()} #{unique_num}\",\n bio: Faker.Lorem.Shakespeare.romeo_and_juliet(),\n avatar: Faker.Avatar.image_url()\n }\n end\n\n defp mock_meta(:github_profile) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n id: \"#{Faker.Name.first_name()} #{unique_num}\",\n login: \"#{Faker.Name.first_name()} #{unique_num}\",\n github_id: \"#{unique_num + 1000}\",\n node_id: \"#{unique_num + 2000}\",\n access_token: \"#{unique_num + 3000}\",\n bio: Faker.Lorem.Shakespeare.romeo_and_juliet(),\n company: Faker.Company.name(),\n location: \"chengdu\",\n email: Faker.Internet.email(),\n avatar_url: Faker.Avatar.image_url(),\n html_url: Faker.Avatar.image_url(),\n followers: unique_num * unique_num,\n following: unique_num * unique_num * unique_num\n }\n end\n\n def mock_attrs(_, attrs \\\\ %{})\n def mock_attrs(:user, attrs), do: mock_meta(:user) |> Map.merge(attrs)\n def mock_attrs(:author, attrs), do: mock_meta(:author) |> Map.merge(attrs)\n def mock_attrs(:post, attrs), do: mock_meta(:post) |> Map.merge(attrs)\n def mock_attrs(:video, attrs), do: mock_meta(:video) |> Map.merge(attrs)\n def mock_attrs(:repo, attrs), do: mock_meta(:repo) |> Map.merge(attrs)\n def mock_attrs(:job, attrs), do: mock_meta(:job) |> Map.merge(attrs)\n def mock_attrs(:community, attrs), do: mock_meta(:community) |> Map.merge(attrs)\n def mock_attrs(:thread, attrs), do: mock_meta(:thread) |> Map.merge(attrs)\n def mock_attrs(:mention, attrs), do: mock_meta(:mention) |> Map.merge(attrs)\n\n def mock_attrs(:communities_threads, attrs),\n do: mock_meta(:communities_threads) |> Map.merge(attrs)\n\n def mock_attrs(:article_tag, attrs), do: mock_meta(:article_tag) |> Map.merge(attrs)\n def mock_attrs(:sys_notification, attrs), do: mock_meta(:sys_notification) |> Map.merge(attrs)\n def mock_attrs(:category, attrs), do: mock_meta(:category) |> Map.merge(attrs)\n def mock_attrs(:github_profile, attrs), do: mock_meta(:github_profile) |> Map.merge(attrs)\n\n # NOTICE: avoid Recursive problem\n # bad example:\n # mismatch mismatch\n # | |\n # defp mock(:user), do: Accounts.User |> struct(mock_meta(:community))\n\n # this line of code will cause SERIOUS Recursive problem\n\n defp mock(:post), do: CMS.Post |> struct(mock_meta(:post))\n defp mock(:video), do: CMS.Video |> struct(mock_meta(:video))\n defp mock(:repo), do: CMS.Repo |> struct(mock_meta(:repo))\n defp mock(:job), do: CMS.Job |> struct(mock_meta(:job))\n defp mock(:comment), do: CMS.Comment |> struct(mock_meta(:comment))\n defp mock(:mention), do: Delivery.Mention |> struct(mock_meta(:mention))\n defp mock(:author), do: CMS.Author |> struct(mock_meta(:author))\n defp mock(:category), do: CMS.Category |> struct(mock_meta(:category))\n defp mock(:article_tag), do: CMS.Tag |> struct(mock_meta(:article_tag))\n\n defp mock(:sys_notification),\n do: Delivery.SysNotification |> struct(mock_meta(:sys_notification))\n\n defp mock(:user), do: Accounts.User |> struct(mock_meta(:user))\n defp mock(:community), do: CMS.Community |> struct(mock_meta(:community))\n defp mock(:thread), do: CMS.Thread |> struct(mock_meta(:thread))\n\n defp mock(:communities_threads),\n do: CMS.CommunityThread |> struct(mock_meta(:communities_threads))\n\n defp mock(factory_name, attributes) do\n factory_name |> mock() |> struct(attributes)\n end\n\n # \"\"\"\n # not use changeset because in test we may insert some attrs which not in schema\n # like: views, insert/update ... to test filter-sort,when ...\n # \"\"\"\n def db_insert(factory_name, attributes \\\\ []) do\n Repo.insert(mock(factory_name, attributes))\n end\n\n def db_insert_multi(factory_name, count \\\\ 2) do\n results =\n Enum.reduce(1..count, [], fn _, acc ->\n {:ok, value} = db_insert(factory_name)\n acc ++ [value]\n end)\n\n results |> done\n end\n\n alias GroupherServer.Accounts.User\n\n def mock_sys_notification(count \\\\ 3) do\n # {:ok, sys_notifications} = db_insert_multi(:sys_notification, count)\n db_insert_multi(:sys_notification, count)\n end\n\n def mock_mentions_for(%User{id: _to_user_id} = user, count \\\\ 3) do\n {:ok, users} = db_insert_multi(:user, count)\n\n Enum.map(users, fn u ->\n unique_num = System.unique_integer([:positive, :monotonic])\n\n info = %{\n source_id: \"1\",\n source_title: \"Title #{unique_num}\",\n source_type: \"post\",\n source_preview: \"preview #{unique_num}\"\n }\n\n {:ok, _} = Delivery.mention_someone(u, user, info)\n end)\n end\n\n def mock_notifications_for(%User{id: _to_user_id} = user, count \\\\ 3) do\n {:ok, users} = db_insert_multi(:user, count)\n\n Enum.map(users, fn u ->\n unique_num = System.unique_integer([:positive, :monotonic])\n\n info = %{\n source_id: \"1\",\n source_title: \"Title #{unique_num}\",\n source_type: \"post\",\n source_preview: \"preview #{unique_num}\",\n action: \"like\"\n }\n\n {:ok, _} = Delivery.notify_someone(u, user, info)\n end)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,368,null,null,null,null,null,null,null,null,null,null,null,null,140,null,null],"name":"lib/groupher_server/cms/passport.ex","source":"defmodule GroupherServer.CMS.Passport do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n @type t :: %Passport{}\n schema \"cms_passports\" do\n field(:rules, :map)\n belongs_to(:user, Accounts.User)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Passport{} = passport, attrs) do\n passport\n |> cast(attrs, [:rules, :user_id])\n |> validate_required([:rules, :user_id])\n |> unique_constraint(:user_id)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,null,3,null,null,null,null,null,null,211,79,null,null,null,null,null,null,null,null,139,null,5,5,null,null,134,134,null,null,null,null,4,4,null,3,null,null,1,null,null,null,null,null,1,null,null,null,139,null,null,null,139,null,null],"name":"lib/groupher_server/cms/delegates/passport_curd.ex","source":"defmodule GroupherServer.CMS.Delegate.PassportCURD do\n @moduledoc \"\"\"\n passport curd\n \"\"\"\n import Helper.Utils, only: [done: 1, deep_merge: 2]\n import Ecto.Query, warn: false\n import ShortMaps\n\n alias Helper.{NestedFilter, ORM}\n alias GroupherServer.CMS.Passport, as: UserPasport\n alias GroupherServer.{Accounts, Repo}\n\n # https://medium.com/front-end-hacking/use-github-oauth-as-your-sso-seamlessly-with-react-3e2e3b358fa1\n # http://www.ubazu.com/using-postgres-jsonb-columns-in-ecto\n # http://www.ubazu.com/using-postgres-jsonb-columns-in-ecto\n\n def paged_passports(community, key) do\n UserPasport\n |> where([p], fragment(\"(?->?->>?)::boolean = ?\", p.rules, ^community, ^key, true))\n |> Repo.all()\n |> done\n end\n\n @doc \"\"\"\n return a user's passport in CMS context\n \"\"\"\n def get_passport(%Accounts.User{} = user) do\n with {:ok, passport} <- ORM.find_by(UserPasport, user_id: user.id) do\n {:ok, passport.rules}\n end\n end\n\n # TODO passport should be public utils\n @doc \"\"\"\n insert or update a user's passport in CMS context\n \"\"\"\n def stamp_passport(rules, %Accounts.User{id: user_id}) do\n case ORM.find_by(UserPasport, user_id: user_id) do\n {:ok, passport} ->\n rules = passport.rules |> deep_merge(rules) |> reject_invalid_rules\n passport |> ORM.update(~m(rules)a)\n\n {:error, _} ->\n rules = rules |> reject_invalid_rules\n UserPasport |> ORM.create(~m(user_id rules)a)\n end\n end\n\n def erase_passport(rules, %Accounts.User{id: user_id}) when is_list(rules) do\n with {:ok, passport} <- ORM.find_by(UserPasport, user_id: user_id) do\n case pop_in(passport.rules, rules) do\n {nil, _} ->\n {:error, \"#{rules} not found\"}\n\n {_, lefts} ->\n passport |> ORM.update(%{rules: lefts})\n end\n end\n end\n\n def delete_passport(%Accounts.User{id: user_id}) do\n ORM.findby_delete!(UserPasport, ~m(user_id)a)\n end\n\n defp reject_invalid_rules(rules) when is_map(rules) do\n rules |> NestedFilter.drop_by_value([false]) |> reject_empty_values\n end\n\n defp reject_empty_values(map) when is_map(map) do\n for {k, v} <- map, v != %{}, into: %{}, do: {k, v}\n end\nend"},{"coverage":[null,null,null,361,null,null,null,null,null,null,null,361,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/router.ex","source":"defmodule GroupherServerWeb.Router do\n use GroupherServerWeb, :router\n\n pipeline :api do\n plug(:accepts, [\"json\"])\n plug(GroupherServerWeb.Context)\n end\n\n scope \"/graphiql\" do\n pipe_through(:api)\n\n forward(\n \"/\",\n Absinthe.Plug.GraphiQL,\n schema: GroupherServerWeb.Schema,\n pipeline: {ApolloTracing.Pipeline, :plug},\n interface: :playground,\n context: %{pubsub: GroupherServerWeb.Endpoint}\n )\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,0,null,0,null,null,0,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,9,null,4,null,null,1,null,null,4,null,null,0,null,null,null,null,null,null],"name":"lib/groupher_server/cms/utils/matcher.ex","source":"defmodule GroupherServer.CMS.Helper.MatcherOld do\n @moduledoc \"\"\"\n this module defined the matches and handy guard ...\n \"\"\"\n import Ecto.Query, warn: false\n\n alias GroupherServer.CMS.{\n Community,\n Post,\n Video,\n Repo,\n Job,\n PostFavorite,\n JobFavorite,\n PostStar,\n JobStar,\n PostComment,\n JobComment,\n Tag,\n Community,\n PostCommentLike,\n PostCommentDislike\n }\n\n @support_thread [:post, :video, :repo, :job]\n @support_react [:favorite, :star, :watch, :comment, :article_tag, :self]\n\n defguard valid_thread(thread) when thread in @support_thread\n defguard invalid_thread(thread) when thread not in @support_thread\n\n defguard valid_reaction(thread, react)\n when valid_thread(thread) and react in @support_react\n\n defguard invalid_reaction(thread, react)\n when invalid_thread(thread) and react not in @support_react\n\n defguard valid_feeling(feel) when feel in [:like, :dislike]\n\n # posts ...\n def match_action(:post, :self), do: {:ok, %{target: Post, reactor: Post, preload: :author}}\n\n def match_action(:post, :favorite),\n do: {:ok, %{target: Post, reactor: PostFavorite, preload: :user, preload_right: :post}}\n\n def match_action(:post, :star), do: {:ok, %{target: Post, reactor: PostStar, preload: :user}}\n def match_action(:post, :article_tag), do: {:ok, %{target: Post, reactor: Tag}}\n def match_action(:post, :community), do: {:ok, %{target: Post, reactor: Community}}\n\n def match_action(:post, :comment),\n do: {:ok, %{target: Post, reactor: PostComment, preload: :author}}\n\n def match_action(:post_comment, :like),\n do: {:ok, %{target: PostComment, reactor: PostCommentLike}}\n\n def match_action(:post_comment, :dislike),\n do: {:ok, %{target: PostComment, reactor: PostCommentDislike}}\n\n # videos ...\n def match_action(:video, :community), do: {:ok, %{target: Video, reactor: Community}}\n\n # repos ...\n def match_action(:repo, :community), do: {:ok, %{target: Repo, reactor: Community}}\n\n # jobs ...\n def match_action(:job, :self), do: {:ok, %{target: Job, reactor: Job, preload: :author}}\n def match_action(:job, :community), do: {:ok, %{target: Job, reactor: Community}}\n def match_action(:job, :star), do: {:ok, %{target: Job, reactor: JobStar, preload: :user}}\n def match_action(:job, :article_tag), do: {:ok, %{target: Job, reactor: Tag}}\n\n def match_action(:job, :comment),\n do: {:ok, %{target: Job, reactor: JobComment, preload: :author}}\n\n def match_action(:job, :favorite),\n do: {:ok, %{target: Job, reactor: JobFavorite, preload: :user}}\n\n def dynamic_where(thread, id) do\n case thread do\n :post ->\n {:ok, dynamic([p], p.post_id == ^id)}\n\n :post_comment ->\n {:ok, dynamic([p], p.post_comment_id == ^id)}\n\n :job ->\n {:ok, dynamic([p], p.job_id == ^id)}\n\n :job_comment ->\n {:ok, dynamic([p], p.job_comment_id == ^id)}\n\n _ ->\n {:error, 'where is not match'}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,84,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,29,null,null],"name":"lib/groupher_server/delivery/mention.ex","source":"defmodule GroupherServer.Delivery.Mention do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id source_title source_id source_type source_preview)a\n @optional_fields ~w(parent_id parent_type read)a\n\n @type t :: %Mention{}\n schema \"mentions\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:parent_id, :string)\n field(:parent_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Mention{} = mention, attrs) do\n mention\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,10,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/cms/job_star.ex","source":"defmodule GroupherServer.CMS.JobStar do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Job\n\n @required_fields ~w(user_id job_id)a\n\n @type t :: %JobStar{}\n schema \"jobs_stars\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:job, Job, foreign_key: :job_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%JobStar{} = job_star, attrs) do\n # |> unique_constraint(:user_id, name: :favorites_user_id_article_id_index)\n job_star\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:user_id, name: :jobs_stars_user_id_job_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server_web.ex","source":"defmodule GroupherServerWeb do\n @moduledoc \"\"\"\n The entrypoint for defining your web interface, such\n as controllers, views, channels and so on.\n\n This can be used in your application as:\n\n use GroupherServerWeb, :controller\n use GroupherServerWeb, :view\n\n The definitions below will be executed for every view,\n controller, etc, so keep them short and clean, focused\n on imports, uses and aliases.\n\n Do NOT define functions inside the quoted expressions\n below. Instead, define any helper function in modules\n and import those modules here.\n \"\"\"\n\n def controller do\n quote do\n use Phoenix.Controller, namespace: GroupherServerWeb\n import Plug.Conn\n import GroupherServerWeb.Router.Helpers\n import GroupherServerWeb.Gettext\n end\n end\n\n def view do\n quote do\n use Phoenix.View,\n root: \"lib/groupher_server_web/templates\",\n namespace: GroupherServerWeb\n\n # Import convenience functions from controllers\n import Phoenix.Controller, only: [get_flash: 2, view_module: 1]\n\n import GroupherServerWeb.Router.Helpers\n import GroupherServerWeb.ErrorHelpers\n import GroupherServerWeb.Gettext\n end\n end\n\n def router do\n quote do\n use Phoenix.Router\n import Plug.Conn\n import Phoenix.Controller\n end\n end\n\n def channel do\n quote do\n use Phoenix.Channel\n import GroupherServerWeb.Gettext\n end\n end\n\n @doc \"\"\"\n When used, dispatch to the appropriate controller/view/etc.\n \"\"\"\n defmacro __using__(which) when is_atom(which) do\n apply(__MODULE__, which, [])\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,14,null,14,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,105,null,null,null,null,null,null,0,null,null,null,null,null,0,null,null,null,60,60,null,null,null,142,null,null,null,15,null,null,null,5,4,1,null,null,0,0,null,null,null,null,6,null,39,8,null,null,null,null,null,6,null,null,null,null,null,4,null,null,null,null,4,null],"name":"lib/helper/utils.ex","source":"defmodule Helper.Utils do\n @moduledoc \"\"\"\n unitil functions\n \"\"\"\n import Ecto.Query, warn: false\n import Helper.ErrorHandler\n import Helper.ErrorCode\n\n def get_config(section, key, app \\\\ :groupher_server) do\n app\n |> Application.get_env(section)\n # |> IO.inspect(label: \"debug ci\")\n |> case do\n nil -> \"\"\n config -> Keyword.get(config, key)\n end\n end\n\n @doc \"\"\"\n handle General {:ok, ..} or {:error, ..} return\n \"\"\"\n def done(nil, :boolean), do: {:ok, false}\n def done(_, :boolean), do: {:ok, true}\n def done(nil, err_msg), do: {:error, err_msg}\n def done({:ok, _}, with: result), do: {:ok, result}\n\n def done({:ok, %{id: id}}, :status), do: {:ok, %{done: true, id: id}}\n def done({:error, _}, :status), do: {:ok, %{done: false}}\n\n def done(nil, queryable, id), do: {:error, not_found_formater(queryable, id)}\n def done(result, _, _), do: {:ok, result}\n\n def done(nil), do: {:error, \"record not found.\"}\n\n # def done({:error, error}), do: {:error, error}\n def done(result), do: {:ok, result}\n\n @doc \"\"\"\n see: https://hexdocs.pm/absinthe/errors.html#content for error format\n \"\"\"\n def handle_absinthe_error(resolution, err_msg, code) when is_integer(code) do\n resolution\n |> Absinthe.Resolution.put_result({:error, message: err_msg, code: code})\n end\n\n def handle_absinthe_error(resolution, err_msg) when is_list(err_msg) do\n # %{resolution | value: [], errors: transform_errors(changeset)}\n resolution\n # |> Absinthe.Resolution.put_result({:error, err_msg})\n |> Absinthe.Resolution.put_result({:error, message: err_msg, code: ecode()})\n end\n\n def handle_absinthe_error(resolution, err_msg) when is_binary(err_msg) do\n resolution\n # |> Absinthe.Resolution.put_result({:error, err_msg})\n |> Absinthe.Resolution.put_result({:error, message: err_msg, code: ecode()})\n end\n\n def map_key_stringify(%{__struct__: _} = map) when is_map(map) do\n map = Map.from_struct(map)\n map |> Enum.reduce(%{}, fn {key, val}, acc -> Map.put(acc, to_string(key), val) end)\n end\n\n def map_key_stringify(map) when is_map(map) do\n map |> Enum.reduce(%{}, fn {key, val}, acc -> Map.put(acc, to_string(key), val) end)\n end\n\n def deep_merge(left, right) do\n Map.merge(left, right, &deep_resolve/3)\n end\n\n def tobe_integer(val) do\n if is_integer(val),\n do: val,\n else: val |> String.to_integer()\n end\n\n def repeat(times, [x]) when is_integer(x), do: to_string(for _ <- 1..times, do: x)\n def repeat(times, x), do: for(_ <- 1..times, do: x)\n\n def add(num, offset \\\\ 1) when is_integer(num) and is_integer(offset), do: num + offset\n\n def map_atom_value(attrs, :string) do\n results =\n Enum.map(attrs, fn {k, v} ->\n if is_atom(v) do\n {k, to_string(v)}\n else\n {k, v}\n end\n end)\n\n results |> Enum.into(%{})\n end\n\n # Key exists in both maps, and both values are maps as well.\n # These can be merged recursively.\n # defp deep_resolve(_key, left = %{},right = %{}) do\n defp deep_resolve(_key, %{} = left, %{} = right), do: deep_merge(left, right)\n\n # Key exists in both maps, but at least one of the values is\n # NOT a map. We fall back to standard merge behavior, preferring\n # the value on the right.\n defp deep_resolve(_key, _left, right), do: right\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,53,null,null,null,null,null,null,null,null,null,null,null,377,null,377,364,null,null,null,null,null],"name":"test/support/conn_case.ex","source":"defmodule GroupherServerWeb.ConnCase do\n @moduledoc \"\"\"\n This module defines the test case to be used by\n tests that require setting up a connection.\n\n Such tests rely on `Phoenix.ConnTest` and also\n import other functionality to make it easier\n to build common datastructures and query the data layer.\n\n Finally, if the test case interacts with the database,\n it cannot be async. For this reason, every test runs\n inside a transaction which is reset at the beginning\n of the test unless the test case is marked as async.\n \"\"\"\n\n use ExUnit.CaseTemplate\n\n using do\n quote do\n # Import conveniences for testing with connections\n use Phoenix.ConnTest\n import GroupherServerWeb.Router.Helpers\n\n # The default endpoint for testing\n @endpoint GroupherServerWeb.Endpoint\n end\n end\n\n setup tags do\n :ok = Ecto.Adapters.SQL.Sandbox.checkout(GroupherServer.Repo)\n\n unless tags[:async] do\n Ecto.Adapters.SQL.Sandbox.mode(GroupherServer.Repo, {:shared, self()})\n end\n\n {:ok, conn: Phoenix.ConnTest.build_conn()}\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,125,null,null,null,null,null,null,null,null,null,null,null,62,null,null],"name":"lib/groupher_server/cms/job_favorite.ex","source":"defmodule GroupherServer.CMS.JobFavorite do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Job\n\n @required_fields ~w(user_id job_id)a\n\n @type t :: %JobFavorite{}\n schema \"jobs_favorites\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:job, Job, foreign_key: :job_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%JobFavorite{} = job_favorite, attrs) do\n job_favorite\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:user_id, name: :jobs_favorites_user_id_job_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,983,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,6,null,null],"name":"lib/groupher_server/cms/job.ex","source":"defmodule GroupherServer.CMS.Job do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, JobFavorite, Tag}\n\n @required_fields ~w(title company company_logo location body digest length)a\n @optional_fields ~w(link_addr link_source min_education)a\n\n @type t :: %Job{}\n schema \"cms_jobs\" do\n field(:title, :string)\n field(:company, :string)\n field(:bonus, :string)\n field(:company_logo, :string)\n field(:location, :string)\n field(:desc, :string)\n field(:body, :string)\n belongs_to(:author, Author)\n field(:views, :integer, default: 0)\n field(:link_addr, :string)\n field(:link_source, :string)\n\n field(:min_salary, :integer, default: 0)\n field(:max_salary, :integer, default: 10_000_000)\n\n field(:min_experience, :integer, default: 1)\n field(:max_experience, :integer, default: 3)\n\n # college - bachelor - master - doctor\n field(:min_education, :string)\n\n field(:digest, :string)\n field(:length, :integer)\n\n # has_many(:comments, {\"jobs_comments\", JobComment})\n has_many(:favorites, {\"jobs_favorites\", JobFavorite})\n # has_many(:stars, {\"posts_stars\", PostStar})\n\n many_to_many(\n :tags,\n Tag,\n join_through: \"jobs_tags\",\n join_keys: [job_id: :id, tag_id: :id],\n # :delete_all will only remove data from the join source\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_jobs\",\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Job{} = job, attrs) do\n job\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,26,26,null,26,26,26,null,26,null,4,4,null,4,4,null,null,22,22,22,22,null,null,null,null,null,17,9,9,null,9,null,9,9,null,9,9,null,9,9,null,null,null,null,null,null,null,null,18,null,null,null,9,9,null,9,null,9,null,null,null,null,6,6,6,null,null,null,null,2,2,null,null,null,null,1,1,1,null,null,null,null,2,2,null,null,null,null,1,1,1,null,null,null],"name":"lib/groupher_server/statistics/delegates/throttle.ex","source":"defmodule GroupherServer.Statistics.Delegate.Throttle do\n import Ecto.Query, warn: false\n import ShortMaps\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.Statistics.PublishThrottle\n alias Helper.{ORM}\n\n def log_publish_action(%User{id: user_id}) do\n cur_date = Timex.today() |> Date.to_iso8601()\n cur_datetime = DateTime.utc_now() |> DateTime.to_iso8601()\n\n last_publish_time = cur_datetime\n publish_hour = cur_datetime\n publish_date = cur_date\n\n case PublishThrottle |> ORM.find_by(~m(user_id)a) do\n {:ok, record} ->\n date_count = record.date_count + 1\n hour_count = record.hour_count + 1\n\n attrs = ~m(user_id publish_date publish_hour date_count hour_count last_publish_time)a\n record |> ORM.update(attrs)\n\n {:error, _} ->\n date_count = 1\n hour_count = 1\n attrs = ~m(user_id publish_date publish_hour date_count hour_count last_publish_time)a\n PublishThrottle |> ORM.create(attrs)\n end\n end\n\n # auto run check for same hour / day\n def load_throttle_record(%User{id: user_id}) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n date_count = if is_same_day?(record.publish_date), do: record.date_count, else: 0\n hour_count = if is_same_hour?(record.publish_hour), do: record.hour_count, else: 0\n\n case date_count !== 0 or hour_count !== 0 do\n true ->\n cur_date = Timex.today() |> Date.to_iso8601()\n cur_datetime = DateTime.utc_now() |> DateTime.to_iso8601()\n\n publish_hour = cur_datetime\n publish_date = cur_date\n\n attrs = ~m(publish_date publish_hour date_count hour_count)a\n record |> ORM.update(attrs)\n\n false ->\n {:ok, record}\n end\n end\n end\n\n defp is_same_day?(datetime) do\n datetime |> Timex.to_date() |> Timex.equal?(Timex.to_date(Timex.now()))\n end\n\n defp is_same_hour?(datetime) do\n {_date, {record_hour, _min, _sec}} = datetime |> Timex.to_erl()\n {_date, {cur_hour, _min, _sec}} = Timex.now() |> Timex.to_erl()\n\n same_hour? = record_hour == cur_hour\n\n is_same_day?(datetime) and same_hour?\n end\n\n # NOTE: the mock_xxx is only use for test\n def mock_throttle_attr(:last_publish_time, %User{id: user_id}, minutes: minutes) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n last_publish_time = Timex.shift(record.last_publish_time, minutes: minutes)\n record |> ORM.update(~m(last_publish_time)a)\n end\n end\n\n def mock_throttle_attr(:hour_count, %User{id: user_id}, count: hour_count) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n record |> ORM.update(~m(hour_count)a)\n end\n end\n\n def mock_throttle_attr(:publish_hour, %User{id: user_id}, hours: hours) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n publish_hour = Timex.shift(record.publish_hour, hours: hours)\n record |> ORM.update(~m(publish_hour)a)\n end\n end\n\n def mock_throttle_attr(:date_count, %User{id: user_id}, count: date_count) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n record |> ORM.update(~m(date_count)a)\n end\n end\n\n def mock_throttle_attr(:publish_date, %User{id: user_id}, days: days) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n publish_date = Timex.shift(record.publish_hour, days: days)\n record |> ORM.update(~m(publish_date)a)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,250,null,null,null,42,null,null],"name":"lib/groupher_server_web/middleware/authorize.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# ---\ndefmodule GroupherServerWeb.Middleware.Authorize do\n @behaviour Absinthe.Middleware\n import Helper.Utils, only: [handle_absinthe_error: 3]\n import Helper.ErrorCode\n\n def call(%{context: %{cur_user: _}} = resolution, _info), do: resolution\n\n def call(resolution, _) do\n resolution\n |> handle_absinthe_error(\"Authorize: need login\", ecode(:account_login))\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/helper/gql_schema_suite.ex","source":"defmodule Helper.GqlSchemaSuite do\n @moduledoc \"\"\"\n helper for reduce boilerplate import/use/alias in absinthe schema\n \"\"\"\n\n defmacro __using__(_opts) do\n quote do\n use Absinthe.Schema.Notation\n use Absinthe.Ecto, repo: GroupherServer.Repo\n\n alias GroupherServerWeb.Resolvers, as: R\n alias GroupherServerWeb.Middleware, as: M\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,37,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/sys_notification_mail.ex","source":"defmodule GroupherServer.Accounts.SysNotificationMail do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id source_id source_type)a\n @optional_fields ~w(source_preview read)a\n\n @type t :: %SysNotificationMail{}\n schema \"sys_notification_mails\" do\n belongs_to(:user, User)\n\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%SysNotificationMail{} = sys_notication_mail, attrs) do\n sys_notication_mail\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,0,null,null,0,0,null,null,null,0,null,null,null],"name":"lib/groupher_server_web/middleware/github_user.ex","source":"defmodule GroupherServerWeb.Middleware.GithubUser do\n @behaviour Absinthe.Middleware\n\n import Helper.Utils, only: [handle_absinthe_error: 2]\n alias Helper.OAuth2.Github\n\n def call(%{arguments: %{code: code}} = resolution, _) do\n # IO.inspect(access_token, label: \"GithubUser middleware token\")\n\n case Github.user_profile(code) do\n {:ok, user} ->\n # IO.inspect user,label: \"get ok\"\n arguments = resolution.arguments |> Map.merge(%{github_user: user})\n %{resolution | arguments: arguments}\n\n {:error, err_msg} ->\n resolution\n |> handle_absinthe_error(err_msg)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,2,null,null,null,4,null,null,null,2,null,null],"name":"lib/groupher_server_web/middleware/viewer_did_convert.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\n\ndefmodule GroupherServerWeb.Middleware.ViewerDidConvert do\n @behaviour Absinthe.Middleware\n\n def call(%{value: nil} = resolution, _) do\n %{resolution | value: false}\n end\n\n def call(%{value: []} = resolution, _) do\n %{resolution | value: false}\n end\n\n def call(%{value: [_]} = resolution, _) do\n %{resolution | value: true}\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/delivery/delivery_mutations.ex","source":"defmodule GroupherServerWeb.Schema.Delivery.Mutations do\n @moduledoc \"\"\"\n Delivery.Mutations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :delivery_mutations do\n field :mention_someone, :status do\n arg(:user_id, non_null(:id))\n\n arg(:source_id, non_null(:id))\n arg(:source_title, non_null(:string))\n arg(:source_type, non_null(:string))\n arg(:source_preview, non_null(:string))\n arg(:parent_id, :id)\n arg(:parent_type, :string)\n\n middleware(M.Authorize, :login)\n\n resolve(&R.Delivery.mention_someone/3)\n end\n\n field :publish_system_notification, :status do\n arg(:source_id, non_null(:id))\n arg(:source_title, non_null(:string))\n arg(:source_type, non_null(:string))\n arg(:source_preview, :string)\n\n middleware(M.Authorize, :login)\n # TODO: use delivery passport system instead of cms's\n middleware(M.Passport, claim: \"cms->system_notification.publish\")\n\n resolve(&R.Delivery.publish_system_notification/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null,null,5,null,null,null,null,7,null,null,7,null,null,null,1,null,1,1,null,null,null,null,null,null,null,7,7,7,5,5,null,5,null,null,null,null,1,1,null,null,null,null,null,null,1,null,null,null,null,1,null,1,null,null,null,1,null,null,null,null,1,null,1,null,null,null,null,null,null,null,null,null,1,null,null,null,9,9,null,null,null,null,3,3,null,null,null,null,null,null,null,6,6,6,null,6,null,null],"name":"lib/groupher_server/cms/delegates/community_curd.ex","source":"defmodule GroupherServer.CMS.Delegate.CommunityCURD do\n @moduledoc \"\"\"\n community curd\n \"\"\"\n import Ecto.Query, warn: false\n import GroupherServer.CMS.Helper.MatcherOld\n import Helper.Utils, only: [done: 1, map_atom_value: 2]\n import GroupherServer.CMS.Delegate.ArticleCURD, only: [ensure_author_exists: 1]\n import ShortMaps\n\n alias Helper.ORM\n alias Helper.QueryBuilder\n alias GroupherServer.{Accounts, Repo}\n\n alias GroupherServer.CMS.{\n Category,\n Community,\n CommunityEditor,\n CommunitySubscriber,\n Tag,\n Thread\n }\n\n @doc \"\"\"\n return paged community subscribers\n \"\"\"\n def community_members(:editors, %Community{id: id}, filters) do\n load_community_members(id, CommunityEditor, filters)\n end\n\n def community_members(:subscribers, %Community{id: id}, filters) do\n load_community_members(id, CommunitySubscriber, filters)\n end\n\n defp load_community_members(id, model, %{page: page, size: size} = filters) do\n model\n |> where([c], c.community_id == ^id)\n |> QueryBuilder.load_inner_users(filters)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n def update_editor(%Community{id: community_id}, title, %Accounts.User{id: user_id}) do\n clauses = ~m(user_id community_id)a\n\n with {:ok, _} <- CommunityEditor |> ORM.update_by(clauses, ~m(title)a) do\n Accounts.User |> ORM.find(user_id)\n end\n end\n\n @doc \"\"\"\n create a Tag base on type: post / tuts / videos ...\n \"\"\"\n def create_tag(thread, attrs, %Accounts.User{id: user_id}) when valid_thread(thread) do\n with {:ok, action} <- match_action(thread, :article_tag),\n {:ok, author} <- ensure_author_exists(%Accounts.User{id: user_id}),\n {:ok, _community} <- ORM.find(Community, attrs.community_id) do\n attrs = attrs |> Map.merge(%{author_id: author.id})\n attrs = attrs |> map_atom_value(:string)\n\n action.reactor |> ORM.create(attrs)\n end\n end\n\n def update_tag(%{id: _id} = attrs) do\n attrs = attrs |> map_atom_value(:string)\n Tag |> ORM.find_update(%{id: attrs.id, title: attrs.title, color: attrs.color})\n end\n\n @doc \"\"\"\n get tags belongs to a community / thread\n \"\"\"\n def get_tags(%Community{id: community_id}, thread) when not is_nil(community_id) do\n thread = to_string(thread)\n\n Tag\n |> join(:inner, [t], c in assoc(t, :community))\n |> where([t, c], c.id == ^community_id and t.thread == ^thread)\n |> distinct([t], t.title)\n |> Repo.all()\n |> done()\n end\n\n def get_tags(%Community{raw: community_raw}, thread) when not is_nil(community_raw) do\n thread = to_string(thread)\n\n Tag\n |> join(:inner, [t], c in assoc(t, :community))\n |> where([t, c], c.raw == ^community_raw and t.thread == ^thread)\n |> distinct([t], t.title)\n |> Repo.all()\n |> done()\n end\n\n @doc \"\"\"\n get all paged tags\n \"\"\"\n def get_tags(%{page: page, size: size} = filter) do\n Tag\n |> QueryBuilder.filter_pack(filter)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n def create_category(%Category{title: title, raw: raw}, %Accounts.User{id: user_id}) do\n with {:ok, author} <- ensure_author_exists(%Accounts.User{id: user_id}) do\n Category |> ORM.create(%{title: title, raw: raw, author_id: author.id})\n end\n end\n\n def update_category(~m(%Category id title)a) do\n with {:ok, category} <- ORM.find(Category, id) do\n category |> ORM.update(~m(title)a)\n end\n end\n\n @doc \"\"\"\n TODO: create_thread\n \"\"\"\n def create_thread(attrs) do\n raw = to_string(attrs.raw)\n title = attrs.title\n index = attrs |> Map.get(:index, 0)\n\n Thread |> ORM.create(~m(title raw index)a)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/statistics/statistics_mutations.ex","source":"defmodule GroupherServerWeb.Schema.Statistics.Mutations do\n @moduledoc \"\"\"\n Statistics mutations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :statistics_mutations do\n field :make_contrubute, :user_contribute do\n arg(:user_id, non_null(:id))\n\n resolve(&R.Statistics.make_contrubute/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,254,null,null,null,148,148,148,null,148,null,null,null,38,null,38,null,null,null,59,null,59,null,null,null,61,61,null,61,null,61,null,61,null,null,null,6,null,6,null,6,null,null,null,312,312,null,312,null,null,null],"name":"test/support/conn_simulator.ex","source":"defmodule GroupherServer.Test.ConnSimulator do\n @moduledoc \"\"\"\n mock user_conn, owner_conn, guest_conn\n \"\"\"\n import GroupherServer.Factory\n import Phoenix.ConnTest, only: [build_conn: 0]\n import Plug.Conn, only: [put_req_header: 3]\n\n alias GroupherServer.{Accounts, CMS}\n alias Helper.{Guardian, ORM}\n\n def simu_conn(:guest) do\n build_conn()\n end\n\n def simu_conn(:user) do\n user_attr = mock_attrs(:user)\n {:ok, user} = db_insert(:user, user_attr)\n token = gen_jwt_token(id: user.id)\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n def simu_conn(:owner, content) do\n token = gen_jwt_token(id: content.author.user.id)\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n def simu_conn(:user, %Accounts.User{} = user) do\n token = gen_jwt_token(id: user.id)\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n def simu_conn(:user, cms: passport_rules) do\n user_attr = mock_attrs(:user)\n {:ok, user} = db_insert(:user, user_attr)\n\n token = gen_jwt_token(id: user.id)\n\n {:ok, _passport} = CMS.stamp_passport(passport_rules, %Accounts.User{id: user.id})\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n def simu_conn(:user, %Accounts.User{} = user, cms: passport_rules) do\n token = gen_jwt_token(id: user.id)\n\n {:ok, _passport} = CMS.stamp_passport(passport_rules, %Accounts.User{id: user.id})\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n defp gen_jwt_token(clauses) do\n with {:ok, user} <- ORM.find_by(Accounts.User, clauses) do\n {:ok, token, _info} = Guardian.jwt_encode(user)\n\n \"Bearer #{token}\"\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,911,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,9,null,null,null,null,null],"name":"lib/groupher_server/cms/video.ex","source":"defmodule GroupherServer.CMS.Video do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, Tag}\n\n @required_fields ~w(title poster desc duration duration_sec source)a\n @optional_fields ~w(link original_author original_author_link publish_at pin markDelete)\n\n @type t :: %Video{}\n schema \"cms_videos\" do\n field(:title, :string)\n field(:poster, :string)\n field(:desc, :string)\n field(:duration, :string)\n field(:duration_sec, :integer)\n\n field(:source, :string)\n field(:link, :string)\n\n field(:original_author, :string)\n field(:original_author_link, :string)\n\n field(:views, :integer, default: 0)\n field(:pin, :boolean, default_value: false)\n field(:markDelete, :boolean, default_value: false)\n\n field(:publish_at, :utc_datetime)\n\n belongs_to(:author, Author)\n\n # has_many(:comments, {\"posts_comments\", PostComment})\n\n many_to_many(\n :tags,\n Tag,\n join_through: \"videos_tags\",\n join_keys: [video_id: :id, tag_id: :id],\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_videos\",\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Video{} = video, attrs) do\n video\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n\n # |> foreign_key_constraint(:posts_tags, name: :posts_tags_tag_id_fkey)\n # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,0,null,null,null,0,null,0,null],"name":"lib/groupher_server_web/middleware/put_root_source.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.PutRootSource do\n @behaviour Absinthe.Middleware\n\n # def call(%{source: %{id: id}} = resolution, _) do\n # arguments = resolution.arguments |> Map.merge(%{root_source_id: id})\n\n # %{resolution | arguments: arguments}\n # end\n\n def call(%{source: %{id: id}} = resolution, _) do\n arguments = resolution.arguments |> Map.merge(%{jj: id})\n\n %{resolution | arguments: arguments}\n # resolution\n end\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(resolution, _), do: resolution\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,911,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,9,null,null,null,null,null],"name":"lib/groupher_server/cms/repo.ex","source":"defmodule GroupherServer.CMS.Repo do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, RepoBuilder, Tag}\n\n @required_fields ~w(repo_name desc readme language producer producer_link repo_link repo_star_count repo_fork_count repo_watch_count)a\n @optional_fields ~w(views pin markDelete last_fetch_time)\n\n @type t :: %Repo{}\n schema \"cms_repos\" do\n field(:repo_name, :string)\n field(:desc, :string)\n field(:readme, :string)\n field(:language, :string)\n belongs_to(:author, Author)\n\n field(:repo_link, :string)\n field(:producer, :string)\n field(:producer_link, :string)\n\n field(:repo_star_count, :integer)\n field(:repo_fork_count, :integer)\n field(:repo_watch_count, :integer)\n\n field(:views, :integer, default: 0)\n field(:pin, :boolean, default_value: false)\n field(:markDelete, :boolean, default_value: false)\n\n field(:last_fetch_time, :utc_datetime)\n # TODO: replace RepoBuilder with paged user map\n has_many(:builders, {\"repos_builders\", RepoBuilder})\n\n many_to_many(\n :tags,\n Tag,\n join_through: \"repos_tags\",\n join_keys: [repo_id: :id, tag_id: :id],\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_repos\",\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Repo{} = repo, attrs) do\n repo\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n\n # |> foreign_key_constraint(:posts_tags, name: :posts_tags_tag_id_fkey)\n # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,693,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,432,null,null],"name":"lib/groupher_server/accounts/achievement.ex","source":"defmodule GroupherServer.Accounts.Achievement do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id)a\n @optional_fields ~w(contents_stared_count contents_favorited_count contents_watched_count followers_count reputation)a\n\n @type t :: %Achievement{}\n schema \"user_achievements\" do\n belongs_to(:user, User)\n\n field(:contents_stared_count, :integer, default: 0)\n field(:contents_favorited_count, :integer, default: 0)\n field(:contents_watched_count, :integer, default: 0)\n field(:followers_count, :integer, default: 0)\n field(:reputation, :integer, default: 0)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Achievement{} = achievement, attrs) do\n achievement\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/repo.ex","source":"defmodule GroupherServer.Repo do\n import Helper.Utils, only: [get_config: 2]\n\n use Ecto.Repo, otp_app: :groupher_server\n use Scrivener, page_size: get_config(:general, :page_size)\n\n @dialyzer {:nowarn_function, rollback: 1}\n\n @doc \"\"\"\n Dynamically loads the repository url from the\n DATABASE_URL environment variable.\n \"\"\"\n def init(_, opts) do\n {:ok, Keyword.put(opts, :url, System.get_env(\"DATABASE_URL\"))}\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,18,null,null,null,null,null,null,null,null,null,null,null,null,null,6,null,null,null,null],"name":"lib/groupher_server/statistics/community_contribute.ex","source":"defmodule GroupherServer.Statistics.CommunityContribute do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS\n\n @type t :: %CommunityContribute{}\n schema \"community_contributes\" do\n field(:count, :integer)\n field(:date, :date)\n # field(:community_id, :id)\n belongs_to(:community, CMS.Community)\n\n timestamps()\n end\n\n @doc false\n def changeset(%CommunityContribute{} = community_contribute, attrs) do\n community_contribute\n |> cast(attrs, [:date, :count, :community_id])\n |> validate_required([:date, :count, :community_id])\n |> foreign_key_constraint(:community_id)\n\n # |> unique_constraint(:community_id, name: :communities_threads_community_id_thread_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,33,null,null,null,null,null,null,null,null,null,null,null,null,null,14,null,null],"name":"lib/groupher_server/cms/post_comment_dislike.ex","source":"defmodule GroupherServer.CMS.PostCommentDislike do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.PostComment\n\n @required_fields ~w(post_comment_id user_id)a\n\n @type t :: %PostCommentDislike{}\n schema \"posts_comments_dislikes\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:post_comment, PostComment, foreign_key: :post_comment_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostCommentDislike{} = post_comment_dislike, attrs) do\n post_comment_dislike\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:post_comment_id)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user_id, name: :posts_comments_dislikes_user_id_post_comment_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,4,4,4,null,4,4,4,null,4,null,4,null,null,null,3,null,2,2,null,null,null,null,null,26,null,26,null,null,26,null,null,null,26,26,null,null,null,60,60,null,60,null,null,null,8,null,null,8,null,8,null,8,null,null,null,60,null,60,null,60,null,60,null,60,null,60,null,null,null,60,null,60,null,null,null,null,null,null,null,10,null,null,null,9,null,null,null,7,null,null,null,26,26,26,null,26,null,null,null,null,7,null,7,null,null,7,null,null,null,null,null,null,null,19,19,null,19,null,null,null,1,null,null,null,18,null,null,19,null,null,null,35,35,null,null,null,33,33,null,null,null,26,26,null,null,null,null,null,null,null,68,null,null,null,null,null,null,94,null,94,null,null,41,null,null,null,null,null,null,16,16,null,null,null,null,null,null,null,null,null,null,null,null,null,19,19,null,19,19,null,null,19,19,null,null,null,4,null,4,null,4,4,null,null,null,null,null,null,null,null,null,null,null,41,null,null],"name":"lib/groupher_server/delivery/delegates/utils.ex","source":"defmodule GroupherServer.Delivery.Delegate.Utils do\n @moduledoc \"\"\"\n The Delivery context.\n \"\"\"\n # commons\n import Ecto.Query, warn: false\n import Helper.Utils\n import ShortMaps\n\n alias GroupherServer.Repo\n\n alias GroupherServer.Delivery.{Notification, SysNotification, Mention, Record}\n alias GroupherServer.Accounts.User\n alias Helper.ORM\n\n def mailbox_status(%User{} = user) do\n filter = %{page: 1, size: 1, read: false}\n {:ok, mention_mail} = fetch_mails(user, Mention, filter)\n {:ok, notification_mail} = fetch_mails(user, Notification, filter)\n\n mention_count = mention_mail.total_count\n notification_count = notification_mail.total_count\n total_count = mention_count + notification_count\n\n has_mail = total_count > 0\n\n result = ~m(has_mail total_count mention_count notification_count)a\n {:ok, result}\n end\n\n def fetch_record(%User{id: user_id}), do: Record |> ORM.find_by(user_id: user_id)\n\n def mark_read_all(%User{} = user, :mention), do: Mention |> do_mark_read_all(user)\n def mark_read_all(%User{} = user, :notification), do: Notification |> do_mark_read_all(user)\n\n @doc \"\"\"\n fetch mentions / notifications\n \"\"\"\n def fetch_messages(:sys_notification, %User{} = user, %{page: page, size: size}) do\n {:ok, last_fetch_time} = get_last_fetch_time(SysNotification, user)\n\n mails =\n SysNotification\n |> order_by(desc: :inserted_at)\n |> where([m], m.inserted_at > ^last_fetch_time)\n |> ORM.paginater(~m(page size)a)\n |> done()\n\n record_operation(user, SysNotification, mails)\n mails\n end\n\n def fetch_messages(%User{} = user, queryable, %{page: _page, size: _size, read: read} = filter) do\n mails = fetch_mails_and_delete(user, queryable, filter)\n record_operation(queryable, read, mails)\n\n mails\n end\n\n defp fetch_mails(user, queryable, %{page: page, size: size, read: read}) do\n {:ok, last_fetch_time} = get_last_fetch_time(queryable, read, user)\n\n queryable\n |> where([m], m.to_user_id == ^user.id)\n |> where([m], m.inserted_at > ^last_fetch_time)\n |> where([m], m.read == ^read)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n defp fetch_mails_and_delete(user, queryable, %{page: page, size: size, read: read}) do\n {:ok, last_fetch_time} = get_last_fetch_time(queryable, read, user)\n\n query =\n queryable\n |> where([m], m.to_user_id == ^user.id)\n |> where([m], m.inserted_at > ^last_fetch_time)\n |> where([m], m.read == ^read)\n\n mails =\n query\n |> order_by(desc: :inserted_at)\n |> ORM.paginater(~m(page size)a)\n |> done()\n\n delete_items(query, mails)\n\n mails\n end\n\n defp record_operation(Mention, _read, {:ok, %{entries: []}}), do: {:ok, \"\"}\n defp record_operation(Notification, _read, {:ok, %{entries: []}}), do: {:ok, \"\"}\n defp record_operation(_, SysNotification, {:ok, %{entries: []}}), do: {:ok, \"\"}\n\n defp record_operation(Mention, read, {:ok, %{entries: entries}}) do\n do_record_operation(:mentions_record, read, {:ok, %{entries: entries}})\n end\n\n defp record_operation(Notification, read, {:ok, %{entries: entries}}) do\n do_record_operation(:notifications_record, read, {:ok, %{entries: entries}})\n end\n\n defp record_operation(%User{} = user, SysNotification, {:ok, %{entries: entries}}) do\n do_record_operation(user, :sys_notifications_record, {:ok, %{entries: entries}})\n end\n\n defp get_record_lasttime(entries) do\n first_insert = entries |> List.first() |> Map.get(:inserted_at)\n last_insert = entries |> List.last() |> Map.get(:inserted_at)\n newest_insert = Enum.max([first_insert, last_insert])\n\n newest_insert |> Timex.to_datetime() |> to_string\n end\n\n # sys_notification\n defp do_record_operation(%User{id: user_id}, record_name, {:ok, %{entries: entries}}) do\n record_last_fetch_time = get_record_lasttime(entries)\n\n attrs =\n %{user_id: user_id} |> Map.put(record_name, %{last_fetch_time: record_last_fetch_time})\n\n Record |> ORM.upsert_by([user_id: user_id], attrs)\n end\n\n # last_fetch_read_time\n # > the last fetch time of mails that is read\n # last_fetch_unread_time\n # > the last fetch time of mails that is read\n defp do_record_operation(record_name, read, {:ok, %{entries: entries}}) do\n record_last_fetch_time = get_record_lasttime(entries)\n user_id = entries |> List.first() |> Map.get(:to_user_id)\n\n attrs =\n case read do\n true ->\n %{user_id: user_id}\n |> Map.put(record_name, %{last_fetch_read_time: record_last_fetch_time})\n\n false ->\n %{user_id: user_id}\n |> Map.put(record_name, %{last_fetch_unread_time: record_last_fetch_time})\n end\n\n Record |> ORM.upsert_by([user_id: user_id], attrs)\n end\n\n defp get_last_fetch_time(Mention, read, user) do\n timekey = get_record_lasttime_key(read)\n do_get_last_fetch_time(:mentions_record, user, timekey)\n end\n\n defp get_last_fetch_time(Notification, read, user) do\n timekey = get_record_lasttime_key(read)\n do_get_last_fetch_time(:notifications_record, user, timekey)\n end\n\n defp get_last_fetch_time(SysNotification, user) do\n timekey = get_record_lasttime_key(:sys_notifications_record)\n do_get_last_fetch_time(:sys_notifications_record, user, timekey)\n end\n\n defp get_record_lasttime_key(:sys_notifications_record) do\n \"last_fetch_time\"\n end\n\n defp get_record_lasttime_key(read) do\n case read do\n true -> \"last_fetch_read_time\"\n false -> \"last_fetch_unread_time\"\n end\n end\n\n defp do_get_last_fetch_time(record_key, %User{id: user_id}, timekey) do\n long_long_ago = Timex.shift(Timex.now(), years: -10)\n\n with {:ok, record} <- Record |> ORM.find_by(user_id: user_id) do\n record\n |> has_valid_value(record_key)\n |> case do\n false ->\n {:ok, long_long_ago}\n\n true ->\n record\n |> Map.get(record_key)\n |> Map.get(timekey, to_string(long_long_ago))\n |> NaiveDateTime.from_iso8601()\n end\n else\n {:error, _} ->\n {:ok, long_long_ago}\n end\n end\n\n defp delete_items(_queryable, {:ok, %{entries: []}}), do: {:ok, \"\"}\n\n defp delete_items(queryable, {:ok, %{entries: entries}}) do\n # delete_all only support queryable and where syntax\n # TODO: move logic to queue job\n\n first_id = entries |> List.first() |> Map.get(:id)\n last_id = entries |> List.last() |> Map.get(:id)\n\n min_id = Enum.min([first_id, last_id])\n max_id = Enum.max([first_id, last_id])\n\n queryable\n |> where([m], m.id >= ^min_id and m.id <= ^max_id)\n |> Repo.delete_all()\n end\n\n defp do_mark_read_all(queryable, %User{} = user) do\n query =\n queryable\n |> where([m], m.to_user_id == ^user.id)\n\n try do\n Repo.update_all(\n query,\n set: [read: true]\n )\n\n {:ok, %{status: true}}\n rescue\n _ -> {:error, %{status: false}}\n end\n end\n\n defp has_valid_value(map, key) when is_map(map) do\n Map.has_key?(map, key) and not is_nil(Map.get(map, key))\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,67,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,59,null,null],"name":"lib/groupher_server/cms/community_editor.ex","source":"defmodule GroupherServer.CMS.CommunityEditor do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias Helper.Certification\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Community\n\n @required_fields ~w(user_id community_id title)a\n\n @type t :: %CommunityEditor{}\n\n schema \"communities_editors\" do\n field(:title, :string)\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:community, Community, foreign_key: :community_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%CommunityEditor{} = community_editor, attrs) do\n community_editor\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> validate_inclusion(:title, Certification.editor_titles(:cms))\n |> foreign_key_constraint(:community_id)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user_id, name: :communities_editors_user_id_community_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,2,null,1,1,null,null,null,null,null,null,null,7,3,3,null,null,null,null,3,null,null,null,null,null,null,10,null,null,null,6,3,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/accounts/delegates/customization.ex","source":"defmodule GroupherServer.Accounts.Delegate.Customization do\n import Ecto.Query, warn: false\n\n alias GroupherServer.Accounts\n alias GroupherServer.Accounts.{User, Customization}\n alias Helper.ORM\n # ...\n # TODO: Constants\n\n @doc \"\"\"\n add custom setting to user\n \"\"\"\n # for map_size\n # see https://stackoverflow.com/questions/33248816/pattern-match-function-against-empty-map\n def add_custom_setting(%User{} = _user, map) when map_size(map) == 0 do\n {:error, \"AccountCustomization: invalid option or not purchased\"}\n end\n\n def add_custom_setting(%User{} = user, map) when is_map(map) do\n valid? = map |> Map.keys() |> Enum.all?(&can_set?(user, &1, :boolean))\n\n case valid? do\n true ->\n attrs = Map.merge(%{user_id: user.id}, map)\n Customization |> ORM.upsert_by([user_id: user.id], attrs)\n\n false ->\n {:error, \"AccountCustomization: invalid option or not purchased\"}\n end\n end\n\n def add_custom_setting(%User{} = user, key, value \\\\ true) do\n with {:ok, key} <- can_set?(user, key) do\n attrs = Map.put(%{user_id: user.id}, key, value)\n Customization |> ORM.upsert_by([user_id: user.id], attrs)\n end\n end\n\n defp can_set?(%User{} = user, key, :boolean) do\n case can_set?(%User{} = user, key) do\n {:ok, _} -> true\n {:error, _} -> false\n end\n end\n\n def can_set?(%User{} = user, key) do\n cond do\n key in valid_custom_items(:free) ->\n {:ok, key}\n\n key in valid_custom_items(:advance) ->\n Accounts.has_purchased?(user, key)\n\n true ->\n {:error, \"AccountCustomization: invalid option\"}\n end\n end\n\n @doc \"\"\"\n # theme -- user can set a default theme\n # sidebar_layout -- user can arrange subscribed community index\n \"\"\"\n def valid_custom_items(:free) do\n [:theme, :sidebar_layout]\n end\n\n @doc \"\"\"\n # :brainwash_free -- ads free\n # ::community_chart -- user can access comunity charts\n \"\"\"\n def valid_custom_items(:advance) do\n # NOTE: :brainwash_free aka. \"ads_free\"\n # use brainwash to avoid brower-block-plugins\n [:brainwash_free, :community_chart]\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,7,null,0,null,null,4,null,4,4,null,null,0,0,null,null,4,null,null],"name":"lib/groupher_server_web/middleware/statistics/make_contribute.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.Statistics.MakeContribute do\n @behaviour Absinthe.Middleware\n # google: must appear in the GROUP BY clause or be used in an aggregate function\n alias GroupherServer.Statistics\n alias GroupherServer.CMS.Community\n alias GroupherServer.Accounts.User\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(%{value: nil, errors: _} = resolution, _), do: resolution\n\n def call(%{value: value, context: %{cur_user: cur_user}} = resolution, for: threads) do\n case is_list(threads) do\n true ->\n if :user in threads, do: Statistics.make_contribute(%User{id: cur_user.id})\n if :community in threads, do: Statistics.make_contribute(%Community{id: value.id})\n\n false ->\n if :user == threads, do: Statistics.make_contribute(%User{id: cur_user.id})\n if :community == threads, do: Statistics.make_contribute(%Community{id: value.id})\n end\n\n resolution\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,172,312,null,312,2,null,310,null,null,null,null,null,null,0,null,null,null,279,null,null,null,null,null,null,null,null,139,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,0,null,0,null,null,null,0,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/helper/nested_filter.ex","source":"defmodule Helper.NestedFilter do\n @moduledoc \"\"\"\n Documentation for NestedFilter.\n see: https://github.com/treble37/nested_filter\n \"\"\"\n @type key :: any\n @type val :: any\n @type keys_to_select :: list\n @type predicate :: (key, val -> boolean)\n\n # @spec drop_by(struct, predicate) :: struct\n def drop_by(%_{} = struct, _), do: struct\n\n # @spec drop_by(map, predicate) :: map\n def drop_by(map, predicate) when is_map(map) do\n map\n |> Enum.reduce(%{}, fn {key, val}, acc ->\n cleaned_val = drop_by(val, predicate)\n\n if predicate.(key, cleaned_val) do\n acc\n else\n Map.put(acc, key, cleaned_val)\n end\n end)\n end\n\n # @spec drop_by(list, predicate) :: list\n def drop_by(list, predicate) when is_list(list) do\n Enum.map(list, &drop_by(&1, predicate))\n end\n\n def drop_by(elem, _) do\n elem\n end\n\n @doc \"\"\"\n Take a (nested) map and filter out any keys with specified values in the\n values_to_reject list.\n \"\"\"\n # @spec drop_by_value(%{any => any}, [any]) :: %{any => any}\n def drop_by_value(map, values_to_reject) when is_map(map) do\n drop_by(map, fn _, val -> val in values_to_reject end)\n end\n\n @doc \"\"\"\n Take a (nested) map and filter out any values with specified keys in the\n keys_to_reject list.\n \"\"\"\n # @spec drop_by_key(%{any => any}, [any]) :: %{any => any}\n def drop_by_key(map, keys_to_reject) when is_map(map) do\n drop_by(map, fn key, _ -> key in keys_to_reject end)\n end\n\n # @spec take_by(map, keys_to_select) :: map\n def take_by(map, keys_to_select) when is_map(map) do\n map\n |> Enum.reduce(%{}, fn {_key, val}, acc ->\n Map.merge(acc, take_by(val, keys_to_select))\n end)\n |> Map.merge(Map.take(map, keys_to_select))\n end\n\n def take_by(_elem, _) do\n %{}\n end\n\n @doc \"\"\"\n Take a (nested) map and keep any values with specified keys in the\n keys_to_select list.\n \"\"\"\n # @spec take_by_key(%{any => any}, [any]) :: %{any => any}\n def take_by_key(map, keys_to_select) when is_map(map) do\n Map.merge(take_by(map, keys_to_select), Map.take(map, keys_to_select))\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,1,1,null,1,null,null,null,1,null,null],"name":"lib/groupher_server_web/resolvers/delivery_resolver.ex","source":"defmodule GroupherServerWeb.Resolvers.Delivery do\n @moduledoc false\n\n alias GroupherServer.Delivery\n alias GroupherServer.Accounts.User\n # alias Helper.ORM\n\n def mention_someone(_root, args, %{context: %{cur_user: cur_user}}) do\n from_user_id = cur_user.id\n to_user_id = args.user_id\n\n Delivery.mention_someone(%User{id: from_user_id}, %User{id: to_user_id}, args)\n end\n\n def publish_system_notification(_root, args, %{context: %{cur_user: _}}) do\n Delivery.publish_system_notification(args)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,96,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,47,null,null],"name":"lib/groupher_server/statistics/publish_throttle.ex","source":"defmodule GroupherServer.Statistics.PublishThrottle do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n @optional_fields ~w(user_id publish_hour publish_date hour_count date_count last_publish_time)a\n @required_fields ~w(user_id)a\n\n @type t :: %PublishThrottle{}\n schema \"publish_throttles\" do\n field(:publish_hour, :utc_datetime)\n field(:publish_date, :date)\n field(:hour_count, :integer)\n field(:date_count, :integer)\n belongs_to(:user, Accounts.User)\n\n field(:last_publish_time, :utc_datetime)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PublishThrottle{} = publish_throttle, attrs) do\n publish_throttle\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user, name: :publish_throttles_user_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,45,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/mention_mail.ex","source":"defmodule GroupherServer.Accounts.MentionMail do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id source_id source_type source_preview)a\n @optional_fields ~w(parent_id parent_type read)a\n\n @type t :: %MentionMail{}\n schema \"mention_mails\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:parent_id, :string)\n field(:parent_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%MentionMail{} = mention, attrs) do\n mention\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,139,139,139,null,null,139,null,null,139,139,null,null,139,null,null,null,139,null,null,null,null,null,null,null,null,139,null,139,null,139,null,null,null,null,null,null,null,null,5,5,5,null,null,5,null,null,5,5,null,null,5,null,null,null,5,null,null,null,null,null,null,null,null,5,5,null,5,null,5,null,null,null,null,5,null,null,null,null,null,3,null,null,null,2,null,null,null,0,null,null,null,0,null,null],"name":"lib/groupher_server/cms/delegates/article_reaction.ex","source":"defmodule GroupherServer.CMS.Delegate.ArticleReaction do\n @moduledoc \"\"\"\n reaction[favorite, star, watch ...] on article [post, job, video...]\n \"\"\"\n import Helper.Utils, only: [done: 1, done: 2]\n import GroupherServer.CMS.Helper.MatcherOld\n import Ecto.Query, warn: false\n import Helper.ErrorCode\n\n alias Helper.ORM\n alias GroupherServer.{Accounts, Repo}\n\n alias Accounts.User\n alias Ecto.Multi\n\n @doc \"\"\"\n favorite / star / watch CMS contents like post / tuts / video ...\n \"\"\"\n # when valid_reaction(thread, react) do\n def reaction(thread, react, content_id, %User{id: user_id}) do\n with {:ok, action} <- match_action(thread, react),\n {:ok, content} <- ORM.find(action.target, content_id, preload: [author: :user]),\n {:ok, user} <- ORM.find(Accounts.User, user_id) do\n Multi.new()\n |> Multi.run(:create_reaction_record, fn _ ->\n create_reaction_record(action, user, thread, content)\n end)\n |> Multi.run(:add_achievement, fn _ ->\n achiever_id = content.author.user_id\n Accounts.achieve(%User{id: achiever_id}, :add, react)\n end)\n |> Repo.transaction()\n |> reaction_result()\n end\n end\n\n defp reaction_result({:ok, %{create_reaction_record: result}}), do: result |> done()\n\n defp reaction_result({:error, :create_reaction_record, _result, _steps}),\n do: {:error, [message: \"create reaction fails\", code: ecode(:react_fails)]}\n\n defp reaction_result({:error, :add_achievement, _result, _steps}),\n do: {:error, [message: \"achieve fails\", code: ecode(:react_fails)]}\n\n defp create_reaction_record(action, %User{id: user_id}, thread, content) do\n attrs = %{} |> Map.put(\"user_id\", user_id) |> Map.put(\"#{thread}_id\", content.id)\n\n action.reactor\n |> ORM.create(attrs)\n |> done(with: content)\n end\n\n # ------\n @doc \"\"\"\n unfavorite / unstar / unwatch CMS contents like post / tuts / video ...\n \"\"\"\n # when valid_reaction(thread, react) do\n def undo_reaction(thread, react, content_id, %User{id: user_id}) do\n with {:ok, action} <- match_action(thread, react),\n {:ok, content} <- ORM.find(action.target, content_id, preload: [author: :user]),\n {:ok, user} <- ORM.find(Accounts.User, user_id) do\n Multi.new()\n |> Multi.run(:delete_reaction_record, fn _ ->\n delete_reaction_record(action, user, thread, content)\n end)\n |> Multi.run(:minus_achievement, fn _ ->\n achiever_id = content.author.user_id\n Accounts.achieve(%User{id: achiever_id}, :minus, react)\n end)\n |> Repo.transaction()\n |> undo_reaction_result()\n end\n end\n\n defp undo_reaction_result({:ok, %{delete_reaction_record: result}}), do: result |> done()\n\n defp undo_reaction_result({:error, :delete_reaction_record, _result, _steps}),\n do: {:error, [message: \"delete reaction fails\", code: ecode(:react_fails)]}\n\n defp undo_reaction_result({:error, :minus_achievement, _result, _steps}),\n do: {:error, [message: \"achieve fails\", code: ecode(:react_fails)]}\n\n defp delete_reaction_record(action, %User{id: user_id}, thread, content) do\n user_where = dynamic([u], u.user_id == ^user_id)\n reaction_where = dynamic_reaction_where(thread, content.id, user_where)\n\n query = from(f in action.reactor, where: ^reaction_where)\n\n case Repo.one(query) do\n nil ->\n {:error, \"record not found\"}\n\n record ->\n Repo.delete(record)\n {:ok, content}\n end\n end\n\n defp dynamic_reaction_where(:post, id, user_where) do\n dynamic([p], p.post_id == ^id and ^user_where)\n end\n\n defp dynamic_reaction_where(:job, id, user_where) do\n dynamic([p], p.job_id == ^id and ^user_where)\n end\n\n defp dynamic_reaction_where(:video, id, user_where) do\n dynamic([p], p.video_id == ^id and ^user_where)\n end\n\n defp dynamic_reaction_where(:repo, id, user_where) do\n dynamic([p], p.repo_id == ^id and ^user_where)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,11,null,null,null,null,null,27,null,27,27,null,null,null,0,null,null,null,null,null,null,22,22,22,22,null,null,null,20,null,null,null,2,null,null,null,null,null,0,null,null,null,20,20,null,null,null,null,20,null,null,20,null,20,20,null,null,null,22,null,22,null,7,null,null,15,null,null,null,null,20,null,null,5,null,null,15,null,null,20,null,7,7,null,null,13,null,null,null,null,null,7,null,null,null,15,null,null,null,44,0,44,null,null,null,14,30,null,null,35,null,null],"name":"lib/groupher_server_web/middleware/passport_loader.ex","source":"defmodule GroupherServerWeb.Middleware.PassportLoader do\n @behaviour Absinthe.Middleware\n import GroupherServer.CMS.Helper.MatcherOld\n import Helper.Utils\n import Helper.ErrorCode\n\n import ShortMaps\n\n alias GroupherServer.CMS\n alias Helper.ORM\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(\n %{context: %{cur_user: _}, arguments: ~m(community_id)a} = resolution,\n source: :community\n ) do\n case ORM.find(CMS.Community, community_id) do\n {:ok, community} ->\n arguments = resolution.arguments |> Map.merge(%{passport_communities: [community]})\n %{resolution | arguments: arguments}\n\n {:error, err_msg} ->\n resolution\n |> handle_absinthe_error(err_msg, ecode(:passport))\n end\n end\n\n # def call(%{context: %{cur_user: cur_user}, arguments: %{id: id}} = resolution, [source: .., base: ..]) do\n # Loader 应该使用 Map 作为参数,以方便模式匹配\n def call(%{context: %{cur_user: _}, arguments: %{id: id}} = resolution, args) do\n with {:ok, thread, react} <- parse_source(args, resolution),\n {:ok, action} <- match_action(thread, react),\n {:ok, preload} <- parse_preload(action, args),\n {:ok, content} <- ORM.find(action.reactor, id, preload: preload) do\n resolution\n |> load_owner_info(react, content)\n |> load_source(content)\n |> load_community_info(content, args)\n else\n {:error, err_msg} ->\n resolution\n |> handle_absinthe_error(err_msg, ecode(:passport))\n end\n end\n\n def call(resolution, _) do\n # TODO communiy in args\n resolution\n end\n\n def load_source(resolution, content) do\n arguments = resolution.arguments |> Map.merge(%{passport_source: content})\n %{resolution | arguments: arguments}\n end\n\n # 取得 content 里面的 conmunities 字段\n def load_community_info(resolution, content, args) do\n communities = content |> Map.get(parse_base(args))\n\n # check if communities is a List\n communities = if is_list(communities), do: communities, else: [communities]\n\n arguments = resolution.arguments |> Map.merge(%{passport_communities: communities})\n %{resolution | arguments: arguments}\n end\n\n defp parse_preload(action, args) do\n {:ok, _, react} = parse_source(args)\n\n case react == :comment do\n true ->\n {:ok, action.preload}\n\n false ->\n {:ok, [action.preload, parse_base(args)]}\n end\n end\n\n def load_owner_info(%{context: %{cur_user: cur_user}} = resolution, react, content) do\n content_author_id =\n cond do\n react == :comment ->\n content.author.id\n\n true ->\n content.author.user_id\n end\n\n case content_author_id == cur_user.id do\n true ->\n arguments = resolution.arguments |> Map.merge(%{passport_is_owner: true})\n %{resolution | arguments: arguments}\n\n _ ->\n resolution\n end\n end\n\n # typical usage is delete_comment, should load conent by thread\n defp parse_source([source: [:arg_thread, react]], %{arguments: %{thread: thread}}) do\n parse_source(source: [thread, react])\n end\n\n defp parse_source(args, _resolution) do\n parse_source(args)\n end\n\n defp parse_source(args) do\n case Keyword.has_key?(args, :source) do\n false -> {:error, \"Invalid.option: #{args}\"}\n true -> args |> Keyword.get(:source) |> match_source\n end\n end\n\n defp match_source([thread, react]), do: {:ok, thread, react}\n defp match_source(thread), do: {:ok, thread, :self}\n\n defp parse_base(args) do\n Keyword.get(args, :base) || :communities\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,1,null,null,null,null,null,null,null,null,null,null,1,1,null,null,null,null,null,0,null,null,null],"name":"lib/groupher_server/application.ex","source":"defmodule GroupherServer.Application do\n use Application\n\n # See https://hexdocs.pm/elixir/Application.html\n # for more information on OTP Applications\n def start(_type, _args) do\n import Supervisor.Spec\n\n # Define workers and child supervisors to be supervised\n children = [\n # Start the Ecto repository\n supervisor(GroupherServer.Repo, []),\n # Start the endpoint when the application starts\n supervisor(GroupherServerWeb.Endpoint, [])\n # Start your own worker by calling: GroupherServer.Worker.start_link(arg1, arg2, arg3)\n # worker(GroupherServer.Worker, [arg1, arg2, arg3]),\n ]\n\n # See https://hexdocs.pm/elixir/Supervisor.html\n # for other strategies and supported options\n opts = [strategy: :one_for_one, name: GroupherServer.Supervisor]\n Supervisor.start_link(children, opts)\n end\n\n # Tell Phoenix to update the endpoint configuration\n # whenever the application is updated.\n def config_change(changed, _new, removed) do\n GroupherServerWeb.Endpoint.config_change(changed, removed)\n :ok\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,119,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,47,null,null],"name":"lib/groupher_server/cms/job_comment.ex","source":"defmodule GroupherServer.CMS.JobComment do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n alias GroupherServer.CMS.{Job, JobCommentReply}\n\n @required_fields ~w(body author_id job_id floor)a\n @optional_fields ~w(reply_id)a\n\n @type t :: %JobComment{}\n schema \"jobs_comments\" do\n field(:body, :string)\n field(:floor, :integer)\n belongs_to(:author, Accounts.User, foreign_key: :author_id)\n belongs_to(:job, Job, foreign_key: :job_id)\n belongs_to(:reply_to, JobComment, foreign_key: :reply_id)\n # belongs_to(:reply_to, JobComment, foreign_key: :job_id)\n has_many(:replies, {\"jobs_comments_replies\", JobCommentReply})\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%JobComment{} = job_comment, attrs) do\n job_comment\n |> cast(attrs, @required_fields ++ @optional_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:job_id)\n |> foreign_key_constraint(:author_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,15,null,null,null,null,null,null,null,null,null,null,null,null,7,null,null],"name":"lib/groupher_server/cms/post_comment_reply.ex","source":"defmodule GroupherServer.CMS.PostCommentReply do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.PostComment\n\n @required_fields ~w(post_comment_id reply_id)a\n\n @type t :: %PostCommentReply{}\n schema \"posts_comments_replies\" do\n belongs_to(:post_comment, PostComment, foreign_key: :post_comment_id)\n belongs_to(:reply, PostComment, foreign_key: :reply_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostCommentReply{} = post_comment_reply, attrs) do\n post_comment_reply\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:post_comment_id)\n |> foreign_key_constraint(:reply_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/community.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Community do\n @moduledoc \"\"\"\n CMS mations for community\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_mutation_community do\n @desc \"create a global community\"\n field :create_community, :community do\n arg(:title, non_null(:string))\n arg(:desc, non_null(:string))\n arg(:raw, non_null(:string))\n arg(:logo, non_null(:string))\n # arg(:category, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->community.create\")\n\n resolve(&R.CMS.create_community/3)\n # middleware(M.Statistics.MakeContribute, for: :user)\n middleware(M.Statistics.MakeContribute, for: [:user, :community])\n end\n\n @desc \"update a community\"\n field :update_community, :community do\n arg(:id, non_null(:id))\n arg(:title, :string)\n arg(:desc, :string)\n arg(:raw, :string)\n arg(:logo, :string)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->community.update\")\n\n resolve(&R.CMS.update_community/3)\n middleware(M.Statistics.MakeContribute, for: [:user, :community])\n end\n\n @desc \"delete a global community\"\n field :delete_community, :community do\n arg(:id, non_null(:id))\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->community.delete\")\n\n resolve(&R.CMS.delete_community/3)\n end\n\n @desc \"create category\"\n field :create_category, :category do\n arg(:title, non_null(:string))\n arg(:raw, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.create\")\n\n resolve(&R.CMS.create_category/3)\n end\n\n @desc \"delete category\"\n field :delete_category, :category do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.delete\")\n\n resolve(&R.CMS.delete_category/3)\n end\n\n @desc \"update category\"\n field :update_category, :category do\n arg(:id, non_null(:id))\n arg(:title, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.update\")\n\n resolve(&R.CMS.update_category/3)\n end\n\n @desc \"create independent thread\"\n field :create_thread, :thread do\n arg(:title, non_null(:string))\n arg(:raw, non_null(:thread))\n arg(:index, :integer, default_value: 0)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->thread.create\")\n\n resolve(&R.CMS.create_thread/3)\n end\n\n @desc \"add a editor for a community\"\n field :set_editor, :user do\n arg(:community_id, non_null(:id))\n arg(:user_id, non_null(:id))\n arg(:title, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->editor.set\")\n\n resolve(&R.CMS.set_editor/3)\n end\n\n @desc \"unset a editor from a community, the user's passport also deleted\"\n field :unset_editor, :user do\n arg(:community_id, non_null(:id))\n arg(:user_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->editor.unset\")\n\n resolve(&R.CMS.unset_editor/3)\n end\n\n # TODO: remove, should remove both editor and cms->passport\n @desc \"update cms editor's title, passport is not effected\"\n field :update_cms_editor, :user do\n arg(:community_id, non_null(:id))\n arg(:user_id, non_null(:id))\n arg(:title, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->editor.update\")\n\n resolve(&R.CMS.update_editor/3)\n end\n\n @desc \"create a tag\"\n field :create_tag, :article_tag do\n arg(:title, non_null(:string))\n arg(:color, non_null(:rainbow_color_enum))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.create\")\n\n resolve(&R.CMS.create_tag/3)\n end\n\n @desc \"update a tag\"\n field :update_tag, :article_tag do\n arg(:id, non_null(:id))\n arg(:title, non_null(:string))\n # arg(:color, non_null(:rainbow_color_enum))\n arg(:color, non_null(:rainbow_color_enum))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.update\")\n\n resolve(&R.CMS.update_tag/3)\n end\n\n @desc \"delete a tag by thread\"\n field :delete_tag, :article_tag do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.delete\")\n\n resolve(&R.CMS.delete_tag/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,6,null,6,null,null,2,null,null,0,null,null],"name":"lib/groupher_server_web/middleware/put_current_user.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.PutCurrentUser do\n @behaviour Absinthe.Middleware\n\n def call(%{context: %{cur_user: cur_user}} = resolution, _) do\n arguments = resolution.arguments |> Map.merge(%{cur_user: cur_user})\n\n %{resolution | arguments: arguments}\n end\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(resolution, _) do\n resolution\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,7,null,7,1,null,null,6,null,null,null,null,12,null,12,2,null,null,10,null,null,null,null,null,null,null,4,null,null,4,null,4,null,null,4,null,null,null,null,null,1,null,null,null,null,null,null,0,null,null,null,1,null,null,1,null,1,null,1,null,null,null,4,4,4,null,4,null,4,null,null,null,null,5,4,null,null,null,null,null,null,null,null,null,null,null,null,0,null,0,null,null,0,0,0,null,0,null,0,0,null,0,null,null,0,null,null,null,null,4,4,null,null,null,null,1,1,null,null,null,null,2,2,null,null,null,3,null,null,null,null,null,null,3,null,null],"name":"lib/groupher_server/statistics/delegates/contribute.ex","source":"defmodule GroupherServer.Statistics.Delegate.Contribute do\n import Ecto.Query, warn: false\n import Helper.Utils\n import ShortMaps\n\n alias GroupherServer.Repo\n alias GroupherServer.Accounts.User\n alias GroupherServer.CMS.Community\n alias GroupherServer.Statistics.{UserContribute, CommunityContribute}\n alias Helper.{ORM, QueryBuilder}\n\n @community_contribute_days get_config(:general, :community_contribute_days)\n @user_contribute_months get_config(:general, :user_contribute_months)\n\n def make_contribute(%Community{id: id}) do\n today = Timex.today() |> Date.to_iso8601()\n\n with {:ok, contribute} <- ORM.find_by(CommunityContribute, community_id: id, date: today) do\n contribute |> inc_contribute_count(:community) |> done\n else\n {:error, _} ->\n CommunityContribute |> ORM.create(%{community_id: id, date: today, count: 1})\n end\n end\n\n def make_contribute(%User{id: id}) do\n today = Timex.today() |> Date.to_iso8601()\n\n with {:ok, contribute} <- ORM.find_by(UserContribute, user_id: id, date: today) do\n contribute |> inc_contribute_count(:user) |> done\n else\n {:error, _} ->\n UserContribute |> ORM.create(%{user_id: id, date: today, count: 1})\n end\n end\n\n @doc \"\"\"\n Returns the list of user_contribute by latest 6 months.\n \"\"\"\n def list_contributes(%User{id: id}) do\n user_id = tobe_integer(id)\n\n \"user_contributes\"\n |> where([c], c.user_id == ^user_id)\n |> QueryBuilder.recent_inserted(months: @user_contribute_months)\n |> select([c], %{date: c.date, count: c.count})\n |> Repo.all()\n |> to_contrubutes_map()\n |> done\n end\n\n def list_contributes(%Community{id: id}) do\n %Community{id: id}\n |> get_contributes()\n |> done\n end\n\n def list_contributes_digest(%Community{id: id}) do\n %Community{id: id}\n |> get_contributes()\n |> to_counts_digest(days: @community_contribute_days)\n |> done\n end\n\n defp get_contributes(%Community{id: id}) do\n community_id = tobe_integer(id)\n\n \"community_contributes\"\n |> where([c], c.community_id == ^community_id)\n |> QueryBuilder.recent_inserted(days: @community_contribute_days)\n |> select([c], %{date: c.date, count: c.count})\n |> Repo.all()\n |> to_contribute_records()\n end\n\n defp to_contrubutes_map(data) do\n end_date = Timex.today()\n start_date = Timex.shift(Timex.today(), months: -6)\n total_count = Enum.reduce(data, 0, &(&1.count + &2))\n\n records = to_contribute_records(data)\n\n ~m(start_date end_date total_count records)a\n end\n\n defp to_contribute_records(data) do\n data\n |> Enum.map(fn %{count: count, date: date} ->\n %{\n date: convert_date(date),\n count: count\n }\n end)\n end\n\n # 返回 count 数组,方便前端绘图\n # example:\n # from: [0,0,0,0,0,0]\n # to: [0,30,3,8,0,0]\n # 如果 7 天都有 count, 不用计算直接 map 返回\n defp to_counts_digest(record, days: count) do\n case length(record) == @community_contribute_days + 1 do\n true ->\n Enum.map(record, & &1.count)\n\n false ->\n today = Timex.today() |> Date.to_erl()\n return_count = abs(count) + 1\n enmpty_tuple = return_count |> repeat(0) |> List.to_tuple()\n\n results =\n Enum.reduce(record, enmpty_tuple, fn record, acc ->\n diff = Timex.diff(Timex.to_date(record.date), today, :days)\n index = diff + abs(count)\n\n put_elem(acc, index, record.count)\n end)\n\n results |> Tuple.to_list()\n end\n end\n\n defp convert_date(date) do\n {:ok, edate} = Date.from_erl(date)\n edate\n end\n\n defp inc_contribute_count(contribute, :community) do\n CommunityContribute\n |> where([c], c.community_id == ^contribute.community_id and c.date == ^contribute.date)\n |> do_inc_count(contribute)\n end\n\n defp inc_contribute_count(contribute, :user) do\n UserContribute\n |> where([c], c.user_id == ^contribute.user_id and c.date == ^contribute.date)\n |> do_inc_count(contribute)\n end\n\n defp do_inc_count(query, contribute, count \\\\ 1) do\n {1, [result]} =\n Repo.update_all(\n query,\n [inc: [count: count]],\n returning: [:count]\n )\n\n put_in(contribute.count, result.count)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,361,null,null,null,null,2,null,null,null,null,null,null,2,null,null,null,null,null,1,null,null,null,null,null,1,null,null,null,2,null,null,null,1,null,null,null,1,null,null,5,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/utils/loader.ex","source":"defmodule GroupherServer.Accounts.Helper.Loader do\n @moduledoc \"\"\"\n dataloader for accounts\n \"\"\"\n import Ecto.Query, warn: false\n\n alias Helper.QueryBuilder\n alias GroupherServer.{Accounts, CMS, Repo}\n\n alias Accounts.{UserFollower, UserFollowing}\n\n def data, do: Dataloader.Ecto.new(Repo, query: &query/2)\n\n def query({\"communities_subscribers\", CMS.CommunitySubscriber}, %{count: _}) do\n CMS.CommunitySubscriber\n |> group_by([f], f.user_id)\n |> select([f], count(f.id))\n end\n\n def query({\"communities_subscribers\", CMS.CommunitySubscriber}, %{filter: filter}) do\n CMS.CommunitySubscriber\n |> QueryBuilder.filter_pack(filter)\n |> join(:inner, [u], c in assoc(u, :community))\n |> select([u, c], c)\n end\n\n def query({\"users_followers\", UserFollower}, %{count: _}) do\n UserFollower\n |> group_by([f], f.user_id)\n |> select([f], count(f.id))\n end\n\n def query({\"users_followings\", UserFollowing}, %{count: _}) do\n UserFollowing\n |> group_by([f], f.user_id)\n |> select([f], count(f.id))\n end\n\n def query({\"users_followers\", UserFollower}, %{viewer_did: _, cur_user: cur_user}) do\n UserFollower |> where([f], f.follower_id == ^cur_user.id)\n end\n\n def query({\"posts_favorites\", CMS.PostFavorite}, %{count: _}) do\n CMS.PostFavorite |> count_cotents\n end\n\n def query({\"jobs_favorites\", CMS.JobFavorite}, %{count: _}) do\n CMS.JobFavorite |> count_cotents\n end\n\n def query(queryable, _args), do: queryable\n\n defp count_cotents(queryable) do\n queryable\n |> group_by([f], f.user_id)\n |> select([f], count(f.id))\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,5,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/cms/job_comment_reply.ex","source":"defmodule GroupherServer.CMS.JobCommentReply do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.JobComment\n\n @required_fields ~w(job_comment_id reply_id)a\n\n @type t :: %JobCommentReply{}\n schema \"jobs_comments_replies\" do\n belongs_to(:job_comment, JobComment, foreign_key: :job_comment_id)\n belongs_to(:reply, JobComment, foreign_key: :reply_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%JobCommentReply{} = job_comment_reply, attrs) do\n job_comment_reply\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:job_comment_id)\n |> foreign_key_constraint(:reply_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,60,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1,null,1,1,null,1,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/helper/certification.ex","source":"defmodule Helper.Certification do\n @moduledoc \"\"\"\n valid editors and passport details\n \"\"\"\n def editor_titles(:cms) do\n [\"chief editor\", \"post editor\"]\n end\n\n def passport_rules(cms: \"chief editor\") do\n %{\n \"post.tag.create\" => true,\n \"post.tag.edit\" => true,\n \"post.article.markDelete\" => true\n }\n end\n\n # a |> Enum.map(fn(x) -> {x, false} end) |> Map.new\n # %{\n # cms: %{\n # system: ..,\n # community: ...,\n # },\n # statistics: %{\n # ....\n # },\n # otherMoudle: %{\n\n # }\n # }\n\n @doc \"\"\"\n 基础权限,社区权限\n \"\"\"\n def all_rules(:cms) do\n %{\n general: [\n \"system_notification.publish\",\n \"stamp_passport\",\n # community\n \"editor.set\",\n \"editor.unset\",\n \"editor.update\",\n \"community.create\",\n \"community.update\",\n \"community.delete\",\n \"category.create\",\n \"category.delete\",\n \"category.update\",\n \"category.set\",\n \"category.unset\",\n \"thread.create\",\n \"post.community.mirror\",\n \"post.community.unmirror\",\n \"job.community.mirror\",\n \"job.community.unmirror\",\n \"post.pin\",\n \"post.undo_pin\",\n \"post.markDelete\",\n \"post.undo_trash\"\n ],\n community: [\n # thread\n \"thread.set\",\n \"thread.unset\",\n \"post.edit\",\n \"post.markDelete\",\n \"post.delete\",\n \"job.edit\",\n \"job.markDelete\",\n \"job.delete\",\n # post tag\n \"post.tag.create\",\n \"post.tag.update\",\n \"post.tag.delete\",\n \"post.tag.set\",\n \"post.tag.unset\",\n # job tag\n \"job.tag.create\",\n \"job.tag.update\",\n \"job.tag.delete\",\n \"job.tag.set\",\n \"job.tag.unset\"\n ]\n }\n end\n\n def all_rules(:cms, :stringify) do\n rules = all_rules(:cms)\n\n %{\n general: rules.general |> Enum.map(fn x -> {x, false} end) |> Map.new() |> Jason.encode!(),\n community:\n rules.community |> Enum.map(fn x -> {x, false} end) |> Map.new() |> Jason.encode!()\n }\n end\nend\n\n# 可以编辑某个社区 post 版块的文章, 支持 owner\n# middleware(M.Passport, claim: \"cms->c?->posts.article.edit\")\n# middleware(M.Passport, claim: \"owner;cms->c?->posts.article.edit\")\n\n# 可以添加某个社区 posts 版块的 tag 标签, 同时可支持 owner\n# middleware(M.Passport, claim: \"cms->c?->posts.tag.add\")\n# middleware(M.Passport, claim: \"cms->c?->posts.tag.edit\")\n# middleware(M.Passport, claim: \"cms->c?->posts.tag.delete\")\n# middleware(M.Passport, claim: \"cms->c?->posts.tag.markDelete\")\n# middleware(M.Passport, claim: \"owner;cms->c?->posts.tag.delete\")\n\n# 可以给某个社区 posts 版块的 posts 设置标签(setTag), 同时可支持 owner?\n# middleware(M.Passport, claim: \"c?->posts.tag.set\")\n\n# 可以某个社区的 posts 版块置顶\n# middleware(M.Passport, claim: \"cms->c?->posts.setTop\")\n\n# 可以编辑某个社区所有版块的文章\n# middleware(M.Passport, claim: \"cms->c?->posts.articles.edit\")\n# middleware(M.Passport, claim: \"cms->c?->job.articles.edit\")\n# ....全部显示声明....\n# middleware(M.Passport, claim: \"cms->c?->radar.articles.edit\")\n\n# 可以给某个社区的某个版块添加/删除管理员, 实际上就是在给其他成员分配上面的权限,同时该用户会被添加到相应的管理员中\n# middleware(M.Passport, claim: \"cms->c?->posts.managers.add\")\n# middleware(M.Passport, claim: \"cms->c?->jobs.managers.add\")\n# middleware(M.Passport, claim: \"cms->c?->videos.managers.add\")\n# middleware(M.Passport, claim: \"cms->c?->videos.managers.delete\")\n\n# 可以给社区的版块设置审核后发布\n# middleware(M.Passport, claim: \"cms->c?->settings.posts.needReview\")\n# middleware(M.Passport, claim: \"cms->c?->posts.reviewer\") # 审核员 (一开始没必要加)\n\n# 在某个社区的某个版块屏蔽某个用户\n# middleware(M.Passport, claim: \"cms->c?->viewer->block\")\n\n# 查看某个社区的总访问量\n# middleware(M.Passport, claim: \"statistics->c?->click\")\n# middleware(M.Passport, claim: \"logs->c?->posts ...\")\n\n# defguard the_fuck(value) when String.contains?(value, \"->?\")\n# classify the require of this gateway"},{"coverage":[null,null,null,null,null,null,null,null,null,null,2,null,null,30,30,30,null,null,null,null,28,28,28,null,null,null,null,25,null,25,25,null,25,null,null,null,25,25,null,25,null,null,null,null,null,null,null,null,null,58,null,null,null,null,58,null,58,null,null,null,2,null,null,null,2,null,null,null,2,2,null,null,null,null,2,null,null,null,2,null,null,null,4,4,null,null,null,null,4,null,4,null,4,null,4,null,null,null,null,null,9,8,null,null,25,null,null,null,null,17,null,null,null,null,null,null,17,null,null,null,null,null,null,47,null,null,null,null,null],"name":"lib/groupher_server/accounts/delegates/mails.ex","source":"defmodule GroupherServer.Accounts.Delegate.Mails do\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1, done: 2]\n import ShortMaps\n\n alias GroupherServer.Repo\n alias GroupherServer.Accounts.{User, MentionMail, NotificationMail, SysNotificationMail}\n alias GroupherServer.Delivery\n alias Helper.ORM\n\n def mailbox_status(%User{} = user), do: Delivery.mailbox_status(user)\n\n def fetch_mentions(%User{} = user, filter) do\n with {:ok, mentions} <- Delivery.fetch_mentions(user, filter),\n {:ok, washed_mentions} <- wash_data(MentionMail, mentions.entries) do\n MentionMail |> messages_handler(washed_mentions, user, filter)\n end\n end\n\n def fetch_notifications(%User{} = user, filter) do\n with {:ok, notifications} <- Delivery.fetch_notifications(user, filter),\n {:ok, washed_notifications} <- wash_data(NotificationMail, notifications.entries) do\n NotificationMail |> messages_handler(washed_notifications, user, filter)\n end\n end\n\n def fetch_sys_notifications(%User{} = user, %{page: page, size: size, read: read}) do\n with {:ok, sys_notifications} <-\n Delivery.fetch_sys_notifications(user, %{page: page, size: size}),\n {:ok, washed_notifications} <-\n wash_data(SysNotificationMail, user, sys_notifications.entries) do\n SysNotificationMail\n |> Repo.insert_all(washed_notifications)\n\n SysNotificationMail\n |> order_by(desc: :inserted_at)\n |> where([m], m.user_id == ^user.id)\n |> where([m], m.read == ^read)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n end\n\n defp messages_handler(queryable, washed_data, %User{id: user_id}, %{\n page: page,\n size: size,\n read: read\n }) do\n queryable\n |> Repo.insert_all(washed_data)\n\n queryable\n |> order_by(desc: :inserted_at)\n |> where([m], m.to_user_id == ^user_id)\n |> where([m], m.read == ^read)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n def mark_mail_read(%MentionMail{id: id}, %User{} = user) do\n do_mark_mail_read(MentionMail, id, user)\n end\n\n def mark_mail_read(%NotificationMail{id: id}, %User{} = user) do\n do_mark_mail_read(NotificationMail, id, user)\n end\n\n def mark_mail_read(%SysNotificationMail{id: id}, %User{} = user) do\n with {:ok, mail} <- SysNotificationMail |> ORM.find_by(id: id, user_id: user.id) do\n mail |> ORM.update(%{read: true}) |> done(:status)\n end\n end\n\n def mark_mail_read_all(%User{} = user, :mention) do\n user |> do_mark_mail_read_all(MentionMail, :mention)\n end\n\n def mark_mail_read_all(%User{} = user, :notification) do\n user |> do_mark_mail_read_all(NotificationMail, :notification)\n end\n\n defp do_mark_mail_read(queryable, id, %User{} = user) do\n with {:ok, mail} <- queryable |> ORM.find_by(id: id, to_user_id: user.id) do\n mail |> ORM.update(%{read: true}) |> done(:status)\n end\n end\n\n defp do_mark_mail_read_all(%User{} = user, mail, atom) do\n query =\n mail\n |> where([m], m.to_user_id == ^user.id)\n\n Repo.update_all(query, set: [read: true])\n\n Delivery.mark_read_all(user, atom)\n end\n\n defp wash_data(MentionMail, []), do: {:ok, []}\n defp wash_data(NotificationMail, []), do: {:ok, []}\n\n defp wash_data(MentionMail, list), do: do_wash_data(list)\n defp wash_data(NotificationMail, list), do: do_wash_data(list)\n\n defp wash_data(SysNotificationMail, user, list) do\n convert =\n list\n |> Enum.map(\n &(Map.from_struct(&1)\n |> Map.delete(:__meta__)\n |> Map.put(:user_id, user.id))\n )\n\n {:ok, convert}\n end\n\n defp do_wash_data(list) do\n convert =\n list\n |> Enum.map(\n &(Map.from_struct(&1)\n |> Map.delete(:__meta__)\n |> Map.delete(:id)\n |> Map.delete(:from_user)\n |> Map.delete(:to_user))\n )\n\n {:ok, convert}\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,19,5,0,null,26,17,12,null],"name":"lib/groupher_server/statistics/statistics.ex","source":"defmodule GroupherServer.Statistics do\n @moduledoc \"\"\"\n The Statistics context.\n \"\"\"\n\n alias GroupherServer.Statistics.Delegate.{\n Contribute,\n Throttle\n }\n\n defdelegate make_contribute(info), to: Contribute\n defdelegate list_contributes(info), to: Contribute\n defdelegate list_contributes_digest(community), to: Contribute\n\n defdelegate log_publish_action(user), to: Throttle\n defdelegate load_throttle_record(user), to: Throttle\n defdelegate mock_throttle_attr(scope, user, opt), to: Throttle\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,315,null,null,null,null,null,null,null,315,null,null,null,null,204,null,null],"name":"lib/helper/guardian.ex","source":"defmodule Helper.Guardian do\n @moduledoc \"\"\"\n This module defines some helper function used by\n encode/decode jwt\n \"\"\"\n use Guardian, otp_app: :groupher_server\n\n @token_expireation 24 * 14\n\n def subject_for_token(resource, _claims) do\n {:ok, to_string(resource.id)}\n end\n\n def resource_from_claims(claims) do\n {:ok, %{id: claims[\"sub\"]}}\n end\n\n def jwt_encode(source, args \\\\ %{}) do\n encode_and_sign(source, args, ttl: {@token_expireation, :hour})\n end\n\n # jwt_decode\n def jwt_decode(token) do\n resource_from_token(token)\n end\nend"},{"coverage":[null,null,null,null,null,null,4,null,null,2,26,null,null,29,31,null,null,62,29,null,3,4,null],"name":"lib/groupher_server/delivery/delivery.ex","source":"defmodule GroupherServer.Delivery do\n @moduledoc \"\"\"\n The Delivery context.\n \"\"\"\n alias GroupherServer.Delivery.Delegate.{Mentions, Notifications, Utils}\n\n defdelegate mailbox_status(user), to: Utils\n\n # system_notifications\n defdelegate publish_system_notification(info), to: Notifications\n defdelegate fetch_sys_notifications(user, filter), to: Notifications\n\n # mentions\n defdelegate mention_someone(from_user, to_user, info), to: Mentions\n defdelegate fetch_mentions(user, filter), to: Mentions\n\n # notifications\n defdelegate notify_someone(from_user, to_user, info), to: Notifications\n defdelegate fetch_notifications(user, filter), to: Notifications\n\n defdelegate fetch_record(user), to: Utils\n defdelegate mark_read_all(user, opt), to: Utils\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/comment.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Comment do\n @moduledoc \"\"\"\n CMS mutations for comments\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_comment_mutations do\n @desc \"create a comment\"\n field :create_comment, :comment do\n # TODO use thread and force community pass-in\n arg(:thread, :thread, default_value: :post)\n arg(:id, non_null(:id))\n arg(:body, non_null(:string))\n\n # TDOO: use a comment resolver\n middleware(M.Authorize, :login)\n # TODO: 文章作者可以删除评论,文章可以设置禁止评论\n resolve(&R.CMS.create_comment/3)\n end\n\n field :delete_comment, :comment do\n arg(:thread, :thread, default_value: :post)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n # middleware(M.PassportLoader, source: [:post, :comment])\n middleware(M.PassportLoader, source: [:arg_thread, :comment])\n # TODO: 文章可以设置禁止评论\n # middleware(M.Passport, claim: \"owner;cms->c?->post.comment.delete\")\n middleware(M.Passport, claim: \"owner\")\n # middleware(M.Authorize, :login)\n resolve(&R.CMS.delete_comment/3)\n end\n\n @desc \"reply a exsiting comment\"\n field :reply_comment, :comment do\n arg(:thread, non_null(:thread), default_value: :post)\n arg(:id, non_null(:id))\n arg(:body, non_null(:string))\n\n middleware(M.Authorize, :login)\n\n resolve(&R.CMS.reply_comment/3)\n end\n\n @desc \"like a comment\"\n field :like_comment, :comment do\n arg(:thread, non_null(:cms_comment), default_value: :post_comment)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.like_comment/3)\n end\n\n @desc \"undo like comment\"\n # field :undo_like_comment, :idlike do\n field :undo_like_comment, :comment do\n arg(:thread, non_null(:cms_comment), default_value: :post_comment)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.undo_like_comment/3)\n end\n\n field :dislike_comment, :comment do\n arg(:thread, non_null(:cms_comment), default_value: :post_comment)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.dislike_comment/3)\n end\n\n field :undo_dislike_comment, :comment do\n arg(:thread, non_null(:cms_comment), default_value: :post_comment)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.undo_dislike_comment/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server.ex","source":"defmodule GroupherServer do\n @moduledoc \"\"\"\n GroupherServer keeps the contexts that define your domain\n and business logic.\n\n Contexts are also responsible for managing your data, regardless\n if it comes from the database, an external API or others.\n \"\"\"\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,123,null,null,null,null,null,null,null,null,null,null,null,null,null,26,null,null],"name":"lib/groupher_server/delivery/record.ex","source":"defmodule GroupherServer.Delivery.Record do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id)a\n @optional_fields ~w(mentions_record notifications_record sys_notifications_record)a\n\n @type t :: %Record{}\n schema \"delivery_records\" do\n field(:mentions_record, :map)\n field(:notifications_record, :map)\n field(:sys_notifications_record, :map)\n belongs_to(:user, User)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Record{} = record, attrs) do\n record\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/gettext.ex","source":"defmodule GroupherServerWeb.Gettext do\n @moduledoc \"\"\"\n A module providing Internationalization with a gettext-based API.\n\n By using [Gettext](https://hexdocs.pm/gettext),\n your module gains a set of macros for translations, for example:\n\n import GroupherServerWeb.Gettext\n\n # Simple translation\n gettext \"Here is the string to translate\"\n\n # Plural translation\n ngettext \"Here is the string to translate\",\n \"Here are the strings to translate\",\n 3\n\n # Domain-based translation\n dgettext \"errors\", \"Here is the error message to translate\"\n\n See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.\n \"\"\"\n use Gettext, otp_app: :groupher_server\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server_web/middleware/see_me.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.SeeMe do\n @behaviour Absinthe.Middleware\n\n def call(res, _) do\n # IO.inspect(\"see me\")\n res\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,45,null,null,null,null,null,null,null,null,null,null,null,null,null,19,null,null],"name":"lib/groupher_server/cms/post_comment_like.ex","source":"defmodule GroupherServer.CMS.PostCommentLike do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.PostComment\n\n @required_fields ~w(post_comment_id user_id)a\n\n @type t :: %PostCommentLike{}\n schema \"posts_comments_likes\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:post_comment, PostComment, foreign_key: :post_comment_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostCommentLike{} = post_comment_like, attrs) do\n post_comment_like\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:post_comment_id)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user_id, name: :posts_comments_likes_user_id_post_comment_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/accounts/bill.ex","source":"defmodule GroupherServer.Accounts.Bill do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id source_type source_title price)a\n @optional_fields ~w(source_id)a\n\n @type t :: %Bill{}\n schema \"bills\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:price, :integer)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Bill{} = bill, attrs) do\n bill\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/delivery/delivery_queries.ex","source":"defmodule GroupherServerWeb.Schema.Delivery.Queries do\n @moduledoc \"\"\"\n Delivery.Queries\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :delivery_queries do\n @desc \"get mention list?\"\n field :xxxx_todo, :boolean do\n arg(:id, non_null(:id))\n\n resolve(&R.Delivery.mention_someone/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,null,null,2,null,null,null,null,null,0,null,null,null,null,null,3,null,null,null,null,null,24,24,null,null,null,null,null,null,null,null,null,4,null,null,null,null,null,null,4,4,null,null,null,4,null,null,null,null,null,null,1,1,null,null,null,1,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null,44,null,null,372,null,null,123,null,null,8,null,null,1,null,null,1,null,null,null,null,7,null,null,null,0,null,null,0,null,null,0,null,null,1,null,null,1,null,null,null,null,null,7,null,null,null,7,null,null,7,null,null,null,7,null,null,7,null,null,null,7,null,null,7,null,null,null,7,null,null,null,91,null,null,null,0,null,null,null,null,null,null,2,null,null,null,null,null,null,7,null,null,null,null,null,null,14,null,null,null,82,null,null,null,44,null,null,298,null,null,null],"name":"lib/helper/query_builder.ex","source":"defmodule Helper.QueryBuilder do\n # alias GroupherServer.Repo\n import Ecto.Query, warn: false\n\n @doc \"\"\"\n handle [3] situation:\n\n 1. basic query with filter\n 2. reaction_user's count\n 3. is viewer reacted?\n\n bewteen [THREAD] and [REACT]\n [THREAD]: cms thread, include: Post, Job, Video, Repo ...\n [REACT]; favorites, stars, watchs ...\n \"\"\"\n def members_pack(queryable, %{filter: filter}) do\n queryable |> load_inner_users(filter)\n end\n\n def members_pack(queryable, %{viewer_did: _, cur_user: cur_user}) do\n queryable |> where([f], f.user_id == ^cur_user.id)\n end\n\n def members_pack(queryable, %{count: _, type: :post}) do\n queryable\n |> group_by([f], f.post_id)\n |> select([f], count(f.id))\n end\n\n def members_pack(queryable, %{count: _, type: :community}) do\n queryable\n |> group_by([f], f.community_id)\n |> select([f], count(f.id))\n end\n\n def load_inner_users(queryable, filter) do\n queryable\n |> join(:inner, [f], u in assoc(f, :user))\n |> select([f, u], u)\n |> filter_pack(filter)\n end\n\n @doc \"\"\"\n load replies of the given comment\n \"\"\"\n def load_inner_replies(queryable, filter) do\n queryable\n |> filter_pack(filter)\n |> join(:inner, [c], r in assoc(c, :reply))\n |> select([c, r], r)\n end\n\n @doc \"\"\"\n inserted in latest x mounth\n \"\"\"\n def recent_inserted(queryable, months: count) do\n end_of_today = Timex.now() |> Timex.end_of_day()\n x_months_ago = Timex.today() |> Timex.shift(months: -count) |> Timex.to_datetime()\n\n queryable\n |> where([q], q.inserted_at >= ^x_months_ago)\n |> where([q], q.inserted_at <= ^end_of_today)\n end\n\n @doc \"\"\"\n inserted in latest x days\n \"\"\"\n def recent_inserted(queryable, days: count) do\n end_of_today = Timex.now() |> Timex.end_of_day()\n x_days_ago = Timex.today() |> Timex.shift(days: -count) |> Timex.to_datetime()\n\n queryable\n |> where([q], q.inserted_at >= ^x_days_ago)\n |> where([q], q.inserted_at <= ^end_of_today)\n end\n\n # this is strategy will cause\n # defp sort_strategy(:desc_inserted), do: [desc: :inserted_at, desc: :views]\n # defp sort_strategy(:most_views), do: [desc: :views, desc: :inserted_at]\n # defp sort_strategy(:least_views), do: [asc: :views, desc: :inserted_at]\n # defp strategy(:most_stars), do: [desc: :views, desc: :inserted_at]\n\n defp sort_by_count(queryable, field, direction) do\n queryable\n |> join(:left, [p], s in assoc(p, ^field))\n |> group_by([p], p.id)\n |> select([p], p)\n |> order_by([_, s], {^direction, fragment(\"count(?)\", s.id)})\n end\n\n def default_article_filters, do: %{pin: false, markDelete: false}\n\n def filter_pack(queryable, filter) when is_map(filter) do\n Enum.reduce(filter, queryable, fn\n {:sort, :desc_inserted}, queryable ->\n # queryable |> order_by(^sort_strategy(:desc_inserted))\n queryable |> order_by(desc: :inserted_at)\n\n {:sort, :asc_inserted}, queryable ->\n queryable |> order_by(asc: :inserted_at)\n\n {:sort, :desc_index}, queryable ->\n queryable |> order_by(desc: :index)\n\n {:sort, :asc_index}, queryable ->\n queryable |> order_by(asc: :index)\n\n {:sort, :most_views}, queryable ->\n # this will cause error in Dialyzer\n # queryable |> order_by(^sort_strategy(:most_views))\n queryable |> order_by(desc: :views, desc: :inserted_at)\n\n {:sort, :least_views}, queryable ->\n # queryable |> order_by(^sort_strategy(:least_views))\n queryable |> order_by(asc: :views, desc: :inserted_at)\n\n {:sort, :most_stars}, queryable ->\n queryable |> sort_by_count(:stars, :desc)\n\n {:sort, :least_stars}, queryable ->\n queryable |> sort_by_count(:stars, :asc)\n\n {:sort, :most_likes}, queryable ->\n queryable |> sort_by_count(:likes, :desc)\n\n {:sort, :most_dislikes}, queryable ->\n queryable |> sort_by_count(:dislikes, :desc)\n\n {:when, :today}, queryable ->\n # date = DateTime.utc_now() |> Timex.to_datetime()\n # use timezone info is server is not in the some timezone\n # Timex.now(\"America/Chicago\")\n date = Timex.now()\n\n queryable\n |> where([p], p.inserted_at >= ^Timex.beginning_of_day(date))\n |> where([p], p.inserted_at <= ^Timex.end_of_day(date))\n\n {:when, :this_week}, queryable ->\n date = Timex.now()\n\n queryable\n |> where([p], p.inserted_at >= ^Timex.beginning_of_week(date))\n |> where([p], p.inserted_at <= ^Timex.end_of_week(date))\n\n {:when, :this_month}, queryable ->\n date = Timex.now()\n\n queryable\n |> where([p], p.inserted_at >= ^Timex.beginning_of_month(date))\n |> where([p], p.inserted_at <= ^Timex.end_of_month(date))\n\n {:when, :this_year}, queryable ->\n date = Timex.now()\n\n queryable\n |> where([p], p.inserted_at >= ^Timex.beginning_of_year(date))\n |> where([p], p.inserted_at <= ^Timex.end_of_year(date))\n\n # TODO: remove\n {_, :all}, queryable ->\n queryable\n\n # TODO: use raw instead title\n {:article_tag, tag_name}, queryable ->\n from(\n q in queryable,\n join: t in assoc(q, :tags),\n where: t.title == ^tag_name\n )\n\n {:category, catetory_raw}, queryable ->\n from(\n q in queryable,\n join: t in assoc(q, :categories),\n where: t.raw == ^catetory_raw\n )\n\n {:community, community_raw}, queryable ->\n from(\n q in queryable,\n join: t in assoc(q, :communities),\n where: t.raw == ^community_raw\n )\n\n {:first, first}, queryable ->\n queryable |> limit(^first)\n\n {:pin, bool}, queryable ->\n queryable\n |> where([p], p.pin == ^bool)\n\n {:markDelete, bool}, queryable ->\n queryable\n |> where([p], p.markDelete == ^bool)\n\n {_, _}, queryable ->\n queryable\n end)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,4092,null,null,null,6600,null,null,null,65366,null,null,null,null,null,null,null,null,null,null,null,361,null,null,null,361,null,null],"name":"lib/groupher_server_web/schema.ex","source":"defmodule GroupherServerWeb.Schema do\n @moduledoc \"\"\"\n scham index\n \"\"\"\n use Absinthe.Schema\n\n alias GroupherServerWeb.Schema.{Account, CMS, Delivery, Statistics, Utils}\n alias GroupherServerWeb.Middleware, as: M\n\n import_types(Absinthe.Type.Custom)\n\n # utils\n import_types(Utils.CommonTypes)\n\n # account\n import_types(Account.Types)\n import_types(Account.Queries)\n import_types(Account.Mutations)\n\n # statistics\n import_types(Statistics.Types)\n import_types(Statistics.Queries)\n import_types(Statistics.Mutations)\n\n # delivery\n import_types(Delivery.Types)\n import_types(Delivery.Queries)\n import_types(Delivery.Mutations)\n\n # cms\n import_types(CMS.Types)\n import_types(CMS.Queries)\n import_types(CMS.Mutations.Community)\n import_types(CMS.Mutations.Operation)\n import_types(CMS.Mutations.Post)\n import_types(CMS.Mutations.Job)\n import_types(CMS.Mutations.Comment)\n\n query do\n import_fields(:account_queries)\n import_fields(:statistics_queries)\n import_fields(:delivery_queries)\n import_fields(:cms_queries)\n end\n\n mutation do\n # account\n import_fields(:account_mutations)\n # statistics\n import_fields(:statistics_mutations)\n # delivery\n import_fields(:delivery_mutations)\n # cms\n import_fields(:cms_mutation_community)\n import_fields(:cms_opertion_mutations)\n import_fields(:cms_post_mutations)\n import_fields(:cms_job_mutations)\n import_fields(:cms_comment_mutations)\n end\n\n def middleware(middleware, _field, %{identifier: :query}) do\n middleware ++ [M.GeneralError]\n end\n\n def middleware(middleware, _field, %{identifier: :mutation}) do\n middleware ++ [M.ChangesetErrors]\n end\n\n def middleware(middleware, _field, _object) do\n [ApolloTracing.Middleware.Tracing, ApolloTracing.Middleware.Caching] ++ middleware\n end\n\n def plugins do\n [Absinthe.Middleware.Dataloader | Absinthe.Plugin.defaults()]\n end\n\n def dataloader do\n alias GroupherServer.{Accounts, CMS}\n\n Dataloader.new()\n |> Dataloader.add_source(Accounts, Accounts.Helper.Loader.data())\n |> Dataloader.add_source(CMS, CMS.Helper.Loader.data())\n end\n\n def context(ctx) do\n ctx |> Map.put(:loader, dataloader())\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/post.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Post do\n @moduledoc \"\"\"\n CMS mutations for post\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_post_mutations do\n @desc \"create a user\"\n field :create_post, :post do\n arg(:title, non_null(:string))\n arg(:body, non_null(:string))\n arg(:digest, non_null(:string))\n arg(:length, non_null(:integer))\n arg(:link_addr, :string)\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PublishThrottle)\n # middleware(M.PublishThrottle, interval: 3, hour_limit: 15, day_limit: 30)\n resolve(&R.CMS.create_article/3)\n end\n\n @desc \"pin a post\"\n field :pin_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->post.pin\")\n resolve(&R.CMS.pin_post/3)\n end\n\n @desc \"unpin a post\"\n field :undo_pin_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->post.undo_pin\")\n resolve(&R.CMS.undo_pin_post/3)\n end\n\n @desc \"markDelete a post, not delete\"\n field :trash_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->post.markDelete\")\n\n resolve(&R.CMS.trash_post/3)\n end\n\n @desc \"markDelete a post, not delete\"\n field :undo_trash_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->post.undo_trash\")\n\n resolve(&R.CMS.undo_trash_post/3)\n end\n\n @desc \"delete a cms/post\"\n # TODO: if post belongs to multi communities, unset instead delete\n field :delete_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :post)\n middleware(M.Passport, claim: \"owner;cms->c?->post.delete\")\n\n resolve(&R.CMS.delete_content/3)\n end\n\n @desc \"update a cms/post\"\n field :update_post, :post do\n arg(:id, non_null(:id))\n arg(:title, :string)\n arg(:body, :string)\n arg(:digest, :string)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :post)\n middleware(M.Passport, claim: \"owner;cms->c?->post.edit\")\n\n resolve(&R.CMS.update_article/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,106,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,12,null,null],"name":"lib/groupher_server/cms/category.ex","source":"defmodule GroupherServer.CMS.Category do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community}\n # alias GroupherServer.Accounts\n # alias Helper.Certification\n\n @required_fields ~w(title raw author_id)a\n\n @type t :: %Category{}\n\n schema \"categories\" do\n field(:title, :string)\n field(:raw, :string)\n belongs_to(:author, Author)\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_categories\",\n join_keys: [category_id: :id, community_id: :id],\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Category{} = category, attrs) do\n category\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n # |> validate_inclusion(:title, Certification.editor_titles(:cms))\n # |> foreign_key_constraint(:community_id)\n # |> foreign_key_constraint(:author_id)\n |> unique_constraint(:title, name: :categories_title_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/job.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Job do\n @moduledoc \"\"\"\n CMS mutations for job\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_job_mutations do\n @desc \"create a user\"\n field :create_job, :job do\n arg(:title, non_null(:string))\n arg(:company, non_null(:string))\n arg(:company_logo, non_null(:string))\n arg(:location, non_null(:string))\n arg(:body, non_null(:string))\n arg(:digest, non_null(:string))\n arg(:length, non_null(:integer))\n arg(:community_id, non_null(:id))\n arg(:link_addr, :string)\n arg(:link_source, :string)\n\n arg(:thread, :thread, default_value: :job)\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.create_article/3)\n end\n\n @desc \"delete a job\"\n field :delete_job, :job do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :job)\n middleware(M.Passport, claim: \"owner;cms->c?->job.delete\")\n\n resolve(&R.CMS.delete_content/3)\n end\n\n @desc \"update a cms/job\"\n field :update_job, :job do\n arg(:id, non_null(:id))\n arg(:title, :string)\n arg(:body, :string)\n arg(:digest, :string)\n # ...\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :job)\n middleware(M.Passport, claim: \"owner;cms->c?->job.edit\")\n\n resolve(&R.CMS.update_article/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/account/account_mutations.ex","source":"defmodule GroupherServerWeb.Schema.Account.Mutations do\n @moduledoc \"\"\"\n accounts mutations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :account_mutations do\n # @desc \"hehehef: create a user\"\n # field :create_user, :user do\n # arg(:username, non_null(:string))\n # arg(:nickname, non_null(:string))\n # arg(:bio, non_null(:string))\n # arg(:company, non_null(:string))\n\n # resolve(&R.Accounts.create_user/3)\n # end\n\n @desc \"update user's profile\"\n field :update_profile, :user do\n arg(:profile, non_null(:user_profile_input))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.update_profile/3)\n end\n\n field :github_signin, :token_info do\n arg(:code, non_null(:string))\n # arg(:profile, non_null(:github_profile_input))\n\n middleware(M.GithubUser)\n resolve(&R.Accounts.github_signin/3)\n end\n\n @doc \"follow a user\"\n field :follow, :user do\n arg(:user_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.follow/3)\n end\n\n @doc \"undo follow to a user\"\n field :undo_follow, :user do\n arg(:user_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.undo_follow/3)\n end\n\n @desc \"mark a mention as read\"\n field :mark_mention_read, :status do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_mention_read/3)\n end\n\n @desc \"mark a all unread mention as read\"\n field :mark_mention_read_all, :status do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_mention_read_all/3)\n end\n\n @desc \"mark a notification as read\"\n field :mark_notification_read, :status do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_notification_read/3)\n end\n\n @desc \"mark a all unread notifications as read\"\n field :mark_notification_read_all, :status do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_notification_read_all/3)\n end\n\n @desc \"mark a system notification as read\"\n field :mark_sys_notification_read, :status do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_sys_notification_read/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/utils/common_types.ex","source":"defmodule GroupherServerWeb.Schema.Helper.Metrics do\n use Absinthe.Schema.Notation\n use Absinthe.Ecto, repo: GroupherServer.Repo\n\n object :status do\n field(:done, :boolean)\n field(:id, :id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,151,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,62,null,null],"name":"lib/groupher_server/delivery/notification.ex","source":"defmodule GroupherServer.Delivery.Notification do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id action source_title source_id source_preview source_type)a\n @optional_fields ~w(parent_id parent_type read)a\n\n @type t :: %Notification{}\n schema \"notifications\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n field(:action, :string)\n\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:parent_id, :string)\n field(:parent_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Notification{} = notification, attrs) do\n notification\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,12,12,12,null,12,null,null,12,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/accounts/delegates/reacted_contents.ex","source":"defmodule GroupherServer.Accounts.Delegate.ReactedContents do\n @moduledoc \"\"\"\n get contents(posts, jobs, videos ...) that user reacted (star, favorite ..)\n \"\"\"\n import GroupherServer.CMS.Helper.MatcherOld\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1]\n import ShortMaps\n\n alias Helper.{ORM, QueryBuilder}\n alias GroupherServer.Accounts.User\n\n def reacted_contents(thread, react, ~m(page size)a = filter, %User{id: user_id}) do\n with {:ok, action} <- match_action(thread, react) do\n action.reactor\n |> where([f], f.user_id == ^user_id)\n |> join(:inner, [f], p in assoc(f, ^thread))\n |> select([f, p], p)\n |> QueryBuilder.filter_pack(filter)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n end\n\n # def reacted_count(thread, react, %User{id: user_id}) do\n # with {:ok, action} <- match_action(thread, react) do\n # action.reactor\n # |> where([f], f.user_id == ^user_id)\n # |> group_by([f], f.post_id)\n # |> select([f], count(f.id))\n # end\n # end\nend"},{"coverage":[null,null,null,null,null,null,null,null,4,null,4,null,null,null,0,null,null,0,null,null],"name":"lib/groupher_server_web/middleware/force_loader.ex","source":"# this is a tmp solution for load related-users like situations\n# it turn dataloader into nomal N+1 resolver\n# NOTE: it should be replaced using \"Select-Top-N-By-Group\" solution\n\ndefmodule GroupherServerWeb.Middleware.ForceLoader do\n @behaviour Absinthe.Middleware\n\n def call(%{source: %{id: id}} = resolution, _) do\n arguments = resolution.arguments |> Map.merge(%{what_ever: id})\n\n %{resolution | arguments: arguments}\n # resolution\n end\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(resolution, _) do\n resolution\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,475,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,167,null,null],"name":"lib/groupher_server/cms/post_comment.ex","source":"defmodule GroupherServer.CMS.PostComment do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n alias GroupherServer.CMS.{\n Post,\n PostCommentDislike,\n PostCommentLike,\n PostCommentReply\n }\n\n @required_fields ~w(body author_id post_id floor)a\n @optional_fields ~w(reply_id)a\n\n @type t :: %PostComment{}\n schema \"posts_comments\" do\n field(:body, :string)\n field(:floor, :integer)\n belongs_to(:author, Accounts.User, foreign_key: :author_id)\n belongs_to(:post, Post, foreign_key: :post_id)\n belongs_to(:reply_to, PostComment, foreign_key: :reply_id)\n\n has_many(:replies, {\"posts_comments_replies\", PostCommentReply})\n has_many(:likes, {\"posts_comments_likes\", PostCommentLike})\n has_many(:dislikes, {\"posts_comments_dislikes\", PostCommentDislike})\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostComment{} = post_comment, attrs) do\n post_comment\n |> cast(attrs, @required_fields ++ @optional_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:post_id)\n |> foreign_key_constraint(:author_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,96,96,96,null,null,96,null,null,null,null,null,null,null,6,6,6,null,null,6,null,null,null,null,null,null,null,5,5,5,null,null,5,null,null,null,null,null,null,null,4,4,4,null,null,4,null,null,null,null,null,null,null,136,136,136,null,null,136,null,null,null,null,null,null,null,5,5,5,null,5,null,null,5,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,30,null,null,null,null,24,null,null,null],"name":"lib/groupher_server/accounts/delegates/achievements.ex","source":"defmodule GroupherServer.Accounts.Delegate.Achievements do\n @moduledoc \"\"\"\n user achievements related\n acheiveements formula:\n 1. create content been stared by other user + 1\n 2. create content been watched by other user + 1\n 3. create content been favorited by other user + 2\n 4. followed by other user + 3\n \"\"\"\n import Helper.Utils, only: [get_config: 2]\n import ShortMaps\n\n alias Helper.{ORM, SpecType}\n alias GroupherServer.Accounts.{Achievement, User}\n\n @favorite_weight get_config(:general, :user_achieve_favorite_weight)\n @star_weight get_config(:general, :user_achieve_star_weight)\n # @watch_weight get_config(:general, :user_achieve_watch_weight)\n @follow_weight get_config(:general, :user_achieve_follow_weight)\n\n @doc \"\"\"\n add user's achievement by add followers_count of favorite_weight\n \"\"\"\n @spec achieve(User.t(), atom, atom) :: SpecType.done()\n def achieve(%User{id: user_id}, :add, :follow) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n followers_count = achievement.followers_count + @follow_weight\n reputation = achievement.reputation + @follow_weight\n\n achievement\n |> ORM.update(~m(followers_count reputation)a)\n end\n end\n\n @doc \"\"\"\n minus user's achievement by add followers_count of favorite_weight\n \"\"\"\n def achieve(%User{id: user_id}, :minus, :follow) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n followers_count = achievement.followers_count |> safe_minus(@follow_weight)\n reputation = achievement.reputation |> safe_minus(@follow_weight)\n\n achievement\n |> ORM.update(~m(followers_count reputation)a)\n end\n end\n\n @doc \"\"\"\n add user's achievement by contents_stared_count of star_weight\n \"\"\"\n def achieve(%User{id: user_id} = _user, :add, :star) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n contents_stared_count = achievement.contents_stared_count + @star_weight\n reputation = achievement.reputation + @star_weight\n\n achievement\n |> ORM.update(~m(contents_stared_count reputation)a)\n end\n end\n\n @doc \"\"\"\n minus user's achievement by contents_stared_count of star_weight\n \"\"\"\n def achieve(%User{id: user_id} = _user, :minus, :star) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n contents_stared_count = achievement.contents_stared_count |> safe_minus(@star_weight)\n reputation = achievement.reputation |> safe_minus(@star_weight)\n\n achievement\n |> ORM.update(~m(contents_stared_count reputation)a)\n end\n end\n\n @doc \"\"\"\n minus user's achievement by contents_favorited_count of favorite_weight\n \"\"\"\n def achieve(%User{id: user_id} = _user, :add, :favorite) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n contents_favorited_count = achievement.contents_favorited_count + @favorite_weight\n reputation = achievement.reputation + @favorite_weight\n\n achievement\n |> ORM.update(~m(contents_favorited_count reputation)a)\n end\n end\n\n @doc \"\"\"\n add user's achievement by contents_favorited_count of favorite_weight\n \"\"\"\n def achieve(%User{id: user_id} = _user, :minus, :favorite) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n contents_favorited_count =\n achievement.contents_favorited_count |> safe_minus(@favorite_weight)\n\n reputation = achievement.reputation |> safe_minus(@favorite_weight)\n\n achievement\n |> ORM.update(~m(contents_favorited_count reputation)a)\n end\n end\n\n # def achieve(%User{} = _user, :+, :watch) do\n # IO.inspect(\"acheiveements add :conent_watched\")\n # end\n\n # def achieve(%User{} = _user, :+, key) do\n # IO.inspect(\"acheiveements add #{key}\")\n # end\n\n # def achieve(%User{} = _user, :-, _key) do\n # IO.inspect(\"acheiveements plus\")\n # end\n\n @spec safe_minus(non_neg_integer(), non_neg_integer()) :: non_neg_integer()\n defp safe_minus(count, unit) when is_integer(count) and is_integer(unit) and unit > 0 do\n case count <= 0 do\n true ->\n 0\n\n false ->\n count - unit\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/channels/user_socket.ex","source":"defmodule GroupherServerWeb.UserSocket do\n use Phoenix.Socket\n\n ## Channels\n # channel \"room:*\", GroupherServerWeb.RoomChannel\n\n ## Transports\n transport(:websocket, Phoenix.Transports.WebSocket)\n # transport :longpoll, Phoenix.Transports.LongPoll\n\n # Socket params are passed from the client and can\n # be used to verify and authenticate a user. After\n # verification, you can put default assigns into\n # the socket that will be set for all channels, ie\n #\n # {:ok, assign(socket, :user_id, verified_user_id)}\n #\n # To deny connection, return `:error`.\n #\n # See `Phoenix.Token` documentation for examples in\n # performing token verification on connect.\n def connect(_params, socket) do\n {:ok, socket}\n end\n\n # Socket id's are topics that allow you to identify all sockets for a given user:\n #\n # def id(socket), do: \"user_socket:#{socket.assigns.user_id}\"\n #\n # Would allow you to broadcast a \"disconnect\" event and terminate\n # all active sockets and channels for a given user:\n #\n # GroupherServerWeb.Endpoint.broadcast(\"user_socket:#{user.id}\", \"disconnect\", %{})\n #\n # Returning `nil` makes this socket anonymous.\n def id(_socket), do: nil\nend"}]} \ No newline at end of file +{"source_files":[{"coverage":[null,null,null,null,null,null,null,null,null,8,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null,null,null,null],"name":"lib/groupher_server/accounts/github_user.ex","source":"defmodule GroupherServer.Accounts.GithubUser do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @type t :: %GithubUser{}\n schema \"github_users\" do\n belongs_to(:user, User)\n\n field(:github_id, :string)\n field(:login, :string)\n field(:avatar_url, :string)\n field(:url, :string)\n field(:html_url, :string)\n field(:name, :string)\n field(:company, :string)\n field(:blog, :string)\n field(:location, :string)\n field(:email, :string)\n field(:bio, :string)\n field(:public_repos, :integer)\n field(:public_gists, :integer)\n field(:followers, :integer)\n field(:following, :integer)\n field(:access_token, :string)\n field(:node_id, :string)\n\n timestamps(type: :utc_datetime)\n end\n\n # @required_fields ~w(github_id login name avatar_url)a\n @required_fields ~w(github_id login avatar_url user_id access_token node_id)a\n @optional_fields ~w(blog company email bio followers following location html_url public_repos public_gists)a\n\n @doc false\n def changeset(%GithubUser{} = github_user, attrs) do\n # |> cast(attrs, [:username, :nickname, :bio, :company])\n # |> validate_required([:username])\n # |> cast(attrs, @required_fields, @optional_fields)\n github_user\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:github_id)\n |> unique_constraint(:node_id)\n |> foreign_key_constraint(:user_id)\n\n # |> validate_length(:username, max: 20)\n # |> unique_constraint(:username)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,2,null,1,1,null,null,null,null,null,null,null,1,1,1,null,null,null,null,3,1,1,null,null,null,null,null,null,null,null,null,null,4,null,null,null,null,null,null,5,null,5,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/accounts/delegates/billing.ex","source":"defmodule GroupherServer.Accounts.Delegate.Billing do\n @moduledoc \"\"\"\n user billings related\n \"\"\"\n import Ecto.Query, warn: false\n\n alias Helper.ORM\n alias GroupherServer.Accounts.{Purchase, User}\n\n # ...\n def purchase_service(%User{} = _user, map) when map_size(map) == 0 do\n {:error, \"AccountPurchase: invalid option or not purchased\"}\n end\n\n def purchase_service(%User{} = user, map) when is_map(map) do\n valid? = map |> Map.keys() |> Enum.all?(&can_purchase?(user, &1, :boolean))\n\n case valid? do\n true ->\n attrs = Map.merge(%{user_id: user.id}, map)\n Purchase |> ORM.upsert_by([user_id: user.id], attrs)\n\n false ->\n {:error, \"AccountCustomization: invalid option or not purchased\"}\n end\n end\n\n def purchase_service(%User{} = user, key, value \\\\ true) do\n with {:ok, key} <- can_purchase?(user, key) do\n attrs = Map.put(%{user_id: user.id}, key, value)\n Purchase |> ORM.upsert_by([user_id: user.id], attrs)\n end\n end\n\n def has_purchased?(%User{} = user, key) do\n with {:ok, purchase} <- Purchase |> ORM.find_by(user_id: user.id),\n value <- purchase |> Map.get(key) do\n case value do\n true -> {:ok, key}\n false -> {:error, \"AccountPurchase: not purchase\"}\n end\n else\n nil -> {:error, \"AccountPurchase: not purchase\"}\n _ -> {:error, \"AccountPurchase: not purchase\"}\n end\n end\n\n defp can_purchase?(%User{} = user, key, :boolean) do\n case can_purchase?(%User{} = user, key) do\n {:ok, _} -> true\n {:error, _} -> false\n end\n end\n\n defp can_purchase?(%User{} = _user, key) do\n valid_service_options = valid_service()\n\n case key in valid_service_options do\n true -> {:ok, key}\n false -> {:error, \"AccountPurchase: purchase invalid service\"}\n end\n end\n\n defp valid_service do\n [:brainwash_free, :community_chart]\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,29,null,null,null,null,null,null,null,null,null,null,null,null,10,null,null],"name":"lib/groupher_server/statistics/user_contribute.ex","source":"defmodule GroupherServer.Statistics.UserContribute do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n @type t :: %UserContribute{}\n schema \"user_contributes\" do\n field(:count, :integer)\n field(:date, :date)\n belongs_to(:user, Accounts.User)\n\n timestamps()\n end\n\n @doc false\n def changeset(%UserContribute{} = user_contribute, attrs) do\n user_contribute\n |> cast(attrs, [:date, :count, :user_id])\n |> validate_required([:date, :count, :user_id])\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,77,84,null,null,6,null,3,22,2,3,2,0,null,4,4,4,null,null,null],"name":"lib/helper/error_code.ex","source":"defmodule Helper.ErrorCode do\n @moduledoc \"\"\"\n error code map for all site\n \"\"\"\n @default_base 4000\n @account_base 4300\n @changeset_base 4100\n @throttle_base 4200\n\n # account error code\n def ecode(:account_login), do: @account_base + 1\n def ecode(:passport), do: @account_base + 2\n # ...\n # changeset error code\n def ecode(:changeset), do: @changeset_base + 2\n # ...\n def ecode(:custom), do: @default_base + 1\n def ecode(:pagination), do: @default_base + 2\n def ecode(:not_exsit), do: @default_base + 3\n def ecode(:already_did), do: @default_base + 4\n def ecode(:self_conflict), do: @default_base + 5\n def ecode(:react_fails), do: @default_base + 6\n # throttle\n def ecode(:throttle_inverval), do: @throttle_base + 1\n def ecode(:throttle_hour), do: @throttle_base + 2\n def ecode(:throttle_day), do: @throttle_base + 3\n def ecode, do: @default_base\n # def ecode(_), do: @default_base\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,102,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,6,null,null,null,null],"name":"lib/groupher_server/cms/thread.ex","source":"defmodule GroupherServer.CMS.Thread do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n @optional_fields ~w(logo index)a\n @required_fields ~w(title raw)a\n\n @type t :: %Thread{}\n schema \"threads\" do\n field(:title, :string)\n field(:raw, :string)\n field(:logo, :string)\n field(:index, :integer)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Thread{} = thread, attrs) do\n thread\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> validate_length(:title, min: 2, max: 20)\n |> validate_length(:raw, min: 2, max: 20)\n |> unique_constraint(:title)\n\n # |> unique_constraint(:raw)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,29,null,null,29,29,29,29,null,null,null,null,29,null,null,null,null,null,null,31,null,null],"name":"lib/groupher_server/delivery/delegates/mentions.ex","source":"defmodule GroupherServer.Delivery.Delegate.Mentions do\n @moduledoc \"\"\"\n The Delivery context.\n \"\"\"\n import Helper.Utils, only: [done: 2]\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.Delivery.Mention\n alias Helper.ORM\n\n alias GroupherServer.Delivery.Delegate.Utils\n\n def mention_someone(%User{id: from_user_id}, %User{id: to_user_id}, info) do\n attrs = %{\n from_user_id: from_user_id,\n to_user_id: to_user_id,\n source_id: info.source_id,\n source_title: info.source_title,\n source_type: info.source_type,\n source_preview: info.source_preview\n }\n\n Mention\n |> ORM.create(attrs)\n |> done(:status)\n end\n\n @doc \"\"\"\n fetch mentions from Delivery stop\n \"\"\"\n def fetch_mentions(%User{} = user, %{page: _, size: _, read: _} = filter) do\n Utils.fetch_messages(user, Mention, filter)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,0,null,0,0,null,null,null,null,null],"name":"test/support/channel_case.ex","source":"defmodule GroupherServerWeb.ChannelCase do\n @moduledoc \"\"\"\n This module defines the test case to be used by\n channel tests.\n\n Such tests rely on `Phoenix.ChannelTest` and also\n import other functionality to make it easier\n to build common datastructures and query the data layer.\n\n Finally, if the test case interacts with the database,\n it cannot be async. For this reason, every test runs\n inside a transaction which is reset at the beginning\n of the test unless the test case is marked as async.\n \"\"\"\n\n use ExUnit.CaseTemplate\n\n using do\n quote do\n # Import conveniences for testing with channels\n use Phoenix.ChannelTest\n\n # The default endpoint for testing\n @endpoint GroupherServerWeb.Endpoint\n end\n end\n\n setup tags do\n :ok = Ecto.Adapters.SQL.Sandbox.checkout(GroupherServer.Repo)\n\n unless tags[:async] do\n Ecto.Adapters.SQL.Sandbox.mode(GroupherServer.Repo, {:shared, self()})\n end\n\n :ok\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,0,null,null,361,null,null,361,null,null,null,null,null,null,null,null,null,null,361,203,203,null,158,null,null,null,null,203,203,null,203,null,null,null,null,null,null,null,null,null,null,203,null,null,null,null,null,null],"name":"lib/groupher_server_web/context.ex","source":"# a plug for router ...\n\ndefmodule GroupherServerWeb.Context do\n @behaviour Plug\n\n import Plug.Conn\n # import Ecto.Query, only: [first: 1]\n\n alias GroupherServer.{Accounts, CMS}\n alias Helper.{Guardian, ORM}\n\n def init(opts), do: opts\n\n def call(conn, _) do\n context = build_context(conn)\n # put_private(conn, :absinthe, %{context: context})\n # TODO: use https://github.com/absinthe-graphql/absinthe/pull/497/files\n Absinthe.Plug.put_options(conn, context: context)\n end\n\n @doc \"\"\"\n Return the current user context based on the authorization header.\n\n Important: Note that at the current time this is just a stub, always\n returning the first user (marked as an admin), provided any\n authorization header is sent.\n \"\"\"\n def build_context(conn) do\n with [\"Bearer \" <> token] <- get_req_header(conn, \"authorization\"),\n {:ok, cur_user} <- authorize(token) do\n %{cur_user: cur_user}\n else\n _ -> %{}\n end\n end\n\n defp authorize(token) do\n with {:ok, claims, _info} <- Guardian.jwt_decode(token) do\n case ORM.find(Accounts.User, claims.id) do\n {:ok, user} ->\n check_passport(user)\n\n {:error, _} ->\n {:error,\n \"user is not exsit, try revoke token, or if you in dev env run the seeds first.\"}\n end\n end\n end\n\n # TODO gather role info from CMS or other context\n defp check_passport(%Accounts.User{} = user) do\n with {:ok, cms_passport} <- CMS.get_passport(%Accounts.User{id: user.id}) do\n {:ok, Map.put(user, :cur_passport, %{\"cms\" => cms_passport})}\n else\n {:error, _} -> {:ok, user}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1,null,7,null,9,3,null,6,null,7,1,2,1,null,null,null,8,2,null,59,1,null,8,1,null,76,2,null,null,44,30,9,null,null,139,5,null,null,null,28,null,10,1,null,5,1,null,null,197,5,11,3,9,null,null,null,20,2,null,14,2,null,null,80,4,211,3,0,null],"name":"lib/groupher_server/cms/cms.ex","source":"defmodule GroupherServer.CMS do\n @moduledoc \"\"\"\n this module defined basic method to handle [CMS] content [CURD] ..\n [CMS]: post, job, ...\n [CURD]: create, update, delete ...\n \"\"\"\n alias GroupherServer.CMS.Delegate.{\n ArticleCURD,\n ArticleCommunity,\n ArticleReaction,\n CommentCURD,\n CommentReaction,\n CommunityCURD,\n CommunityOperation,\n PassportCURD\n }\n\n # do not pattern match in delegating func, do it on one delegating inside\n # see https://github.com/elixir-lang/elixir/issues/5306\n\n # Community CURD: editors, thread, tag\n # >> editor ..\n defdelegate update_editor(user, community, title), to: CommunityCURD\n # >> subscribers / editors\n defdelegate community_members(type, community, filters), to: CommunityCURD\n # >> category\n defdelegate create_category(category_attrs, user), to: CommunityCURD\n defdelegate update_category(category_attrs), to: CommunityCURD\n # >> thread\n defdelegate create_thread(attrs), to: CommunityCURD\n # >> tag\n defdelegate create_tag(thread, attrs, user), to: CommunityCURD\n defdelegate update_tag(attrs), to: CommunityCURD\n defdelegate get_tags(community, thread), to: CommunityCURD\n defdelegate get_tags(filter), to: CommunityCURD\n\n # CommunityOperation\n # >> category\n defdelegate set_category(community, category), to: CommunityOperation\n defdelegate unset_category(community, category), to: CommunityOperation\n # >> editor\n defdelegate set_editor(community, title, user), to: CommunityOperation\n defdelegate unset_editor(community, user), to: CommunityOperation\n # >> thread\n defdelegate set_thread(community, thread), to: CommunityOperation\n defdelegate unset_thread(community, thread), to: CommunityOperation\n # >> subscribe / unsubscribe\n defdelegate subscribe_community(community, user), to: CommunityOperation\n defdelegate unsubscribe_community(community, user), to: CommunityOperation\n\n # ArticleCURD\n defdelegate paged_contents(queryable, filter), to: ArticleCURD\n defdelegate create_article(community, thread, attrs, user), to: ArticleCURD\n defdelegate reaction_users(thread, react, id, filters), to: ArticleCURD\n\n # ArticleReaction\n defdelegate reaction(thread, react, content_id, user), to: ArticleReaction\n defdelegate undo_reaction(thread, react, content_id, user), to: ArticleReaction\n\n # ArticleCommunity\n # >> set flag on article, like: pin / unpin article\n defdelegate set_flag(queryable, id, attrs, user), to: ArticleCommunity\n # >> tag: set / unset\n defdelegate set_tag(community, thread, tag, content_id), to: ArticleCommunity\n defdelegate unset_tag(thread, tag, content_id), to: ArticleCommunity\n # >> community: set / unset\n defdelegate mirror_article(community, thread, content_id), to: ArticleCommunity\n defdelegate unmirror_article(community, thread, content_id), to: ArticleCommunity\n\n # Comment CURD\n defdelegate create_comment(thread, content_id, body, user), to: CommentCURD\n defdelegate delete_comment(thread, content_id), to: CommentCURD\n defdelegate paged_comments(thread, content_id, filters), to: CommentCURD\n defdelegate paged_replies(thread, comment, user), to: CommentCURD\n defdelegate reply_comment(thread, comment, body, user), to: CommentCURD\n\n # Comment Reaction\n # >> like / undo like\n defdelegate like_comment(thread, comment, user), to: CommentReaction\n defdelegate undo_like_comment(thread, comment, user), to: CommentReaction\n # >> dislike / undo dislike\n defdelegate dislike_comment(thread, comment, user), to: CommentReaction\n defdelegate undo_dislike_comment(thread, comment, user), to: CommentReaction\n\n # Passport CURD\n defdelegate stamp_passport(rules, user), to: PassportCURD\n defdelegate erase_passport(rules, user), to: PassportCURD\n defdelegate get_passport(user), to: PassportCURD\n defdelegate paged_passports(community, key), to: PassportCURD\n defdelegate delete_passport(user), to: PassportCURD\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,115,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,6,null,null,null,null],"name":"lib/groupher_server/cms/tag.ex","source":"defmodule GroupherServer.CMS.Tag do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, Job, Post, Video}\n\n @required_fields ~w(thread title color author_id community_id)a\n\n @type t :: %Tag{}\n schema \"tags\" do\n field(:title, :string)\n field(:color, :string)\n field(:thread, :string)\n belongs_to(:community, Community)\n belongs_to(:author, Author)\n\n many_to_many(\n :posts,\n Post,\n join_through: \"posts_tags\",\n join_keys: [post_id: :id, tag_id: :id],\n # :delete_all will only remove data from the join source\n on_delete: :delete_all\n # on_replace: :delete\n )\n\n many_to_many(\n :videos,\n Video,\n join_through: \"videos_tags\",\n join_keys: [video_id: :id, tag_id: :id]\n )\n\n many_to_many(\n :jobs,\n Job,\n join_through: \"jobs_tags\",\n join_keys: [job_id: :id, tag_id: :id]\n )\n\n timestamps(type: :utc_datetime)\n end\n\n def changeset(%Tag{} = tag, attrs) do\n tag\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> foreign_key_constraint(:community_id)\n |> unique_constraint(:tag_duplicate, name: :tags_community_id_thread_title_index)\n\n # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,107,null,null,null,null,null,null,null,null,null,null,null,null,null,97,null,null],"name":"lib/groupher_server/accounts/user_follower.ex","source":"defmodule GroupherServer.Accounts.UserFollower do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id follower_id)a\n\n @type t :: %UserFollower{}\n schema \"users_followers\" do\n belongs_to(:user, User, foreign_key: :user_id)\n belongs_to(:follower, User, foreign_key: :follower_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%UserFollower{} = user_follower, attrs) do\n user_follower\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> foreign_key_constraint(:follower_id)\n |> unique_constraint(:user_id, name: :users_followers_user_id_follower_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,108,null,null,null,223,null,null,null,null,null,null,null,253,null,253,null,null,null,null,null,null,null,null,null,1407,null,null,null,null,null,null,null,null,1232,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,103,null,null,null,null,null,null,null,0,null,null,null,null,null,null,17,17,null,null,null,null,17,null,17,null,null,null,null,17,null,null,null,null,null,null,32,null,null,5,4,null,null,null,null,19,18,null,null,null,null,252,null,null,null,null,180,null,null,null,null,null,null,null,null,null,310,310,null,null,null,null,null,28,3,null,null,31,null,31,31,null,null,null,null,null,null,null,1,null,null,1,null,null,null,null,32,null,null,1,1,null,null,31,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,977,null,null,null,null,null,null,null,null,null,209,209,null,null,null,205,null,null],"name":"lib/helper/orm.ex","source":"defmodule Helper.ORM do\n @moduledoc \"\"\"\n General CORD functions\n \"\"\"\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1, done: 3, add: 1]\n import Helper.ErrorHandler\n import ShortMaps\n\n alias Helper.{QueryBuilder, SpecType}\n alias GroupherServer.Repo\n\n @doc \"\"\"\n a wrap for paginate request\n \"\"\"\n def paginater(queryable, page: page, size: size) do\n queryable |> Repo.paginate(page: page, page_size: size)\n end\n\n def paginater(queryable, ~m(page size)a) do\n queryable |> Repo.paginate(page: page, page_size: size)\n end\n\n @doc \"\"\"\n wrap Repo.get with preload and result/errer format handle\n \"\"\"\n def find(queryable, id, preload: preload) do\n queryable\n |> preload(^preload)\n |> Repo.get(id)\n |> done(queryable, id)\n end\n\n @doc \"\"\"\n simular to Repo.get/3, with standard result/error handle\n \"\"\"\n @spec find(Ecto.Queryable.t(), SpecType.id()) :: {:ok, any()} | {:error, String.t()}\n def find(queryable, id) do\n queryable\n |> Repo.get(id)\n |> done(queryable, id)\n end\n\n @doc \"\"\"\n simular to Repo.get_by/3, with standard result/error handle\n \"\"\"\n def find_by(queryable, clauses) do\n queryable\n |> Repo.get_by(clauses)\n |> case do\n nil ->\n {:error, not_found_formater(queryable, clauses)}\n\n result ->\n {:ok, result}\n end\n end\n\n @doc \"\"\"\n return pageinated Data required by filter\n \"\"\"\n # TODO: find content not in markDelete by default\n def find_all(queryable, %{page: page, size: size} = filter) do\n queryable\n |> QueryBuilder.filter_pack(filter)\n |> paginater(page: page, size: size)\n |> done()\n end\n\n @doc \"\"\"\n return Data required by filter\n \"\"\"\n # TODO: find content not in markDelete by default\n def find_all(queryable, filter) do\n queryable |> QueryBuilder.filter_pack(filter) |> Repo.all() |> done()\n end\n\n @doc \"\"\"\n Require queryable has a views fields to count the views of the queryable Modal\n \"\"\"\n def read(queryable, id, inc: :views) do\n with {:ok, result} <- find(queryable, id) do\n result |> inc_views_count(queryable) |> done()\n end\n end\n\n defp inc_views_count(content, queryable) do\n {1, [result]} =\n Repo.update_all(\n from(p in queryable, where: p.id == ^content.id),\n [inc: [views: 1]],\n returning: [:views]\n )\n\n put_in(content.views, result.views)\n end\n\n @doc \"\"\"\n NOTICE: this should be use together with Authorize/OwnerCheck etc Middleware\n DO NOT use it directly\n \"\"\"\n def delete(content), do: Repo.delete(content)\n\n def find_delete(queryable, id) do\n with {:ok, content} <- find(queryable, id) do\n delete(content)\n end\n end\n\n def findby_delete(queryable, clauses) do\n with {:ok, content} <- find_by(queryable, clauses) do\n delete(content)\n end\n end\n\n def findby_or_insert(queryable, clauses, attrs) do\n case queryable |> find_by(clauses) do\n {:ok, content} ->\n {:ok, content}\n\n {:error, _} ->\n queryable |> create(attrs)\n end\n end\n\n @doc \"\"\"\n NOTE: this should be use together with passport_loader etc Middleware\n DO NOT use it directly\n \"\"\"\n def update(content, attrs) do\n content\n |> content.__struct__.changeset(attrs)\n |> Repo.update()\n end\n\n @doc \"\"\"\n find and update sourc\n \"\"\"\n def find_update(queryable, id, attrs), do: do_find_update(queryable, id, attrs)\n def find_update(queryable, %{id: id} = attrs), do: do_find_update(queryable, id, attrs)\n\n defp do_find_update(queryable, id, attrs) do\n with {:ok, content} <- find(queryable, id) do\n content\n |> content.__struct__.changeset(attrs)\n |> Repo.update()\n end\n end\n\n @doc \"\"\"\n find then update\n \"\"\"\n def update_by(source, clauses, attrs) do\n with {:ok, content} <- find_by(source, clauses) do\n content\n |> Ecto.Changeset.change(attrs)\n |> Repo.update()\n end\n end\n\n def upsert_by(queryable, clauses, attrs) do\n case queryable |> find_by(clauses) do\n {:ok, content} ->\n content\n |> content.__struct__.changeset(attrs)\n |> Repo.update()\n\n {:error, _} ->\n queryable |> create(attrs)\n end\n end\n\n @doc \"\"\"\n see https://elixirforum.com/t/ecto-inc-dec-update-one-helpers/5564\n \"\"\"\n # def update_one(queryable, where, changes) do\n # query |> Ecto.Query.where(^where) |> Repo.update_all(set: changes)\n # end\n\n # def inc(queryable, where, changes) do\n # query |> Ecto.Query.where(^where) |> Repo.update_all(inc: changes)\n # end\n\n def create(model, attrs) do\n model\n |> struct\n |> model.changeset(attrs)\n |> Repo.insert()\n end\n\n @doc \"\"\"\n return the total count of a Modal based on id column\n also support filters\n \"\"\"\n def count(queryable, filter \\\\ %{}) do\n queryable\n |> QueryBuilder.filter_pack(filter)\n |> select([f], count(f.id))\n |> Repo.one()\n end\n\n def next_count(queryable) do\n queryable |> count() |> add()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,20,null,20,null,null,null,628,null,628,null,null,null,null,628,null,628,null,null],"name":"lib/helper/error_handler.ex","source":"defmodule Helper.ErrorHandler do\n @moduledoc \"\"\"\n This module defines some helper function used by\n handle/format changset errors\n \"\"\"\n alias GroupherServerWeb.Gettext, as: Translator\n\n def not_found_formater(queryable, id) when is_integer(id) or is_binary(id) do\n model = queryable |> to_string |> String.split(\".\") |> List.last()\n\n Translator |> Gettext.dgettext(\"404\", \"#{model}(%{id}) not found\", id: id)\n end\n\n def not_found_formater(queryable, clauses) do\n model = queryable |> to_string |> String.split(\".\") |> List.last()\n\n detail =\n clauses\n |> Enum.into(%{})\n |> Map.values()\n |> List.first()\n |> to_string\n\n Translator |> Gettext.dgettext(\"404\", \"#{model}(%{name}) not found\", name: detail)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,0,null,null,17,null,null,null,58,null,null,0,null],"name":"lib/groupher_server_web/middleware/covert_to_int.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.ConvertToInt do\n @behaviour Absinthe.Middleware\n # google: must appear in the GROUP BY clause or be used in an aggregate function\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(%{value: [value]} = resolution, _) do\n %{resolution | value: value}\n end\n\n def call(%{value: []} = resolution, _) do\n %{resolution | value: 0}\n end\n\n def call(resolution, _), do: resolution\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,44,null,null,null,44,null,null,null,null,44,44,38,38,null,38,null,35,null,null,3,3,null,3,3,null,null,null,null,3,null,null,null,6,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,30,30,30,26,26,null,26,26,null,26,26,null,null,null,null,null,null,null,null,null,null,null,null,null,9,9,null,9,null,null,null,9,null,null,null,null,null,null,null,46,null,null,null,null,46,null,null,null,null,null,7,null,null],"name":"lib/groupher_server/cms/delegates/article_curd.ex","source":"defmodule GroupherServer.CMS.Delegate.ArticleCURD do\n import Ecto.Query, warn: false\n import GroupherServer.CMS.Helper.MatcherOld\n import Helper.Utils, only: [done: 1]\n import ShortMaps\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.{Repo, CMS, Statistics}\n alias GroupherServer.CMS.Delegate.ArticleCommunity\n alias Helper.{ORM, QueryBuilder}\n\n alias CMS.{Author, Community}\n\n @doc \"\"\"\n get paged post / job ...\n \"\"\"\n def paged_contents(queryable, filter) do\n normal_content_fr = filter |> Map.merge(QueryBuilder.default_article_filters())\n\n queryable\n |> ORM.find_all(normal_content_fr)\n |> add_pin_contents_ifneed(queryable, filter)\n end\n\n # only first page need pin contents\n defp add_pin_contents_ifneed(contents, queryable, filter) do\n with {:ok, normal_contents} <- contents,\n true <- 1 == Map.get(normal_contents, :page_number) do\n pin_content_fr = filter |> Map.merge(%{pin: true})\n {:ok, pined_content} = queryable |> ORM.find_all(pin_content_fr)\n\n case pined_content |> Map.get(:total_count) do\n 0 ->\n contents\n\n _ ->\n pind_entries = pined_content |> Map.get(:entries)\n normal_entries = normal_contents |> Map.get(:entries)\n\n normal_count = normal_contents |> Map.get(:total_count)\n pind_count = pined_content |> Map.get(:total_count)\n\n normal_contents\n |> Map.put(:entries, pind_entries ++ normal_entries)\n |> Map.put(:total_count, pind_count + normal_count)\n |> done\n end\n else\n _error ->\n contents\n end\n end\n\n @doc \"\"\"\n Creates a content(post/job ...), and set community.\n\n ## Examples\n\n iex> create_post(%{field: value})\n {:ok, %Post{}}\n\n iex> create_post(%{field: bad_value})\n {:error, %Ecto.Changeset{}}\n\n \"\"\"\n def create_article(%Community{id: community_id}, thread, attrs, %User{id: user_id}) do\n with {:ok, author} <- ensure_author_exists(%User{id: user_id}),\n {:ok, action} <- match_action(thread, :community),\n {:ok, community} <- ORM.find(Community, community_id),\n {:ok, content} <-\n action.target\n |> struct()\n |> action.target.changeset(attrs)\n |> Ecto.Changeset.put_change(:author_id, author.id)\n |> Repo.insert() do\n Statistics.log_publish_action(%User{id: user_id})\n ArticleCommunity.mirror_article(community, thread, content.id)\n end\n end\n\n @doc \"\"\"\n get CMS contents\n post's favorites/stars/comments ...\n ...\n jobs's favorites/stars/comments ...\n\n with or without page info\n \"\"\"\n def reaction_users(thread, react, id, %{page: page, size: size} = filters) do\n # when valid_reaction(thread, react) do\n with {:ok, action} <- match_action(thread, react),\n {:ok, where} <- dynamic_where(thread, id) do\n # common_filter(action.reactor)\n action.reactor\n |> where(^where)\n |> QueryBuilder.load_inner_users(filters)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n end\n\n def ensure_author_exists(%User{} = user) do\n # unique_constraint: avoid race conditions, make sure user_id unique\n # foreign_key_constraint: check foreign key: user_id exsit or not\n # see alos no_assoc_constraint in https://hexdocs.pm/ecto/Ecto.Changeset.html\n %Author{user_id: user.id}\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.unique_constraint(:user_id)\n |> Ecto.Changeset.foreign_key_constraint(:user_id)\n |> Repo.insert()\n |> handle_existing_author()\n end\n\n defp handle_existing_author({:ok, author}), do: {:ok, author}\n\n defp handle_existing_author({:error, changeset}) do\n ORM.find_by(Author, user_id: changeset.data.user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/accounts/user_bill.ex","source":"defmodule GroupherServer.Accounts.UserBill do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.{Bill, User}\n\n @required_fields ~w(user_id bill_id)a\n\n @type t :: %UserBill{}\n schema \"users_bills\" do\n belongs_to(:user, User, foreign_key: :user_id)\n belongs_to(:bill, Bill, foreign_key: :bill_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%UserBill{} = user_bill, attrs) do\n user_bill\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> foreign_key_constraint(:bill_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,0,null,0,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,0,0,null,null,null,null],"name":"test/support/data_case.ex","source":"defmodule GroupherServer.DataCase do\n @moduledoc \"\"\"\n This module defines the setup for tests requiring\n access to the application's data layer.\n\n You may define functions here to be used as helpers in\n your tests.\n\n Finally, if the test case interacts with the database,\n it cannot be async. For this reason, every test runs\n inside a transaction which is reset at the beginning\n of the test unless the test case is marked as async.\n \"\"\"\n\n use ExUnit.CaseTemplate\n\n using do\n quote do\n alias GroupherServer.Repo\n\n import Ecto\n import Ecto.Changeset\n import Ecto.Query\n import GroupherServer.DataCase\n end\n end\n\n setup tags do\n :ok = Ecto.Adapters.SQL.Sandbox.checkout(GroupherServer.Repo)\n\n unless tags[:async] do\n Ecto.Adapters.SQL.Sandbox.mode(GroupherServer.Repo, {:shared, self()})\n end\n\n :ok\n end\n\n @doc \"\"\"\n A helper that transform changeset errors to a map of messages.\n\n assert {:error, changeset} = Accounts.create_user(%{password: \"short\"})\n assert \"password is too short\" in errors_on(changeset).password\n assert %{password: [\"password is too short\"]} = errors_on(changeset)\n\n \"\"\"\n def errors_on(changeset) do\n Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->\n Enum.reduce(opts, message, fn {key, value}, acc ->\n String.replace(acc, \"%{#{key}}\", to_string(value))\n end)\n end)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/account/account_queries.ex","source":"defmodule GroupherServerWeb.Schema.Account.Queries do\n @moduledoc \"\"\"\n accounts GraphQL queries\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :account_queries do\n @desc \"get all users\"\n field :paged_users, non_null(:paged_users) do\n arg(:filter, non_null(:paged_users_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.users/3)\n end\n\n @desc \"get user by id\"\n field :user, :user do\n arg(:id, non_null(:id))\n\n resolve(&R.Accounts.user/3)\n end\n\n @desc \"get login-user's info\"\n field :account, :user do\n middleware(M.Authorize, :login)\n\n resolve(&R.Accounts.account/3)\n end\n\n @desc \"anyone can get anyone's subscribed communities\"\n field :subscribed_communities, :paged_communities do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.subscribed_communities/3)\n end\n\n @desc \"get user's follower\"\n field :paged_followers, :paged_users do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.paged_followers/3)\n end\n\n @desc \"get user's follower\"\n field :paged_followings, :paged_users do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.paged_followings/3)\n end\n\n @desc \"get favorited posts\"\n field :favorited_posts, :paged_posts do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.favorited_posts/3)\n end\n\n @desc \"get favorited jobs\"\n field :favorited_jobs, :paged_jobs do\n arg(:user_id, :id)\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.favorited_jobs/3)\n end\n\n @desc \"get all passport rules include system and community etc ...\"\n field :all_passport_rules_string, :rules do\n middleware(M.Authorize, :login)\n\n resolve(&R.Accounts.get_all_rules/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/logs/user_activity.ex","source":"defmodule GroupherServer.Logs.UserActivity do\n @moduledoc false\n # alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id source_title source_id source_type)a\n # @optional_fields ~w(source_type)a\n\n schema \"user_activity_logs\" do\n belongs_to(:user, User)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(user_activity, attrs) do\n user_activity\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,42,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/notification.mail.ex","source":"defmodule GroupherServer.Accounts.NotificationMail do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id action source_id source_type source_preview)a\n @optional_fields ~w(parent_id parent_type read)a\n\n @type t :: %NotificationMail{}\n schema \"notification_mails\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n field(:action, :string)\n\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:parent_id, :string)\n field(:parent_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%NotificationMail{} = notication_mail, attrs) do\n notication_mail\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,51,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"test/support/test_tools.ex","source":"defmodule GroupherServer.TestTools do\n @moduledoc \"\"\"\n helper for reduce import mudules in test files\n \"\"\"\n use ExUnit.CaseTemplate\n\n using do\n quote do\n use GroupherServerWeb.ConnCase, async: true\n\n import GroupherServer.Factory\n import GroupherServer.Test.ConnSimulator\n import GroupherServer.Test.AssertHelper\n import Ecto.Query, warn: false\n import Helper.ErrorCode\n\n import ShortMaps\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,8,null,null,null,null,null,null,null,2,null,2,null,null,null,null,null,null,null,8,8,null,null,null,null,null,null,null,1,null,1,null,null,null,null,null,null,null,null,null,null,null,null,null,59,59,null,null,59,null,null,null,null,null,null,1,1,1,null,null,null,null,59,null,null,null,null,null,null,null,null,null,null,null,null,76,75,null,null,null,null,2,null,1,null,null,null],"name":"lib/groupher_server/cms/delegates/community_operation.ex","source":"defmodule GroupherServer.CMS.Delegate.CommunityOperation do\n @moduledoc \"\"\"\n community operations, like: set/unset category/thread/editor...\n \"\"\"\n import ShortMaps\n\n alias Ecto.Multi\n alias Helper.{Certification, ORM}\n alias GroupherServer.Accounts.User\n alias GroupherServer.CMS.Delegate.PassportCURD\n alias GroupherServer.Repo\n\n alias GroupherServer.CMS.{\n Category,\n Community,\n CommunityCategory,\n CommunityEditor,\n CommunitySubscriber,\n CommunityThread,\n Thread\n }\n\n @doc \"\"\"\n set a category to community\n \"\"\"\n def set_category(%Community{id: community_id}, %Category{id: category_id}) do\n with {:ok, community_category} <-\n CommunityCategory |> ORM.create(~m(community_id category_id)a) do\n Community |> ORM.find(community_category.community_id)\n end\n end\n\n @doc \"\"\"\n unset a category to community\n \"\"\"\n def unset_category(%Community{id: community_id}, %Category{id: category_id}) do\n with {:ok, community_category} <-\n CommunityCategory |> ORM.findby_delete!(~m(community_id category_id)a) do\n Community |> ORM.find(community_category.community_id)\n end\n end\n\n @doc \"\"\"\n set to thread to a community\n \"\"\"\n def set_thread(%Community{id: community_id}, %Thread{id: thread_id}) do\n with {:ok, community_thread} <- CommunityThread |> ORM.create(~m(community_id thread_id)a) do\n Community |> ORM.find(community_thread.community_id)\n end\n end\n\n @doc \"\"\"\n unset to thread to a community\n \"\"\"\n def unset_thread(%Community{id: community_id}, %Thread{id: thread_id}) do\n with {:ok, community_thread} <-\n CommunityThread |> ORM.findby_delete!(~m(community_id thread_id)a) do\n Community |> ORM.find(community_thread.community_id)\n end\n end\n\n @doc \"\"\"\n set a community editor\n \"\"\"\n def set_editor(%Community{id: community_id}, title, %User{id: user_id}) do\n Multi.new()\n |> Multi.insert(\n :insert_editor,\n CommunityEditor.changeset(%CommunityEditor{}, ~m(user_id community_id title)a)\n )\n |> Multi.run(:stamp_passport, fn _ ->\n rules = Certification.passport_rules(cms: title)\n PassportCURD.stamp_passport(rules, %User{id: user_id})\n end)\n |> Repo.transaction()\n |> set_editor_result()\n end\n\n @doc \"\"\"\n unset a community editor\n \"\"\"\n def unset_editor(%Community{id: community_id}, %User{id: user_id}) do\n with {:ok, _} <- ORM.findby_delete!(CommunityEditor, ~m(user_id community_id)a),\n {:ok, _} <- PassportCURD.delete_passport(%User{id: user_id}) do\n User |> ORM.find(user_id)\n end\n end\n\n defp set_editor_result({:ok, %{insert_editor: editor}}) do\n User |> ORM.find(editor.user_id)\n end\n\n defp set_editor_result({:error, :stamp_passport, _result, _steps}),\n do: {:error, \"stamp passport error\"}\n\n defp set_editor_result({:error, :insert_editor, _result, _steps}),\n do: {:error, \"insert editor error\"}\n\n @doc \"\"\"\n subscribe a community. (ONLY community, post etc use watch )\n \"\"\"\n def subscribe_community(%Community{id: community_id}, %User{id: user_id}) do\n with {:ok, record} <- CommunitySubscriber |> ORM.create(~m(user_id community_id)a) do\n Community |> ORM.find(record.community_id)\n end\n end\n\n def unsubscribe_community(%Community{id: community_id}, %User{id: user_id}) do\n with {:ok, record} <-\n CommunitySubscriber |> ORM.findby_delete!(community_id: community_id, user_id: user_id) do\n Community |> ORM.find(record.community_id)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null],"name":"lib/groupher_server_web/schema/account/account_misc.ex","source":"defmodule GroupherServerWeb.Schema.Account.Metrics do\n use Absinthe.Schema.Notation\n\n import GroupherServerWeb.Schema.Helper.Fields\n # import Helper.Utils, only: [get_config: 2]\n # @page_size get_config(:general, :page_size)\n\n @desc \"article_filter doc\"\n input_object :paged_users_filter do\n pagination_args()\n # field(:when, :when_enum)\n # field(:sort, :sort_enum)\n # field(:article_tag, :string, default_value: :all)\n # field(:community, :string)\n end\n\n input_object :github_profile_input do\n # is github_id in db table\n field(:id, non_null(:string))\n field(:login, non_null(:string))\n field(:avatar_url, non_null(:string))\n field(:url, :string)\n field(:html_url, :string)\n field(:name, :string)\n field(:company, :string)\n field(:blog, :string)\n field(:location, :string)\n field(:email, :string)\n field(:bio, :string)\n field(:public_repos, :integer)\n field(:public_gists, :integer)\n end\n\n input_object :user_profile_input do\n field(:nickname, :string)\n field(:bio, :string)\n field(:sex, :string)\n field(:education, :string)\n field(:location, :string)\n field(:company, :string)\n field(:email, :string)\n field(:qq, :string)\n field(:weibo, :string)\n field(:weichat, :string)\n end\n\n # see: https://github.com/absinthe-graphql/absinthe/issues/206\n # https://github.com/absinthe-graphql/absinthe/wiki/Scalar-Recipes\n scalar :json, name: \"Json\" do\n description(\"\"\"\n The `Json` scalar type represents arbitrary json string data, represented as UTF-8\n character sequences. The Json type is most often used to represent a free-form\n human-readable json string.\n \"\"\")\n\n serialize(&encode/1)\n parse(&decode/1)\n end\n\n @spec decode(Absinthe.Blueprint.Input.String.t()) :: {:ok, term()} | :error\n @spec decode(Absinthe.Blueprint.Input.Null.t()) :: {:ok, nil}\n defp decode(%Absinthe.Blueprint.Input.String{value: value}) do\n case Jason.decode(value) do\n {:ok, result} -> {:ok, result}\n _ -> :error\n end\n end\n\n defp decode(%Absinthe.Blueprint.Input.Null{}) do\n {:ok, nil}\n end\n\n defp decode(_) do\n :error\n end\n\n defp encode(value), do: value\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,18,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,null,null,null,null],"name":"lib/groupher_server/cms/community_category.ex","source":"defmodule GroupherServer.CMS.CommunityCategory do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Category, Community}\n\n @type t :: %CommunityCategory{}\n\n schema \"communities_categories\" do\n belongs_to(:community, Community, foreign_key: :community_id)\n belongs_to(:category, Category, foreign_key: :category_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @required_fields ~w(community_id category_id)a\n\n @doc false\n def changeset(%CommunityCategory{} = community_category, attrs) do\n community_category\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:community_id)\n |> foreign_key_constraint(:category_id)\n |> unique_constraint(\n :community_id,\n name: :communities_categories_community_id_category_id_index\n )\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,0,null,0,null,null,168,null],"name":"lib/groupher_server_web/middleware/general_error.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.GeneralError do\n @behaviour Absinthe.Middleware\n\n def call(%{errors: [List = errors]} = resolution, _) do\n message = [%{message: errors}]\n\n %{resolution | value: [], errors: message}\n end\n\n def call(resolution, _), do: resolution\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,11,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/cms/post_star.ex","source":"defmodule GroupherServer.CMS.PostStar do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Post\n\n @required_fields ~w(user_id post_id)a\n\n @type t :: %PostStar{}\n schema \"posts_stars\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:post, Post, foreign_key: :post_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostStar{} = post_star, attrs) do\n # |> unique_constraint(:user_id, name: :favorites_user_id_article_id_index)\n post_star\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:user_id, name: :posts_stars_user_id_post_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,197,197,196,196,null,196,196,null,null,null,null,196,null,196,null,null,null,153,43,null,null,null,null,null,5,5,5,null,5,5,null,null,null,null,null,null,null,null,null,null,null,null,11,11,null,11,null,null,null,11,null,null,null,null,3,3,3,null,3,null,3,null,null,null,null,9,9,9,null,9,null,null,null,null,null,null,9,9,null,null,null,null,7,null,2,null,null,null,7,7,null,7,7,null,7,null,null,null,null,null,2,2,null,2,null,2,null,null,null,null,null,196,null,null,null,196,null,null,null,null,9,null,null,null,9,null,null,162,45,null,7,2,null],"name":"lib/groupher_server/cms/delegates/comment_curd.ex","source":"defmodule GroupherServer.CMS.Delegate.CommentCURD do\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1]\n import GroupherServer.CMS.Helper.MatcherOld\n import ShortMaps\n\n alias GroupherServer.{Repo, Accounts}\n alias Helper.{ORM, QueryBuilder}\n alias GroupherServer.CMS.{PostCommentReply, JobCommentReply}\n\n @doc \"\"\"\n Creates a comment for psot, job ...\n \"\"\"\n def create_comment(thread, content_id, body, %Accounts.User{id: user_id}) do\n with {:ok, action} <- match_action(thread, :comment),\n {:ok, content} <- ORM.find(action.target, content_id),\n {:ok, user} <- ORM.find(Accounts.User, user_id) do\n next_floor = get_next_floor(thread, action.reactor, content.id)\n\n attrs = %{\n author_id: user.id,\n body: body,\n floor: next_floor\n }\n\n attrs = merge_comment_attrs(thread, attrs, content.id)\n\n action.reactor |> ORM.create(attrs)\n end\n end\n\n defp merge_comment_attrs(:post, attrs, id), do: attrs |> Map.merge(%{post_id: id})\n defp merge_comment_attrs(:job, attrs, id), do: attrs |> Map.merge(%{job_id: id})\n\n @doc \"\"\"\n Delete the comment and increase all the floor after this comment\n \"\"\"\n def delete_comment(thread, content_id) do\n with {:ok, action} <- match_action(thread, :comment),\n {:ok, comment} <- ORM.find(action.reactor, content_id) do\n case ORM.delete(comment) do\n {:ok, comment} ->\n Repo.update_all(\n from(p in action.reactor, where: p.id > ^comment.id),\n inc: [floor: -1]\n )\n\n {:ok, comment}\n\n {:error, error} ->\n {:error, error}\n end\n end\n end\n\n def paged_comments(thread, content_id, %{page: page, size: size} = filters) do\n with {:ok, action} <- match_action(thread, :comment) do\n dynamic = dynamic_comment_where(thread, content_id)\n\n action.reactor\n |> where(^dynamic)\n |> QueryBuilder.filter_pack(filters)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n end\n\n def paged_replies(thread, comment_id, %Accounts.User{id: user_id}) do\n with {:ok, action} <- match_action(thread, :comment) do\n action.reactor\n |> where([c], c.author_id == ^user_id)\n |> join(:inner, [c], r in assoc(c, :reply_to))\n |> where([c, r], r.id == ^comment_id)\n |> Repo.all()\n |> done()\n end\n end\n\n def reply_comment(thread, comment_id, body, %Accounts.User{id: user_id}) do\n with {:ok, action} <- match_action(thread, :comment),\n {:ok, comment} <- ORM.find(action.reactor, comment_id) do\n next_floor = get_next_floor(thread, action.reactor, comment)\n\n attrs = %{\n author_id: user_id,\n body: body,\n reply_to: comment,\n floor: next_floor\n }\n\n attrs = merge_reply_attrs(thread, attrs, comment)\n brige_reply(thread, action.reactor, comment, attrs)\n end\n end\n\n defp merge_reply_attrs(:post, attrs, comment),\n do: attrs |> Map.merge(%{post_id: comment.post_id})\n\n defp merge_reply_attrs(:job, attrs, comment), do: attrs |> Map.merge(%{job_id: comment.job_id})\n\n defp brige_reply(:post, queryable, comment, attrs) do\n # TODO: use Multi task to refactor\n with {:ok, reply} <- ORM.create(queryable, attrs) do\n ORM.update(reply, %{reply_id: comment.id})\n\n {:ok, _} =\n PostCommentReply |> ORM.create(%{post_comment_id: comment.id, reply_id: reply.id})\n\n queryable |> ORM.find(reply.id)\n end\n end\n\n defp brige_reply(:job, queryable, comment, attrs) do\n # TODO: use Multi task to refactor\n with {:ok, reply} <- ORM.create(queryable, attrs) do\n ORM.update(reply, %{reply_id: comment.id})\n\n {:ok, _} = JobCommentReply |> ORM.create(%{job_comment_id: comment.id, reply_id: reply.id})\n\n queryable |> ORM.find(reply.id)\n end\n end\n\n # for create comment\n defp get_next_floor(thread, queryable, id) when is_integer(id) do\n dynamic = dynamic_comment_where(thread, id)\n\n queryable\n |> where(^dynamic)\n |> ORM.next_count()\n end\n\n # for reply comment\n defp get_next_floor(thread, queryable, comment) do\n dynamic = dynamic_reply_where(thread, comment)\n\n queryable\n |> where(^dynamic)\n |> ORM.next_count()\n end\n\n defp dynamic_comment_where(:post, id), do: dynamic([c], c.post_id == ^id)\n defp dynamic_comment_where(:job, id), do: dynamic([c], c.job_id == ^id)\n\n defp dynamic_reply_where(:post, comment), do: dynamic([c], c.post_id == ^comment.post_id)\n defp dynamic_reply_where(:job, comment), do: dynamic([c], c.job_id == ^comment.job_id)\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,2,2,null,null,null,null,2,null,null,null,62,null,null,62,62,62,62,62,null,null,62,null,null,null,null,null,null,29,null,null,null,26,null,null],"name":"lib/groupher_server/delivery/delegates/notifications.ex","source":"defmodule GroupherServer.Delivery.Delegate.Notifications do\n @moduledoc \"\"\"\n The Delivery context.\n \"\"\"\n import Helper.Utils, only: [done: 2]\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.Delivery.{Notification, SysNotification}\n alias Helper.ORM\n\n alias GroupherServer.Delivery.Delegate.Utils\n\n # TODO: audience\n def publish_system_notification(info) do\n attrs = %{\n source_id: info.source_id,\n source_title: info.source_title,\n source_type: info |> Map.get(:source_type, \"\"),\n source_preview: info |> Map.get(:source_preview, \"\")\n }\n\n SysNotification |> ORM.create(attrs) |> done(:status)\n end\n\n def notify_someone(%User{id: from_user_id}, %User{id: to_user_id}, info) do\n attrs = %{\n from_user_id: from_user_id,\n to_user_id: to_user_id,\n action: info.action,\n source_id: info.source_id,\n source_title: info.source_title,\n source_type: info.source_type,\n source_preview: info.source_preview\n }\n\n Notification |> ORM.create(attrs)\n end\n\n @doc \"\"\"\n fetch notifications from Delivery\n \"\"\"\n def fetch_notifications(%User{} = user, %{page: _, size: _, read: _} = filter) do\n Utils.fetch_messages(user, Notification, filter)\n end\n\n def fetch_sys_notifications(%User{} = user, %{page: _, size: _} = filter) do\n Utils.fetch_messages(:sys_notification, user, filter)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,1722,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,34,null,null,null,null,null],"name":"lib/groupher_server/cms/post.ex","source":"defmodule GroupherServer.CMS.Post do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, PostComment, PostFavorite, PostStar, Tag}\n\n @required_fields ~w(title body digest length)a\n @optional_fields ~w(link_addr pin markDelete)\n\n @type t :: %Post{}\n schema \"cms_posts\" do\n field(:body, :string)\n field(:title, :string)\n field(:digest, :string)\n field(:link_addr, :string)\n field(:length, :integer)\n field(:views, :integer, default: 0)\n\n field(:pin, :boolean, default_value: false)\n field(:markDelete, :boolean, default_value: false)\n belongs_to(:author, Author)\n\n # TODO\n # 相关文章\n # has_may(:related_post, ...)\n\n has_many(:comments, {\"posts_comments\", PostComment})\n has_many(:favorites, {\"posts_favorites\", PostFavorite})\n has_many(:stars, {\"posts_stars\", PostStar})\n # The keys are inflected from the schema names!\n # see https://hexdocs.pm/ecto/Ecto.Schema.html\n many_to_many(\n :tags,\n Tag,\n join_through: \"posts_tags\",\n join_keys: [post_id: :id, tag_id: :id],\n # :delete_all will only remove data from the join source\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_posts\",\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Post{} = post, attrs) do\n post\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n\n # |> foreign_key_constraint(:posts_tags, name: :posts_tags_tag_id_fkey)\n # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,0,null,null,null,null,null,null,0,0,null,0,null,null,0,null,null,0,0,null,null,null,null,0,null,null,null,0,0,null,0,0,null,0,null,null,null,null,null,null,null,null,null,null,null,null,0,0,null,null,null,null,0,0,0,null,null,null,null],"name":"lib/helper/oauth2/github.ex","source":"defmodule Helper.OAuth2.Github do\n use Tesla, only: [:get, :post]\n import Helper.Utils, only: [get_config: 2]\n\n # see Tesla intro: https://medium.com/@teamon/introducing-tesla-the-flexible-http-client-for-elixir-95b699656d88\n @timeout_limit 5000\n @client_id get_config(:github_oauth, :client_id)\n @client_secret get_config(:github_oauth, :client_secret)\n @redirect_uri \"http://www.coderplanets.com\"\n\n # wired only this style works\n plug(Tesla.Middleware.BaseUrl, \"https://github.com/login/oauth\")\n # plug(Tesla.Middleware.BaseUrl, \"https://www.github.com/login/oauth\")\n # plug(Tesla.Middleware.BaseUrl, \"https://api.github.com/login/oauth\")\n plug(Tesla.Middleware.Headers, %{\n \"User-Agent\" => \"groupher server\"\n # \"Accept\" => \"application/json\"\n # \"Accept\" => \"application/json;application/vnd.github.jean-grey-preview+json\"\n })\n\n plug(Tesla.Middleware.Retry, delay: 200, max_retries: 2)\n plug(Tesla.Middleware.Timeout, timeout: @timeout_limit)\n plug(Tesla.Middleware.JSON)\n plug(Tesla.Middleware.FormUrlencoded)\n\n def user_profile(code) do\n # body = \"client_id=#{@client_id}&client_secret=#{@client_secret}&code=#{code}&redirect_uri=#{@redirect_uri}\"\n # post(\"access_token?#{body}\",%{})\n headers = %{\"Accept\" => \"application/json\"}\n\n query = [\n code: code,\n client_id: @client_id,\n client_secret: @client_secret,\n redirect_uri: @redirect_uri\n ]\n\n try do\n case post(\"/access_token\", %{}, query: query, headers: headers) do\n %{status: 200, body: %{\"error\" => error, \"error_description\" => description}} ->\n {:error, \"#{error}: #{description}\"}\n\n %{status: 200, body: %{\"access_token\" => access_token, \"token_type\" => \"bearer\"}} ->\n user_info(access_token)\n end\n rescue\n e ->\n e |> handle_tesla_error\n end\n end\n\n def user_info(access_token) do\n url = \"https://api.github.com/user\"\n # this special header is too get node_id\n # see: https://developer.github.com/v3/\n\n headers = %{\"Accept\" => \"application/vnd.github.jean-grey-preview+json\"}\n query = [access_token: access_token]\n\n try do\n case get(url, query: query, headers: headers) do\n %{status: 200, body: body} ->\n body = body |> Map.merge(%{\"access_token\" => access_token})\n {:ok, body}\n\n %{status: 401, body: body} ->\n {:error, \"OAuth2 Github: \" <> body[\"message\"]}\n\n %{status: 403, body: body} ->\n {:error, \"OAuth2 Github: \" <> body}\n\n _ ->\n {:error, \"OAuth2 Github: unhandle error\"}\n end\n rescue\n e ->\n e |> handle_tesla_error\n end\n end\n\n defp handle_tesla_error(error) do\n case error do\n %{reason: :timeout} -> {:error, \"OAuth2 Github: timeout in #{@timeout_limit} msec\"}\n %{reason: reason} -> {:error, \"OAuth2 Github: #{reason}\"}\n _ -> {:error, \"unhandle error #{inspect(error)}\"}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/cms_misc.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Metrics do\n use Absinthe.Schema.Notation\n\n import GroupherServerWeb.Schema.Helper.Fields\n\n alias GroupherServer.CMS\n\n @default_inner_page_size 5\n\n enum :comment_replies_type do\n value(:comment_replies_type)\n end\n\n enum :post_type do\n value(:post)\n end\n\n enum :community_type do\n value(:community)\n end\n\n enum :favorite_action do\n value(:favorite)\n end\n\n enum :count_type do\n value(:count)\n end\n\n enum :viewer_did_type do\n value(:viewer_did)\n end\n\n enum :star_action do\n value(:star)\n end\n\n enum :comment_action do\n value(:comment)\n end\n\n enum :unique_type do\n value(true)\n value(false)\n end\n\n enum :cms_action do\n value(:favorite)\n value(:star)\n value(:watch)\n end\n\n enum :thread do\n value(:post)\n value(:job)\n value(:video)\n value(:repo)\n value(:wiki)\n end\n\n enum :cms_comment do\n value(:post_comment)\n end\n\n enum :order_enum do\n value(:asc)\n value(:desc)\n end\n\n enum :when_enum do\n value(:today)\n value(:this_week)\n value(:this_month)\n value(:this_year)\n end\n\n enum :comment_sort_enum do\n value(:asc_inserted)\n value(:desc_inserted)\n value(:most_likes)\n value(:most_dislikes)\n end\n\n enum :thread_sort_enum do\n value(:asc_index)\n value(:desc_index)\n value(:asc_inserted)\n value(:desc_inserted)\n end\n\n enum :sort_enum do\n value(:most_views)\n value(:most_updated)\n value(:most_favorites)\n value(:most_stars)\n value(:most_watched)\n value(:most_comments)\n value(:least_views)\n value(:least_updated)\n value(:least_favorites)\n value(:least_stars)\n value(:least_watched)\n value(:least_comments)\n value(:recent_updated)\n end\n\n enum :rainbow_color_enum do\n value(:red)\n value(:orange)\n value(:yellow)\n value(:green)\n value(:cyan)\n value(:blue)\n value(:purple)\n end\n\n @desc \"inline members-like filter for dataloader usage\"\n input_object :members_filter do\n field(:first, :integer, default_value: @default_inner_page_size)\n end\n\n input_object :comments_filter do\n pagination_args()\n field(:sort, :comment_sort_enum, default_value: :asc_inserted)\n end\n\n input_object :communities_filter do\n @desc \"limit of records (default 20), if first > 30, only return 30 at most\"\n pagination_args()\n field(:sort, :sort_enum)\n field(:category, :string)\n end\n\n input_object :threads_filter do\n pagination_args()\n field(:sort, :thread_sort_enum)\n end\n\n input_object :paged_filter do\n @desc \"limit of records (default 20), if first > 30, only return 30 at most\"\n pagination_args()\n field(:sort, :sort_enum)\n end\n\n @desc \"article_filter doc\"\n input_object :article_filter do\n @desc \"limit of records (default 20), if first > 30, only return 30 at most\"\n field(:first, :integer)\n\n @desc \"Matching a tag\"\n field(:article_tag, :string, default_value: :all)\n # field(:sort, :sort_input)\n field(:when, :when_enum)\n field(:sort, :sort_enum)\n # @desc \"Matching a tag\"\n # @desc \"Added to the menu after this date\"\n # field(:added_after, :datetime)\n end\n\n @desc \"article_filter doc\"\n input_object :paged_article_filter do\n @desc \"limit of records (default 20), if first > 30, only return 30 at most\"\n pagination_args()\n\n field(:when, :when_enum)\n field(:sort, :sort_enum)\n field(:article_tag, :string, default_value: :all)\n field(:community, :string)\n\n # @desc \"Matching a name\"\n # field(:order, :order_enum, default_value: :desc)\n\n # @desc \"Matching a tag\"\n # field(:article_tag, :string, default_value: :all)\n end\n\n @doc \"\"\"\n only used for reaction result, like: favorite/star/watch ...\n \"\"\"\n interface :article do\n field(:id, :id)\n field(:title, :string)\n\n resolve_type(fn\n %CMS.Post{}, _ -> :post\n _, _ -> nil\n end)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,3,2,0,null,null,252,null,null,99,4,3,3,null,null,12,null,null,30,null,null,28,25,null,null,2,4,6,null,null,0,4,3,null,null,6,4,null],"name":"lib/groupher_server/accounts/accounts.ex","source":"defmodule GroupherServer.Accounts do\n @moduledoc false\n\n alias GroupherServer.Accounts.Delegate.{\n Achievements,\n Billing,\n Customization,\n Fans,\n Mails,\n Profile,\n ReactedContents\n }\n\n # profile\n defdelegate update_profile(user, attrs), to: Profile\n defdelegate github_signin(github_user), to: Profile\n defdelegate default_subscribed_communities(filter), to: Profile\n defdelegate subscribed_communities(user, filter), to: Profile\n\n # achievement\n defdelegate achieve(user, operation, key), to: Achievements\n\n # fans\n defdelegate follow(user, follower), to: Fans\n defdelegate undo_follow(user, follower), to: Fans\n defdelegate fetch_followers(user, filter), to: Fans\n defdelegate fetch_followings(user, filter), to: Fans\n\n # reacted contents\n defdelegate reacted_contents(thread, react, filter, user), to: ReactedContents\n\n # mentions\n defdelegate fetch_mentions(user, filter), to: Mails\n\n # notifications\n defdelegate fetch_notifications(user, filter), to: Mails\n defdelegate fetch_sys_notifications(user, filter), to: Mails\n\n # common message\n defdelegate mailbox_status(user), to: Mails\n defdelegate mark_mail_read_all(user, opt), to: Mails\n defdelegate mark_mail_read(mail, user), to: Mails\n\n # purchase\n defdelegate purchase_service(user, key, value), to: Billing\n defdelegate purchase_service(user, key), to: Billing\n defdelegate has_purchased?(user, key), to: Billing\n\n # customization\n defdelegate add_custom_setting(user, key, value), to: Customization\n defdelegate add_custom_setting(user, key), to: Customization\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,18,null,null,null,10,null,null,null,31,31,31,null,null,31,31,null,null,null,null,null,1,1,1,null,null,1,1,null,null,null,null,null,null,null,null,10,10,10,10,null,null,null,8,8,null,null,null,null,null,null,null,null,1,1,1,null,null,1,1,null,null,null,null,null,null,10,10,null,10,null,10,null,null,10,null,null,null],"name":"lib/groupher_server/cms/delegates/article_operation.ex","source":"defmodule GroupherServer.CMS.Delegate.ArticleCommunity do\n @moduledoc \"\"\"\n set / unset operations for Article-like resource\n \"\"\"\n import GroupherServer.CMS.Helper.MatcherOld\n import Ecto.Query, warn: false\n import Helper.ErrorCode\n\n alias Helper.ORM\n alias GroupherServer.Accounts.User\n alias GroupherServer.CMS.{Community, Tag}\n alias GroupherServer.Repo\n\n @doc \"\"\"\n pin / unpin, markDelete / untrash articles\n \"\"\"\n def set_flag(queryable, id, %{pin: _} = attrs, %User{} = _user) do\n queryable |> ORM.find_update(id, attrs)\n end\n\n def set_flag(queryable, id, %{markDelete: _} = attrs, %User{} = _user) do\n queryable |> ORM.find_update(id, attrs)\n end\n\n def mirror_article(%Community{id: community_id}, thread, content_id) when valid_thread(thread) do\n with {:ok, action} <- match_action(thread, :community),\n {:ok, content} <- ORM.find(action.target, content_id, preload: :communities),\n {:ok, community} <- ORM.find(action.reactor, community_id) do\n content\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.put_assoc(:communities, content.communities ++ [community])\n |> Repo.update()\n end\n end\n\n def unmirror_article(%Community{id: community_id}, thread, content_id)\n when valid_thread(thread) do\n with {:ok, action} <- match_action(thread, :community),\n {:ok, content} <- ORM.find(action.target, content_id, preload: :communities),\n {:ok, community} <- ORM.find(action.reactor, community_id) do\n content\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.put_assoc(:communities, content.communities -- [community])\n |> Repo.update()\n end\n end\n\n @doc \"\"\"\n set tag for post / tuts / videos ...\n \"\"\"\n # check community first\n def set_tag(%Community{id: communitId}, thread, %Tag{id: tag_id}, content_id) do\n with {:ok, action} <- match_action(thread, :article_tag),\n {:ok, content} <- ORM.find(action.target, content_id, preload: :tags),\n {:ok, tag} <- ORM.find(action.reactor, tag_id) do\n case tag_in_community_thread?(%Community{id: communitId}, thread, tag) do\n true ->\n content\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.put_assoc(:tags, content.tags ++ [tag])\n |> Repo.update()\n\n _ ->\n {:error, message: \"Tag,Community,Thread not match\", code: ecode(:custom)}\n end\n end\n end\n\n def unset_tag(thread, %Tag{id: tag_id}, content_id) when valid_thread(thread) do\n with {:ok, action} <- match_action(thread, :article_tag),\n {:ok, content} <- ORM.find(action.target, content_id, preload: :tags),\n {:ok, tag} <- ORM.find(action.reactor, tag_id) do\n content\n |> Ecto.Changeset.change()\n |> Ecto.Changeset.put_assoc(:tags, content.tags -- [tag])\n |> Repo.update()\n end\n end\n\n # make sure the reuest tag is in the current community thread\n # example: you can't set a other thread tag to this thread's article\n defp tag_in_community_thread?(%Community{id: communityId}, thread, tag) do\n with {:ok, community} <- ORM.find(Community, communityId) do\n matched_tags =\n Tag\n |> where([t], t.community_id == ^community.id)\n # |> where([t], t.thread == ^(to_string(thread) |> String.upcase()))\n |> where([t], t.thread == ^to_string(thread))\n |> Repo.all()\n\n tag in matched_tags\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1,0,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/endpoint.ex","source":"defmodule GroupherServerWeb.Endpoint do\n use Phoenix.Endpoint, otp_app: :groupher_server\n\n socket(\"/socket\", GroupherServerWeb.UserSocket)\n\n plug(Plug.RequestId)\n plug(Plug.Logger)\n\n plug(\n Plug.Parsers,\n parsers: [:urlencoded, :multipart, :json],\n pass: [\"*/*\"],\n json_decoder: Jason\n )\n\n plug(Plug.MethodOverride)\n plug(Plug.Head)\n\n # plug(:inspect_conn)\n\n plug(\n Corsica,\n # log: [rejected: :error],\n log: [rejected: :debug],\n origins: \"*\",\n allow_headers: [\n \"authorization\",\n \"content-type\",\n \"special\",\n \"accept\",\n \"origin\",\n \"x-requested-with\"\n ],\n allow_credentials: true\n )\n\n plug(GroupherServerWeb.Router)\n\n @doc \"\"\"\n Callback invoked for dynamically configuring the endpoint.\n\n It receives the endpoint configuration and checks if\n configuration should be loaded from the system environment.\n \"\"\"\n def init(_key, config) do\n if config[:load_from_system_env] do\n port = System.get_env(\"PORT\") || raise \"expected the PORT environment variable to be set\"\n {:ok, Keyword.put(config, :http, [:inet6, port: port])}\n else\n {:ok, config}\n end\n end\n\n # defp inspect_conn(conn, _), do: IO.inspect(conn)\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/cms/repo_builder.ex","source":"defmodule GroupherServer.CMS.RepoBuilder do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n @required_fields ~w(nickname avatar link)a\n @optional_fields ~w(bio)\n\n @type t :: %RepoBuilder{}\n schema \"cms_repo_users\" do\n field(:nickname, :string)\n field(:avatar, :string)\n field(:link, :string)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%RepoBuilder{} = repo_builder, attrs) do\n repo_builder\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/account/account_types.ex","source":"defmodule GroupherServerWeb.Schema.Account.Types do\n use Helper.GqlSchemaSuite\n\n import GroupherServerWeb.Schema.Helper.Fields\n import Absinthe.Resolution.Helpers\n\n alias GroupherServer.Accounts\n alias GroupherServerWeb.Schema\n\n import_types(Schema.Account.Metrics)\n\n object :user do\n field(:id, :id)\n field(:nickname, :string)\n field(:avatar, :string)\n field(:bio, :string)\n field(:sex, :string)\n field(:email, :string)\n field(:location, :string)\n field(:education, :string)\n field(:company, :string)\n field(:qq, :string)\n field(:weibo, :string)\n field(:weichat, :string)\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n field(:from_github, :boolean)\n field(:github_profile, :github_profile, resolve: dataloader(Accounts, :github_profile))\n field(:achievement, :achievement, resolve: dataloader(Accounts, :achievement))\n\n field(:cms_passport_string, :string) do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.get_passport_string/3)\n end\n\n field(:cms_passport, :json) do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.get_passport/3)\n end\n\n field :subscribed_communities, list_of(:community) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(Accounts, :subscribed_communities))\n end\n\n field :subscribed_communities_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :subscribed_communities))\n middleware(M.ConvertToInt)\n end\n\n field :followers_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :followers))\n middleware(M.ConvertToInt)\n end\n\n field :followings_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :followings))\n middleware(M.ConvertToInt)\n end\n\n field :viewer_has_followed, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n middleware(M.PutCurrentUser)\n resolve(dataloader(Accounts, :followers))\n middleware(M.ViewerDidConvert)\n end\n\n field :favorited_posts, :paged_posts do\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.favorited_posts/3)\n end\n\n field :favorited_jobs, :paged_jobs do\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.favorited_jobs/3)\n end\n\n field :favorited_posts_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :favorited_posts))\n middleware(M.ConvertToInt)\n end\n\n field :favorited_jobs_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(Accounts, :favorited_jobs))\n middleware(M.ConvertToInt)\n end\n\n field :contributes, :contribute_map do\n resolve(&R.Statistics.list_contributes/3)\n end\n\n # TODO, for msg-bell UI\n # field :has_messges,\n # 1. has_mentions ?\n # 2. has_system_messages ?\n # 3. has_notifications ?\n # 4. has_watches ?\n\n field :mail_box, :mail_box_status do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.get_mail_box_status/3)\n end\n\n field :mentions, :paged_mentions do\n arg(:filter, :messages_filter)\n\n middleware(M.Authorize, :login)\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.fetch_mentions/3)\n end\n\n field :notifications, :paged_notifications do\n arg(:filter, :messages_filter)\n\n middleware(M.Authorize, :login)\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.fetch_notifications/3)\n end\n\n field :sys_notifications, :paged_sys_notifications do\n arg(:filter, :messages_filter)\n\n middleware(M.Authorize, :login)\n middleware(M.PageSizeProof)\n resolve(&R.Accounts.fetch_sys_notifications/3)\n end\n end\n\n object :github_profile do\n field(:id, :id)\n field(:github_id, :string)\n # field(:user, :user, resolve: dataloader(Accounts, :user))\n field(:login, :string)\n field(:avatar_url, :string)\n field(:url, :string)\n field(:html_url, :string)\n field(:name, :string)\n field(:company, :string)\n field(:blog, :string)\n field(:location, :string)\n field(:email, :string)\n field(:bio, :string)\n field(:public_repos, :integer)\n field(:public_gists, :integer)\n end\n\n object :achievement do\n field(:reputation, :integer)\n field(:followers_count, :integer)\n field(:contents_stared_count, :integer)\n field(:contents_favorited_count, :integer)\n field(:contents_watched_count, :integer)\n end\n\n object :token_info do\n field(:token, :string)\n field(:user, :user)\n end\n\n object :rules do\n field(:cms, :json)\n end\n\n object :paged_users do\n field(:entries, list_of(:user))\n pagination_fields()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,39,null,39,39,null,null,null,null,null,152,null,152,152,null,null,null,null,null,10,null,10,10,null,null,null,null,null,26,26,26,null,null,null,12,12,12,null,null,null,2,null,null,null,null,null,null,null,null,null,null,null,98,null,null,null,null,null,null,null,null,null,null,9,null,null,null,null,null,null,87,null,null,null,null,null,null,87,null,87,null,null,null,null,null,null,null,null,null,null,null,null,151,null,null,null,null,null,null,null,0,null,null,null,null,null,null,3,null,null,null,null,null,null,13,null,null,null,null,13,null,13,null,null,null,null,null,null,null,6,6,null,null,null,null],"name":"test/support/assert_helper.ex","source":"defmodule GroupherServer.Test.AssertHelper do\n @moduledoc \"\"\"\n This module defines some helper function used by\n tests that require check from graphql response\n \"\"\"\n\n import Phoenix.ConnTest\n import Helper.Utils, only: [map_key_stringify: 1, get_config: 2]\n\n @endpoint GroupherServerWeb.Endpoint\n\n @page_size get_config(:general, :page_size)\n @inner_page_size get_config(:general, :inner_page_size)\n\n @doc \"\"\"\n used for non exsit id\n \"\"\"\n def non_exsit_id, do: 15_982_398_614\n def inner_page_size, do: @inner_page_size\n def page_size, do: @page_size\n\n def is_valid_kv?(obj, key, :list) when is_map(obj) do\n obj = map_key_stringify(obj)\n\n case Map.has_key?(obj, key) do\n true -> obj |> Map.get(key) |> is_list\n _ -> false\n end\n end\n\n def is_valid_kv?(obj, key, :int) when is_map(obj) do\n obj = map_key_stringify(obj)\n\n case Map.has_key?(obj, key) do\n true -> obj |> Map.get(key) |> is_integer\n _ -> false\n end\n end\n\n def is_valid_kv?(obj, key, :string) when is_map(obj) and is_binary(key) do\n obj = map_key_stringify(obj)\n\n case Map.has_key?(obj, key) do\n true -> String.length(Map.get(obj, key)) != 0\n _ -> false\n end\n end\n\n def is_valid_pagination?(obj) when is_map(obj) do\n is_valid_kv?(obj, \"entries\", :list) and is_valid_kv?(obj, \"totalPages\", :int) and\n is_valid_kv?(obj, \"totalCount\", :int) and is_valid_kv?(obj, \"pageSize\", :int) and\n is_valid_kv?(obj, \"pageNumber\", :int)\n end\n\n def is_valid_pagination?(obj, :raw) when is_map(obj) do\n is_valid_kv?(obj, \"entries\", :list) and is_valid_kv?(obj, \"total_pages\", :int) and\n is_valid_kv?(obj, \"total_count\", :int) and is_valid_kv?(obj, \"page_size\", :int) and\n is_valid_kv?(obj, \"page_number\", :int)\n end\n\n def has_boolen_value?(obj, key) do\n obj |> Map.get(key) |> is_boolean\n end\n\n @doc \"\"\"\n simulate the Graphiql murate operation\n \"\"\"\n def mutation_result(conn, query, variables, key) do\n conn\n |> post(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n # |> IO.inspect(label: \"debug\")\n |> Map.get(\"data\")\n |> Map.get(key)\n end\n\n @doc \"\"\"\n check if Graphiql murate get error\n \"\"\"\n def mutation_get_error?(conn, query, variables) do\n conn\n |> post(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n # |> IO.inspect(label: \"debug\")\n |> Map.has_key?(\"errors\")\n end\n\n @doc \"\"\"\n Graphiql murate error with code equal check\n \"\"\"\n def mutation_get_error?(conn, query, variables, code) when is_integer(code) do\n resp =\n conn\n |> post(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n\n # |> IO.inspect(label: \"debug\")\n\n case resp |> Map.has_key?(\"errors\") do\n true ->\n code == resp[\"errors\"] |> List.first() |> Map.get(\"code\")\n\n false ->\n false\n end\n end\n\n def query_result(conn, query, variables, key) do\n conn\n |> get(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n # |> IO.inspect(label: \"debug\")\n |> Map.get(\"data\")\n |> Map.get(key)\n end\n\n def query_result(conn, query, key) do\n conn\n |> get(\"/graphiql\", query: query, variables: %{})\n |> json_response(200)\n |> Map.get(\"data\")\n |> Map.get(key)\n end\n\n def query_get_error?(conn, query, variables) do\n conn\n |> get(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n |> Map.has_key?(\"errors\")\n end\n\n @doc \"\"\"\n check if Graphiql murate get error\n \"\"\"\n def query_get_error?(conn, query, variables, code) when is_integer(code) do\n resp =\n conn\n |> get(\"/graphiql\", query: query, variables: variables)\n |> json_response(200)\n\n case resp |> Map.has_key?(\"errors\") do\n true ->\n code == resp[\"errors\"] |> List.first() |> Map.get(\"code\")\n\n false ->\n false\n end\n end\n\n def firstn_and_last(values, 3) do\n [value_1 | [value_2 | [value_3 | _]]] = values\n value_x = values |> List.last()\n\n [value_1, value_2, value_3, value_x]\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/utils/helper.ex","source":"defmodule GroupherServerWeb.Schema.Helper.Fields do\n import Helper.Utils, only: [get_config: 2]\n @page_size get_config(:general, :page_size)\n # @default_inner_page_size 5\n\n # see: https://github.com/absinthe-graphql/absinthe/issues/363\n defmacro pagination_args() do\n quote do\n field(:page, :integer, default_value: 1)\n field(:size, :integer, default_value: unquote(@page_size))\n end\n end\n\n defmacro pagination_fields() do\n quote do\n field(:total_count, :integer)\n field(:page_size, :integer)\n field(:total_pages, :integer)\n field(:page_number, :integer)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,14,1,null,null,24,null,null,null,1,null,null,null,0,null,null,null,5,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,2,null,null,null,2,null,null,null,2,null,null,null,2,null,null,null,null,2,null,null,null,null,20,null,null,null,1,null,null,null,1,null,null,null,null,20,null,null,null,20,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,0,null,null,null,null,1,null,null,null,null,0,null,null,null,1,null,null,null,2,null,null,null,2,null,null,null,null,null,null,null,null,null,1,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/resolvers/accounts_resolver.ex","source":"defmodule GroupherServerWeb.Resolvers.Accounts do\n @moduledoc \"\"\"\n accounts resolvers\n \"\"\"\n import ShortMaps\n\n alias Helper.{Certification, ORM}\n alias GroupherServer.{Accounts, CMS}\n\n alias Accounts.{MentionMail, NotificationMail, SysNotificationMail, User}\n\n def user(_root, %{id: id}, _info), do: User |> ORM.find(id)\n def users(_root, ~m(filter)a, _info), do: User |> ORM.find_all(filter)\n\n def account(_root, _args, %{context: %{cur_user: cur_user}}) do\n User |> ORM.find(cur_user.id)\n end\n\n def update_profile(_root, %{profile: profile}, %{context: %{cur_user: cur_user}}) do\n Accounts.update_profile(%User{id: cur_user.id}, profile)\n end\n\n def github_signin(_root, %{github_user: github_user}, _info) do\n Accounts.github_signin(github_user)\n end\n\n def follow(_root, ~m(user_id)a, %{context: %{cur_user: cur_user}}) do\n Accounts.follow(cur_user, %User{id: user_id})\n end\n\n def undo_follow(_root, ~m(user_id)a, %{context: %{cur_user: cur_user}}) do\n Accounts.undo_follow(cur_user, %User{id: user_id})\n end\n\n def paged_followers(_root, ~m(user_id filter)a, _info) do\n Accounts.fetch_followers(%User{id: user_id}, filter)\n end\n\n def paged_followers(_root, ~m(filter)a, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_followers(cur_user, filter)\n end\n\n def paged_followings(_root, ~m(user_id filter)a, _info) do\n Accounts.fetch_followings(%User{id: user_id}, filter)\n end\n\n def paged_followings(_root, ~m(filter)a, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_followings(cur_user, filter)\n end\n\n # for check other users query\n def favorited_posts(_root, ~m(user_id filter)a, _info) do\n Accounts.reacted_contents(:post, :favorite, filter, %User{id: user_id})\n end\n\n def favorited_posts(_root, ~m(filter)a, %{context: %{cur_user: cur_user}}) do\n Accounts.reacted_contents(:post, :favorite, filter, cur_user)\n end\n\n def favorited_jobs(_root, ~m(user_id filter)a, _info) do\n Accounts.reacted_contents(:job, :favorite, filter, %User{id: user_id})\n end\n\n def favorited_jobs(_root, ~m(filter)a, %{context: %{cur_user: cur_user}}) do\n Accounts.reacted_contents(:job, :favorite, filter, cur_user)\n end\n\n # TODO: refactor\n def get_mail_box_status(_root, _args, %{context: %{cur_user: cur_user}}) do\n Accounts.mailbox_status(cur_user)\n end\n\n # mentions\n def fetch_mentions(_root, %{filter: filter}, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_mentions(cur_user, filter)\n end\n\n def mark_mention_read(_root, %{id: id}, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read(%MentionMail{id: id}, cur_user)\n end\n\n def mark_mention_read_all(_root, _args, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read_all(cur_user, :mention)\n end\n\n # notification\n def fetch_notifications(_root, %{filter: filter}, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_notifications(cur_user, filter)\n end\n\n def fetch_sys_notifications(_root, %{filter: filter}, %{context: %{cur_user: cur_user}}) do\n Accounts.fetch_sys_notifications(cur_user, filter)\n end\n\n def mark_notification_read(_root, %{id: id}, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read(%NotificationMail{id: id}, cur_user)\n end\n\n def mark_notification_read_all(_root, _args, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read_all(cur_user, :notification)\n end\n\n def mark_sys_notification_read(_root, %{id: id}, %{context: %{cur_user: cur_user}}) do\n Accounts.mark_mail_read(%SysNotificationMail{id: id}, cur_user)\n end\n\n # for user self's\n def subscribed_communities(_root, %{filter: filter}, %{cur_user: cur_user}) do\n Accounts.subscribed_communities(%User{id: cur_user.id}, filter)\n end\n\n #\n def subscribed_communities(_root, %{user_id: \"\", filter: filter}, _info) do\n Accounts.default_subscribed_communities(filter)\n end\n\n # for check other users subscribed_communities\n def subscribed_communities(_root, %{user_id: user_id, filter: filter}, _info) do\n Accounts.subscribed_communities(%User{id: user_id}, filter)\n end\n\n def subscribed_communities(_root, %{filter: filter}, _info) do\n Accounts.default_subscribed_communities(filter)\n end\n\n def get_passport(root, _args, %{context: %{cur_user: _}}) do\n CMS.get_passport(%User{id: root.id})\n end\n\n def get_passport_string(root, _args, %{context: %{cur_user: _}}) do\n case CMS.get_passport(%User{id: root.id}) do\n {:ok, passport} ->\n {:ok, Jason.encode!(passport)}\n\n {:error, _} ->\n {:ok, nil}\n end\n end\n\n def get_all_rules(_root, _args, %{context: %{cur_user: _}}) do\n cms_rules = Certification.all_rules(:cms, :stringify)\n\n {:ok,\n %{\n cms: cms_rules\n }}\n end\n\n # def create_user(_root, args, %{context: %{cur_user: %{root: true}}}) do\n # Accounts.create_user2(args)\n # end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/logs/logs.ex","source":"defmodule GroupherServer.Logs do\n @moduledoc \"\"\"\n The Logs context.\n \"\"\"\n\n # import Ecto.Query, warn: false\n # alias GroupherServer.Repo\n\n # alias GroupherServer.Logs.UserActivity\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/statistics/statistics_types.ex","source":"defmodule GroupherServerWeb.Schema.Statistics.Types do\n use Absinthe.Schema.Notation\n use Absinthe.Ecto, repo: GroupherServer.Repo\n\n # import Absinthe.Resolution.Helpers\n\n # alias GroupherServer.Accounts\n\n object :user_contribute do\n field(:count, :integer)\n field(:date, :date)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,5,5,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,null,1,null,1,null,null,null,2,null,null,null,null,null,null,null,2,2,null,null,null,null,null,null,null,0,null,0,null,null,0,null,null,null,null,null,2,null,null,2,null,null,2,null,null,2,null,null,null,null,null,null,null,null,3,null,null,null,null,null,2,null,null,null,null,null,null,null,null,null,2,null,null,null,null,2,null,2,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/delegates/profile.ex","source":"defmodule GroupherServer.Accounts.Delegate.Profile do\n @moduledoc \"\"\"\n accounts profile\n \"\"\"\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1, get_config: 2]\n import ShortMaps\n\n alias Helper.{Guardian, ORM, QueryBuilder}\n alias GroupherServer.Accounts.{GithubUser, User}\n alias GroupherServer.{CMS, Repo}\n\n alias Ecto.Multi\n\n @default_subscribed_communities get_config(:general, :default_subscribed_communities)\n\n def update_profile(%User{id: id}, attrs \\\\ %{}) do\n with {:ok, user} <- ORM.find(User, id) do\n case user.id === id do\n true -> user |> ORM.update(attrs)\n false -> {:error, \"Error: not qualified\"}\n end\n end\n end\n\n @doc \"\"\"\n github_signin steps:\n ------------------\n step 0: get access_token is enough, even profile is not need?\n step 1: check is access_token valid or not, think use a Middleware\n step 2.1: if access_token's github_id exsit, then login\n step 2.2: if access_token's github_id not exsit, then signup\n step 3: return groupher token\n \"\"\"\n def github_signin(github_user) do\n case ORM.find_by(GithubUser, github_id: to_string(github_user[\"id\"])) do\n {:ok, g_user} ->\n {:ok, user} = ORM.find(User, g_user.user_id)\n # IO.inspect label: \"send back from db\"\n token_info(user)\n\n {:error, _} ->\n # IO.inspect label: \"register then send\"\n register_github_user(github_user)\n end\n end\n\n @doc \"\"\"\n get default subscribed communities for unlogin user\n \"\"\"\n def default_subscribed_communities(%{page: _, size: _} = filter) do\n filter = Map.merge(filter, %{size: @default_subscribed_communities})\n CMS.Community |> ORM.find_all(filter)\n end\n\n @doc \"\"\"\n get users subscribed communities\n \"\"\"\n def subscribed_communities(%User{id: id}, %{page: page, size: size} = filter) do\n CMS.CommunitySubscriber\n |> where([c], c.user_id == ^id)\n |> join(:inner, [c], cc in assoc(c, :community))\n |> select([c, cc], cc)\n |> QueryBuilder.filter_pack(filter)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n defp register_github_user(github_profile) do\n Multi.new()\n |> Multi.run(:create_user, fn _ ->\n create_user(github_profile, :github)\n end)\n |> Multi.run(:create_profile, fn %{create_user: user} ->\n create_profile(user, github_profile, :github)\n end)\n |> Repo.transaction()\n |> register_github_result()\n end\n\n defp register_github_result({:ok, %{create_user: user}}), do: token_info(user)\n\n defp register_github_result({:error, :create_user, _result, _steps}),\n do: {:error, \"Accounts create_user internal error\"}\n\n defp register_github_result({:error, :create_profile, _result, _steps}),\n do: {:error, \"Accounts create_profile internal error\"}\n\n defp token_info(%User{} = user) do\n with {:ok, token, _info} <- Guardian.jwt_encode(user) do\n {:ok, %{token: token, user: user}}\n end\n end\n\n defp create_user(user, :github) do\n user = %User{\n nickname: user[\"login\"],\n avatar: user[\"avatar_url\"],\n bio: user[\"bio\"],\n location: user[\"location\"],\n email: user[\"email\"],\n company: user[\"company\"],\n from_github: true\n }\n\n Repo.insert(user)\n end\n\n defp create_profile(user, github_profile, :github) do\n # attrs = github_user |> Map.merge(%{github_id: github_user.id, user_id: 1}) |> Map.delete(:id)\n attrs =\n github_profile\n |> Map.merge(%{\"github_id\" => to_string(github_profile[\"id\"]), \"user_id\" => user.id})\n # |> Map.merge(%{\"github_id\" => github_profile[\"id\"], \"user_id\" => user.id})\n |> Map.delete(\"id\")\n\n %GithubUser{}\n |> GithubUser.changeset(attrs)\n |> Repo.insert()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,null,null,190,null,null,null,null,3,3,null,3,null,0,0,0,null,null,null,3,3,3,null,null,null,null,null,null,3,null,0,null,null,null,3,null,0,null,null,null,null,null,null,3,null,null,null,null,null],"name":"lib/groupher_server_web/middleware/changeset_errors.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.ChangesetErrors do\n @behaviour Absinthe.Middleware\n import Helper.Utils, only: [handle_absinthe_error: 3]\n import Helper.ErrorCode\n\n alias GroupherServerWeb.Gettext, as: Translator\n\n def call(%{errors: [%Ecto.Changeset{} = changeset]} = resolution, _) do\n # IO.inspect changeset, label: \"Changeset error\"\n # IO.inspect transform_errors(changeset), label: \"transform_errors\"\n resolution\n |> handle_absinthe_error(transform_errors(changeset), ecode(:changeset))\n end\n\n def call(resolution, _), do: resolution\n\n defp transform_errors(changeset) do\n changeset\n |> Ecto.Changeset.traverse_errors(&format_error/1)\n |> Enum.map(fn {key, err_msg_list} ->\n err_msg = err_msg_list |> List.first()\n\n cond do\n Map.has_key?(err_msg, :count) ->\n %{\n key: Translator |> Gettext.dgettext(\"fields\", \"#{key}\"),\n message: Translator |> Gettext.dgettext(\"errors\", err_msg.raw, count: err_msg.count)\n }\n\n true ->\n %{\n key: Translator |> Gettext.dgettext(\"fields\", \"#{key}\"),\n message: Translator |> Gettext.dgettext(\"errors\", err_msg.msg)\n }\n end\n end)\n end\n\n defp format_error({msg, opts}) do\n err_string =\n Enum.reduce(opts, msg, fn {key, value}, acc ->\n String.replace(acc, \"%{#{key}}\", to_string(value))\n end)\n\n # TODO handle: number type\n cond do\n String.contains?(msg, \"%{count}\") ->\n %{\n msg: err_string,\n count: Keyword.get(opts, :count),\n raw: msg\n }\n\n true ->\n %{\n msg: err_string\n }\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/delivery/delivery_types.ex","source":"defmodule GroupherServerWeb.Schema.Delivery.Types do\n use Absinthe.Schema.Notation\n use Absinthe.Ecto, repo: GroupherServer.Repo\n\n import GroupherServerWeb.Schema.Helper.Fields\n import Helper.Utils, only: [get_config: 2]\n\n @page_size get_config(:general, :page_size)\n\n object :mail_box_status do\n field(:has_mail, :boolean)\n field(:total_count, :integer)\n field(:mention_count, :integer)\n field(:notification_count, :integer)\n end\n\n object :mention do\n field(:id, :id)\n field(:from_user_id, :id)\n field(:to_user_id, :id)\n\n field(:source_title, :string)\n field(:read, :boolean)\n end\n\n object :sys_notification do\n field(:id, :id)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_preview, :string)\n field(:source_type, :string)\n\n field(:read, :boolean)\n end\n\n object :notification do\n field(:id, :id)\n field(:from_user_id, :id)\n field(:to_user_id, :id)\n field(:action, :string)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_preview, :string)\n field(:source_type, :string)\n field(:read, :boolean)\n end\n\n object :sys_notification do\n field(:id, :id)\n field(:user_id, :id)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_preview, :string)\n field(:source_type, :string)\n field(:read, :boolean)\n end\n\n object :paged_mentions do\n field(:entries, list_of(:mention))\n pagination_fields()\n end\n\n object :paged_notifications do\n field(:entries, list_of(:notification))\n pagination_fields()\n end\n\n object :paged_sys_notifications do\n field(:entries, list_of(:sys_notification))\n pagination_fields()\n end\n\n input_object :messages_filter do\n field(:read, :boolean, default_value: false)\n\n field(:page, :integer, default_value: 1)\n field(:size, :integer, default_value: @page_size)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,26,null,3,null,null,4,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,7,null,null,null,null,null,null,null,null,null,20,null,null,null,null,null,null,null,null,null,8,null,null,null,null,null,null,37,null,null,null,null,21,null,null,null,null,72,null,18,null,54,7,null,47,10,null,37,37,null,null,null,0,null,null,null,null,37,37,null,37,null,24,null,null,null,13,null,null,null,null,7,7,null,7,null,null,null,null,7,null,6,null,null,null,1,null,null,null,null,18,18,null,18,null,18,null,null,null,null,null,18,null,16,null,null,null,2,null,null,null,null,10,10,null,10,null,null,15,15,null,null,null,10,null,7,null,null,null,3,null,null,null],"name":"lib/groupher_server_web/middleware/passport.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\n# RBAC vs CBAC\n# https://stackoverflow.com/questions/22814023/role-based-access-control-rbac-vs-claims-based-access-control-cbac-in-asp-n\n\n# 本中间件会隐式的加载 community 的 rules 信息,并应用该 rules 信息\ndefmodule GroupherServerWeb.Middleware.Passport do\n @moduledoc \"\"\"\n c? -> community / communities\n t? -> thread, could be post / job / tut / video ...\n \"\"\"\n @behaviour Absinthe.Middleware\n\n import Helper.Utils\n import Helper.ErrorCode\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(%{arguments: %{passport_is_owner: true}} = resolution, claim: \"owner\"), do: resolution\n\n def call(%{arguments: %{passport_is_owner: true}} = resolution, claim: \"owner;\" <> _rest),\n do: resolution\n\n def call(\n %{\n context: %{cur_user: %{cur_passport: _}},\n arguments: %{community: _, thread: _}\n } = resolution,\n claim: \"cms->c?->t?.\" <> _rest = claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(\n %{\n context: %{cur_user: %{cur_passport: _}},\n arguments: %{thread: _}\n } = resolution,\n claim: \"cms->t?.\" <> _rest = claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(\n %{\n context: %{cur_user: %{cur_passport: _}},\n arguments: %{passport_communities: _}\n } = resolution,\n claim: \"cms->c?->\" <> _rest = claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(\n %{\n context: %{cur_user: %{cur_passport: _}},\n arguments: %{passport_communities: _}\n } = resolution,\n claim: \"owner;\" <> claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(\n %{context: %{cur_user: %{cur_passport: _}}} = resolution,\n claim: \"cms->\" <> _rest = claim\n ) do\n resolution |> check_passport_stamp(claim)\n end\n\n def call(resolution, _) do\n resolution\n |> handle_absinthe_error(\"PassportError: your passport not qualified.\", ecode(:passport))\n end\n\n defp check_passport_stamp(resolution, claim) do\n # TODO: refactor\n cond do\n claim |> String.starts_with?(\"cms->c?->t?.\") ->\n resolution |> cp_check(claim)\n\n claim |> String.starts_with?(\"cms->t?.\") ->\n resolution |> p_check(claim)\n\n claim |> String.starts_with?(\"cms->c?->\") ->\n resolution |> c_check(claim)\n\n claim |> String.starts_with?(\"cms->\") ->\n resolution |> do_check(claim)\n\n true ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\n\n defp do_check(resolution, claim) do\n cur_passport = resolution.context.cur_user.cur_passport\n path = claim |> String.split(\"->\")\n\n case get_in(cur_passport, path) do\n true ->\n resolution\n\n nil ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\n\n defp p_check(resolution, claim) do\n cur_passport = resolution.context.cur_user.cur_passport\n thread = resolution.arguments.thread |> to_string\n\n path =\n claim\n |> String.replace(\"t?\", thread)\n |> String.split(\"->\")\n\n case get_in(cur_passport, path) do\n true ->\n resolution\n\n nil ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\n\n defp cp_check(resolution, claim) do\n cur_passport = resolution.context.cur_user.cur_passport\n community_title = resolution.arguments.passport_communities |> List.first() |> Map.get(:title)\n\n thread = resolution.arguments.thread |> to_string\n\n path =\n claim\n |> String.replace(\"c?\", community_title)\n |> String.replace(\"t?\", thread)\n |> String.split(\"->\")\n\n case get_in(cur_passport, path) do\n true ->\n resolution\n\n nil ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\n\n defp c_check(resolution, claim) do\n cur_passport = resolution.context.cur_user.cur_passport\n communities = resolution.arguments.passport_communities\n\n result =\n communities\n |> Enum.filter(fn community ->\n path = claim |> String.replace(\"c?\", community.title) |> String.split(\"->\")\n get_in(cur_passport, path) == true\n end)\n |> length\n\n case result > 0 do\n true ->\n resolution\n\n false ->\n resolution\n |> handle_absinthe_error(\"PassportError: Passport not qualified.\", ecode(:passport))\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,148,null,null,null,null,null,null,null,null,null,null,null,73,null,null],"name":"lib/groupher_server/cms/post_favorite.ex","source":"defmodule GroupherServer.CMS.PostFavorite do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Post\n\n @required_fields ~w(user_id post_id)a\n\n @type t :: %PostFavorite{}\n schema \"posts_favorites\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:post, Post, foreign_key: :post_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostFavorite{} = post_favorite, attrs) do\n post_favorite\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:user_id, name: :posts_favorites_user_id_post_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,2806,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/cms/author.ex","source":"defmodule GroupherServer.CMS.Author do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Post\n\n @type t :: %Author{}\n\n schema \"cms_authors\" do\n field(:role, :string)\n # field(:user_id, :id)\n has_many(:posts, Post)\n # user_id filed in own-table\n belongs_to(:user, Accounts.User)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Author{} = author, attrs) do\n # |> foreign_key_constraint(:user_id)\n author\n |> cast(attrs, [:role])\n |> validate_required([:role])\n |> unique_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,104,null,null,null,null,null,null,null,null,null,null,null,null,null,97,null,null],"name":"lib/groupher_server/accounts/user_following.ex","source":"defmodule GroupherServer.Accounts.UserFollowing do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id following_id)a\n\n @type t :: %UserFollowing{}\n schema \"users_followings\" do\n belongs_to(:user, User, foreign_key: :user_id)\n belongs_to(:following, User, foreign_key: :following_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%UserFollowing{} = user_following, attrs) do\n user_following\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> foreign_key_constraint(:following_id)\n |> unique_constraint(:user_id, name: :users_followers_user_id_following_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,361,null,null,null,null,0,0,null,null,null,null,null,null,null,0,null,null,null,null,0,null,null,null,0,null,null,null,null,null,0,null,0,null,null,null,0,null,null,null,null,null,0,0,null,null,null,null,null,0,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,0,null,null,null,0,0,null,null,0,null,null,null,null,null,null,null,12,null,null,null,3,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,0,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,2,null,null,null,1,null,null,null,6,null,null,null,2,null,null,null,null,null,null,1,null,null,null,null,2,null,null,null,null,null,0,null,null,null,null,null,4,null,null,null,2,null,null,null,null,1,null,null,null,null,null,4,null,null,null,null,0,null,null,null,null,1,null,null,null,null,null,null,1,null,null,null,null,2,null,null,null,null,null,0,null,null,null,null,null,null,null,null,26,null,null],"name":"lib/groupher_server/cms/utils/loader.ex","source":"defmodule GroupherServer.CMS.Helper.Loader do\n @moduledoc \"\"\"\n dataloader for cms context\n \"\"\"\n import Ecto.Query, warn: false\n\n alias Helper.QueryBuilder\n alias GroupherServer.Repo\n # alias GroupherServer.Accounts\n alias GroupherServer.CMS.{\n Author,\n CommunityEditor,\n CommunitySubscriber,\n CommunityThread,\n JobCommentReply,\n Post,\n PostComment,\n PostCommentDislike,\n PostCommentLike,\n PostCommentReply,\n PostFavorite,\n PostStar\n # job comment\n # JobComment,\n }\n\n def data, do: Dataloader.Ecto.new(Repo, query: &query/2, run_batch: &run_batch/5)\n\n # Big thanks: https://elixirforum.com/t/grouping-error-in-absinthe-dadaloader/13671/2\n # see also: https://github.com/absinthe-graphql/dataloader/issues/25\n def run_batch(Post, post_query, :posts_count, community_ids, repo_opts) do\n query =\n from(\n p in post_query,\n join: c in assoc(p, :communities),\n where: c.id in ^community_ids,\n group_by: c.id,\n select: {c.id, [count(p.id)]}\n )\n\n results =\n query\n |> Repo.all(repo_opts)\n |> Map.new()\n\n for id <- community_ids, do: Map.get(results, id, [0])\n end\n\n def run_batch(PostComment, comment_query, :cp_count, post_ids, repo_opts) do\n results =\n comment_query\n |> join(:inner, [c], a in assoc(c, :author))\n # |> distinct([c, a], a.id)\n |> group_by([c, a], a.id)\n |> group_by([c, a], c.post_id)\n |> select([c, a], {c.post_id, count(a.id)})\n |> Repo.all(repo_opts)\n |> Enum.group_by(fn {x, _} -> x end)\n |> Enum.map(fn {x, y} -> {x, [length(y)]} end)\n |> Map.new()\n\n for id <- post_ids, do: Map.get(results, id, [0])\n end\n\n def run_batch(PostComment, comment_query, :cp_users, post_ids, repo_opts) do\n # IO.inspect(comment_query, label: \"# run_batch # comment_query\")\n\n sq =\n from(\n pc in comment_query,\n join: a in assoc(pc, :author),\n select: %{id: a.id, row_number: fragment(\"row_number() OVER (PARTITION BY author_id)\")}\n )\n\n query =\n from(\n pc in comment_query,\n join: s in subquery(sq),\n on: s.id == pc.author_id,\n where: s.row_number == 10,\n select: {pc.post_id, s.id}\n )\n\n # query = comment_query\n # |> join(:inner, [c], a in assoc(c, :author))\n # |> distinct([c, a], c.post_id)\n # |> join(:inner_lateral, [c, a], u in fragment(\"SELECT * FROM users AS us WHERE us.id = ? LIMIT 1\", a.id))\n # |> join(:inner_lateral, [c, a], u in fragment(\"SELECT * FROM users AS us WHERE us.id > ? LIMIT 1\", 100))\n # |> select([c, a, u], {c.post_id, u.id, u.nickname})\n\n results =\n query\n # |> IO.inspect(label: \"before\")\n |> Repo.all(repo_opts)\n # |> IO.inspect(label: \"geting fuck\")\n |> bat_man()\n\n # results =\n # comment_query\n # |> join(:inner, [c], a in assoc(c, :author))\n # |> group_by([c, a], a.id)\n # |> group_by([c, a], c.post_id)\n # |> select([c, a], {c.post_id, a})\n # ---------\n # |> join(:inner, [c], s in subquery(sq), on: s.id == c.post_id)\n # |> join(:inner, [c], a in subquery(isubquery), c.post_id == 106)\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT * FROM users AS u WHERE u.id = ? LIMIT 3\", c.post_id))\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT * FROM users WHERE users.id > ? LIMIT 3\", 100))\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT * FROM posts_comments JOIN users ON users.id = ? LIMIT 2\", c.author_id))\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT * FROM posts_comments AS pc WHERE pc.author_id = ? LIMIT 2\", 185))\n # |> join(:inner_lateral, [c], a in fragment(\"SELECT ROW_NUMBER() OVER (PARTITION BY ?) FROM posts_comments AS pc GROUP BY pc.post_id\", c.post_id))\n # |> distinct([c, a], c.post_id)\n # |> join(:inner_lateral, [c, a], x in fragment(\"SELECT * FROM posts_comments JOIN users ON users.id = posts_comments.author_id WHERE post_id = ? LIMIT 2\", c.post_id))\n # |> join(:inner_lateral, [c, a], x in fragment(\"SELECT * FROM posts_comments JOIN users ON users.id = posts_comments.author_id LIMIT 3\"))\n # |> select([c,a,x], {c.post_id, x.author_id})\n # |> select([c,a,x], {c.post_id, a.id})\n # |> where([c, a], a.row_number < 3)\n # |> join(:inner, [c], a in assoc(c, :author))\n # |> join(:inner, [c], a in subquery(isubquery))\n # |> group_by([c, a, x], x.author_id)\n # |> distinct([c, a], a.author_id)\n # |> select([c, a], {c.post_id, a.author_id})\n # |> select([c, a], {c.post_id, fragment(\"max(?) OVER (PARTITION BY ?)\", a.id, a.id)})\n # |> select([c, a], %{post_id: c.post_id, user: fragment(\"max(?) OVER (PARTITION BY ?)\", a.id, a.id)})\n # |> select([c, a], fragment(\"SELECT ROW_NUMBER() OVER (PARTITION BY ?) FROM cms_authors AS r , \", a.id))\n # |> join([c], c in subquery(sq), on: c.post_id == bq.id)\n # |> having([c, a], count(\"*\") < 10)\n # |> having([c, a], a.id < 180)\n # |> limit(3)\n # |> order_by([p, s], desc: fragment(\"count(?)\", s.id))\n # |> distinct([c, a], a.id)\n # |> Repo.all(repo_opts)\n # |> IO.inspect(label: \"get fuck\")\n # |> bat_man()\n\n for id <- post_ids, do: Map.get(results, id, [])\n end\n\n # TODO: use meta-programing to extract all query below\n # --------------------\n def bat_man(data) do\n # TODO refactor later\n data\n |> Enum.group_by(fn {x, _} -> x end)\n |> Enum.map(fn {x, y} ->\n {x,\n Enum.reduce(y, [], fn kv, acc ->\n {_, v} = kv\n acc ++ [v]\n end)}\n end)\n |> Map.new()\n end\n\n def query(Author, _args) do\n # you cannot use preload with select together\n # https://stackoverflow.com/questions/43010352/ecto-select-relations-from-preload\n # see also\n # https://github.com/elixir-ecto/ecto/issues/1145\n from(a in Author, join: u in assoc(a, :user), select: u)\n end\n\n def query({\"communities_threads\", CommunityThread}, _info) do\n from(\n ct in CommunityThread,\n join: t in assoc(ct, :thread),\n order_by: [asc: t.index],\n select: t\n )\n end\n\n @doc \"\"\"\n get unique participators join in comments\n \"\"\"\n def query({\"posts_comments\", PostComment}, %{filter: filter, unique: true}) do\n # def query({\"posts_comments\", PostComment}, %{unique: true}) do\n PostComment\n # |> QueryBuilder.members_pack(args)\n |> QueryBuilder.filter_pack(filter)\n |> join(:inner, [c], a in assoc(c, :author))\n |> distinct([c, a], a.id)\n |> select([c, a], a)\n end\n\n def query({\"posts_comments\", PostComment}, %{count: _, unique: true}) do\n # TODO: not very familar with SQL, but it has to be 2 group_by to work, check later\n # and the expect count should be the length of reault\n PostComment\n |> join(:inner, [c], a in assoc(c, :author))\n |> distinct([c, a], a.id)\n |> group_by([c, a], a.id)\n |> group_by([c, a], c.post_id)\n |> select([c, a], count(c.id))\n end\n\n def query({\"posts_comments\", PostComment}, %{count: _}) do\n PostComment\n |> group_by([c], c.post_id)\n |> select([c], count(c.id))\n end\n\n # def query({\"posts_comments\", PostComment}, %{filter: %{first: first}} = filter) do\n def query({\"posts_comments\", PostComment}, %{filter: filter}) do\n PostComment\n # |> limit(3)\n |> QueryBuilder.filter_pack(filter)\n end\n\n @doc \"\"\"\n handle query:\n 1. bacic filter of pagi,when,sort ...\n 2. count of the reactions\n 3. check is viewer reacted\n \"\"\"\n def query({\"posts_favorites\", PostFavorite}, args) do\n PostFavorite |> QueryBuilder.members_pack(args)\n end\n\n def query({\"posts_stars\", PostStar}, args) do\n PostStar |> QueryBuilder.members_pack(args)\n end\n\n def query({\"communities_subscribers\", CommunitySubscriber}, args) do\n CommunitySubscriber |> QueryBuilder.members_pack(args)\n end\n\n def query({\"communities_editors\", CommunityEditor}, args) do\n CommunityEditor |> QueryBuilder.members_pack(args)\n end\n\n # for comments replies, likes, repliesCount, likesCount...\n def query({\"posts_comments_replies\", PostCommentReply}, %{count: _}) do\n PostCommentReply\n |> group_by([c], c.post_comment_id)\n |> select([c], count(c.id))\n end\n\n def query({\"posts_comments_replies\", PostCommentReply}, %{filter: filter}) do\n PostCommentReply\n |> QueryBuilder.load_inner_replies(filter)\n end\n\n def query({\"posts_comments_replies\", PostCommentReply}, %{reply_to: _}) do\n PostCommentReply\n |> join(:inner, [c], r in assoc(c, :post_comment))\n |> select([c, r], r)\n end\n\n def query({\"posts_comments_likes\", PostCommentLike}, %{count: _}) do\n PostCommentLike\n |> group_by([f], f.post_comment_id)\n |> select([f], count(f.id))\n end\n\n def query({\"posts_comments_likes\", PostCommentLike}, %{viewer_did: _, cur_user: cur_user}) do\n PostCommentLike |> where([f], f.user_id == ^cur_user.id)\n end\n\n def query({\"posts_comments_likes\", PostCommentLike}, %{filter: _filter} = args) do\n PostCommentLike\n |> QueryBuilder.members_pack(args)\n end\n\n def query({\"posts_comments_dislikes\", PostCommentDislike}, %{count: _}) do\n PostCommentDislike\n |> group_by([f], f.post_comment_id)\n |> select([f], count(f.id))\n end\n\n # component dislikes\n def query({\"posts_comments_dislikes\", PostCommentDislike}, %{viewer_did: _, cur_user: cur_user}) do\n PostCommentDislike |> where([f], f.user_id == ^cur_user.id)\n end\n\n def query({\"posts_comments_dislikes\", PostCommentDislike}, %{filter: _filter} = args) do\n PostCommentDislike\n |> QueryBuilder.members_pack(args)\n end\n\n # ---- job comments ------\n def query({\"jobs_comments_replies\", JobCommentReply}, %{count: _}) do\n JobCommentReply\n |> group_by([c], c.job_comment_id)\n |> select([c], count(c.id))\n end\n\n def query({\"jobs_comments_replies\", JobCommentReply}, %{filter: filter}) do\n JobCommentReply\n |> QueryBuilder.load_inner_replies(filter)\n end\n\n def query({\"jobs_comments_replies\", JobCommentReply}, %{reply_to: _}) do\n JobCommentReply\n |> join(:inner, [c], r in assoc(c, :job_comment))\n |> select([c, r], r)\n end\n\n # ---- job ------\n\n # default loader\n def query(queryable, _args) do\n # IO.inspect(queryable, label: \"default loader\")\n # IO.inspect(args, label: \"default args\")\n queryable\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,153,null,null,null,null,null,null,null,null,null,null,null,null,null,76,null,null],"name":"lib/groupher_server/cms/community_subscriber.ex","source":"defmodule GroupherServer.CMS.CommunitySubscriber do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Community\n\n @required_fields ~w(user_id community_id)a\n\n @type t :: %CommunitySubscriber{}\n schema \"communities_subscribers\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:community, Community, foreign_key: :community_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%CommunitySubscriber{} = community_subscriber, attrs) do\n community_subscriber\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:community_id)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user_id, name: :communities_subscribers_user_id_community_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,20,null,null,null,2,null,null,null,14,null,null,null,2,null,null,null,null,34,34,null,34,null,1,null,null,33,null,33,null,null,null,null,null,4,4,4,4,null,null,null],"name":"lib/groupher_server/cms/delegates/comment_reaction.ex","source":"defmodule GroupherServer.CMS.Delegate.CommentReaction do\n import GroupherServer.CMS.Helper.MatcherOld\n\n alias GroupherServer.Accounts\n alias Helper.ORM\n\n def like_comment(thread, comment_id, %Accounts.User{id: user_id}) do\n feel_comment(thread, comment_id, user_id, :like)\n end\n\n def undo_like_comment(thread, comment_id, %Accounts.User{id: user_id}) do\n undo_feel_comment(thread, comment_id, user_id, :like)\n end\n\n def dislike_comment(thread, comment_id, %Accounts.User{id: user_id}) do\n feel_comment(thread, comment_id, user_id, :dislike)\n end\n\n def undo_dislike_comment(thread, comment_id, %Accounts.User{id: user_id}) do\n undo_feel_comment(thread, comment_id, user_id, :dislike)\n end\n\n defp feel_comment(thread, comment_id, user_id, feeling)\n when valid_feeling(feeling) do\n with {:ok, action} <- match_action(thread, feeling) do\n clause = %{post_comment_id: comment_id, user_id: user_id}\n\n case ORM.find_by(action.reactor, clause) do\n {:ok, _} ->\n {:error, \"user has #{to_string(feeling)}d this comment\"}\n\n {:error, _} ->\n action.reactor |> ORM.create(clause)\n\n ORM.find(action.target, comment_id)\n end\n end\n end\n\n defp undo_feel_comment(thread, comment_id, user_id, feeling) do\n with {:ok, action} <- match_action(thread, feeling) do\n clause = %{post_comment_id: comment_id, user_id: user_id}\n ORM.findby_delete!(action.reactor, clause)\n ORM.find(action.target, comment_id)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/helper/spec_type.ex","source":"defmodule Helper.Types do\n @moduledoc \"\"\"\n custom @types\n \"\"\"\n\n @typedoc \"\"\"\n Type GraphQL flavor the error format\n \"\"\"\n @type gq_error :: {:error, [message: String.t(), code: non_neg_integer()]}\n\n @typedoc \"\"\"\n general response conventions\n \"\"\"\n @type done :: {:ok, map} | {:error, map}\n\n @type id :: non_neg_integer() | String.t()\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/statistics/statistics_queries.ex","source":"defmodule GroupherServerWeb.Schema.Statistics.Queries do\n @moduledoc \"\"\"\n Statistics.Queries\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :statistics_queries do\n @desc \"list of user contribute in last 6 month\"\n field :user_contributes, list_of(:user_contribute) do\n arg(:id, non_null(:id))\n\n resolve(&R.Statistics.list_contributes/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,5273,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,null,null,null,null,null],"name":"lib/groupher_server/cms/community.ex","source":"defmodule GroupherServer.CMS.Community do\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.CMS.{\n Category,\n Post,\n Video,\n Repo,\n Job,\n CommunityThread,\n CommunitySubscriber,\n CommunityEditor\n }\n\n alias GroupherServer.Accounts\n\n @required_fields ~w(title desc user_id logo raw)a\n # @required_fields ~w(title desc user_id)a\n @optional_fields ~w(label)a\n\n schema \"communities\" do\n field(:title, :string)\n field(:desc, :string)\n field(:logo, :string)\n # field(:category, :string)\n field(:label, :string)\n field(:raw, :string)\n\n belongs_to(:author, Accounts.User, foreign_key: :user_id)\n\n has_many(:threads, {\"communities_threads\", CommunityThread})\n has_many(:subscribers, {\"communities_subscribers\", CommunitySubscriber})\n has_many(:editors, {\"communities_editors\", CommunityEditor})\n\n many_to_many(\n :categories,\n Category,\n join_through: \"communities_categories\",\n join_keys: [community_id: :id, category_id: :id],\n # :delete_all will only remove data from the join source\n on_delete: :delete_all\n # on_replace: :delete\n )\n\n many_to_many(\n :posts,\n Post,\n join_through: \"communities_posts\",\n join_keys: [community_id: :id, post_id: :id]\n )\n\n many_to_many(\n :videos,\n Video,\n join_through: \"communities_videos\",\n join_keys: [community_id: :id, video_id: :id]\n )\n\n many_to_many(\n :repos,\n Repo,\n join_through: \"communities_repos\",\n join_keys: [community_id: :id, repo_id: :id]\n )\n\n many_to_many(\n :jobs,\n Job,\n join_through: \"communities_jobs\",\n join_keys: [community_id: :id, job_id: :id]\n )\n\n # posts_managers\n # jobs_managers\n # tuts_managers\n # videos_managers\n #\n # posts_block_list ...\n # videos_block_list ...\n timestamps(type: :utc_datetime)\n end\n\n def changeset(%Community{} = community, attrs) do\n # |> cast_assoc(:author)\n # |> unique_constraint(:title, name: :communities_title_index)\n community\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> validate_length(:title, min: 3, max: 30)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:title, name: :communities_title_index)\n\n # |> foreign_key_constraint(:communities_author_fkey)\n # |> unique_constraint(:user_id, name: :posts_favorites_user_id_post_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,1,0,null,null,4,null,null,3,3,null,null,2,null,2,null,null,null,null,9,3,3,2,null,16,14,14,9,null,null,12,null,null,null,4,null,5,null,null,1,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,null,null,0,null,null,null,0,null,null,null,0,null,null,null,null,null,2,null,null,1,null,null,1,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,null,3,null,null,1,null,null,1,null,null,null,1,null,null,null,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,1,null,null,null,null,null,null,3,null,null,1,null,1,null,null,10,null,null,null,1,null,null,1,null,null,null,1,null,null,null,null,null,null,1,null,null,null,null,null,2,null,null,null,2,null,null,null,1,null,null,null,5,null,null,null,1,null,null,null,null,null,null,10,null,null,8,null,null,null,3,null,null,null,2,null,null,null,2,null,null,null,1,null,null,null,2,null,null,null,1,null,null,null,3,null,null],"name":"lib/groupher_server_web/resolvers/cms_resolver.ex","source":"defmodule GroupherServerWeb.Resolvers.CMS do\n @moduledoc false\n\n import ShortMaps\n import Ecto.Query, warn: false\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.CMS\n alias GroupherServer.CMS.{Post, Video, Repo, Job, Community, Category, Tag, Thread}\n alias Helper.ORM\n\n # #######################\n # community ..\n # #######################\n def community(_root, %{id: id}, _info), do: Community |> ORM.find(id)\n def community(_root, %{title: title}, _info), do: Community |> ORM.find_by(title: title)\n def community(_root, %{raw: raw}, _info), do: Community |> ORM.find_by(raw: raw)\n\n def community(_root, _args, _info), do: {:error, \"please provide community id or title or raw\"}\n def paged_communities(_root, ~m(filter)a, _info), do: Community |> ORM.find_all(filter)\n\n def create_community(_root, args, %{context: %{cur_user: user}}) do\n args = args |> Map.merge(%{user_id: user.id})\n Community |> ORM.create(args)\n end\n\n def update_community(_root, args, _info), do: Community |> ORM.find_update(args)\n\n def delete_community(_root, %{id: id}, _info), do: Community |> ORM.find_delete!(id)\n\n # #######################\n # community thread (post, job)\n # #######################\n def post(_root, %{id: id}, _info), do: Post |> ORM.read(id, inc: :views)\n def video(_root, %{id: id}, _info), do: Video |> ORM.read(id, inc: :views)\n def repo(_root, %{id: id}, _info), do: Repo |> ORM.read(id, inc: :views)\n def job(_root, %{id: id}, _info), do: Job |> ORM.read(id, inc: :views)\n\n def paged_posts(_root, ~m(filter)a, _info), do: Post |> CMS.paged_contents(filter)\n def paged_videos(_root, ~m(filter)a, _info), do: Video |> CMS.paged_contents(filter)\n def paged_repos(_root, ~m(filter)a, _info), do: Repo |> CMS.paged_contents(filter)\n def paged_jobs(_root, ~m(filter)a, _info), do: Job |> ORM.find_all(filter)\n\n def create_article(_root, ~m(community_id thread)a = args, %{context: %{cur_user: user}}) do\n CMS.create_article(%Community{id: community_id}, thread, args, user)\n end\n\n def update_article(_root, %{passport_source: content} = args, _info),\n do: ORM.update(content, args)\n\n def delete_content(_root, %{passport_source: content}, _info), do: ORM.delete(content)\n\n def pin_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{pin: true}, user)\n end\n\n def undo_pin_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{pin: false}, user)\n end\n\n def trash_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{markDelete: true}, user)\n end\n\n def undo_trash_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{markDelete: false}, user)\n end\n\n # #######################\n # thread reaction ..\n # #######################\n def reaction(_root, ~m(id thread action)a, %{context: %{cur_user: user}}) do\n CMS.reaction(thread, action, id, user)\n end\n\n def undo_reaction(_root, ~m(id thread action)a, %{context: %{cur_user: user}}) do\n CMS.undo_reaction(thread, action, id, user)\n end\n\n def reaction_users(_root, ~m(id action thread filter)a, _info) do\n CMS.reaction_users(thread, action, id, filter)\n end\n\n # #######################\n # category ..\n # #######################\n def paged_categories(_root, ~m(filter)a, _info), do: Category |> ORM.find_all(filter)\n\n def create_category(_root, ~m(title raw)a, %{context: %{cur_user: user}}) do\n CMS.create_category(%Category{title: title, raw: raw}, user)\n end\n\n def delete_category(_root, %{id: id}, _info), do: Category |> ORM.find_delete!(id)\n\n def update_category(_root, ~m(id title)a, %{context: %{cur_user: _}}) do\n CMS.update_category(~m(%Category id title)a)\n end\n\n def set_category(_root, ~m(community_id category_id)a, %{context: %{cur_user: _}}) do\n CMS.set_category(%Community{id: community_id}, %Category{id: category_id})\n end\n\n def unset_category(_root, ~m(community_id category_id)a, %{context: %{cur_user: _}}) do\n CMS.unset_category(%Community{id: community_id}, %Category{id: category_id})\n end\n\n # #######################\n # thread ..\n # #######################\n def paged_threads(_root, ~m(filter)a, _info), do: Thread |> ORM.find_all(filter)\n\n def create_thread(_root, ~m(title raw index)a, _info),\n do: CMS.create_thread(~m(title raw index)a)\n\n def set_thread(_root, ~m(community_id thread_id)a, _info) do\n CMS.set_thread(%Community{id: community_id}, %Thread{id: thread_id})\n end\n\n def unset_thread(_root, ~m(community_id thread_id)a, _info) do\n CMS.unset_thread(%Community{id: community_id}, %Thread{id: thread_id})\n end\n\n # #######################\n # editors ..\n # #######################\n def set_editor(_root, ~m(community_id user_id title)a, _) do\n CMS.set_editor(%Community{id: community_id}, title, %User{id: user_id})\n end\n\n def unset_editor(_root, ~m(community_id user_id)a, _) do\n CMS.unset_editor(%Community{id: community_id}, %User{id: user_id})\n end\n\n def update_editor(_root, ~m(community_id user_id title)a, _) do\n CMS.update_editor(%Community{id: community_id}, title, %User{id: user_id})\n end\n\n def community_editors(_root, ~m(id filter)a, _info) do\n CMS.community_members(:editors, %Community{id: id}, filter)\n end\n\n # #######################\n # tags ..\n # #######################\n def create_tag(_root, args, %{context: %{cur_user: user}}) do\n CMS.create_tag(args.thread, args, user)\n end\n\n def delete_tag(_root, %{id: id}, _info), do: Tag |> ORM.find_delete!(id)\n\n def update_tag(_root, args, _info), do: CMS.update_tag(args)\n\n def set_tag(_root, ~m(community_id thread id tag_id)a, _info) do\n CMS.set_tag(%Community{id: community_id}, thread, %Tag{id: tag_id}, id)\n end\n\n def unset_tag(_root, ~m(id thread tag_id)a, _info),\n do: CMS.unset_tag(thread, %Tag{id: tag_id}, id)\n\n def get_tags(_root, ~m(community_id thread)a, _info) do\n CMS.get_tags(%Community{id: community_id}, thread)\n end\n\n def get_tags(_root, ~m(community thread)a, _info) do\n CMS.get_tags(%Community{raw: community}, thread)\n end\n\n def get_tags(_root, %{thread: _thread}, _info) do\n {:error, \"community_id or community is needed\"}\n end\n\n def get_tags(_root, ~m(filter)a, _info), do: CMS.get_tags(filter)\n\n # #######################\n # community subscribe ..\n # #######################\n def subscribe_community(_root, ~m(community_id)a, %{context: %{cur_user: cur_user}}) do\n CMS.subscribe_community(%Community{id: community_id}, cur_user)\n end\n\n def unsubscribe_community(_root, ~m(community_id)a, %{context: %{cur_user: cur_user}}) do\n CMS.unsubscribe_community(%Community{id: community_id}, cur_user)\n end\n\n def community_subscribers(_root, ~m(id filter)a, _info) do\n CMS.community_members(:subscribers, %Community{id: id}, filter)\n end\n\n def mirror_article(_root, ~m(thread id community_id)a, _info) do\n CMS.mirror_article(%Community{id: community_id}, thread, id)\n end\n\n def unmirror_article(_root, ~m(thread id community_id)a, _info) do\n CMS.unmirror_article(%Community{id: community_id}, thread, id)\n end\n\n # #######################\n # comemnts ..\n # #######################\n def paged_comments(_root, ~m(id thread filter)a, _info),\n do: CMS.paged_comments(thread, id, filter)\n\n def create_comment(_root, ~m(thread id body)a, %{context: %{cur_user: user}}) do\n CMS.create_comment(thread, id, body, user)\n end\n\n def delete_comment(_root, ~m(thread id)a, _info) do\n CMS.delete_comment(thread, id)\n end\n\n def reply_comment(_root, ~m(thread id body)a, %{context: %{cur_user: user}}) do\n CMS.reply_comment(thread, id, body, user)\n end\n\n def like_comment(_root, ~m(thread id)a, %{context: %{cur_user: user}}) do\n CMS.like_comment(thread, id, user)\n end\n\n def undo_like_comment(_root, ~m(thread id)a, %{context: %{cur_user: user}}) do\n CMS.undo_like_comment(thread, id, user)\n end\n\n def dislike_comment(_root, ~m(thread id)a, %{context: %{cur_user: user}}) do\n CMS.dislike_comment(thread, id, user)\n end\n\n def undo_dislike_comment(_root, ~m(thread id)a, %{context: %{cur_user: user}}) do\n CMS.undo_dislike_comment(thread, id, user)\n end\n\n def stamp_passport(_root, ~m(user_id rules)a, %{context: %{cur_user: _user}}) do\n CMS.stamp_passport(rules, %User{id: user_id})\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,0,0,null,null,null,null,null,null,null,0,0,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,0,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/cms_types.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Types do\n @moduledoc \"\"\"\n cms types used in queries & mutations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n import GroupherServerWeb.Schema.Helper.Fields\n import Ecto.Query, warn: false\n import Absinthe.Resolution.Helpers, only: [dataloader: 2, on_load: 2]\n\n alias GroupherServer.CMS\n alias GroupherServerWeb.Schema\n\n import_types(Schema.CMS.Metrics)\n\n object :idlike do\n field(:id, :id)\n end\n\n object :comment do\n field(:id, :id)\n field(:body, :string)\n field(:floor, :integer)\n field(:author, :user, resolve: dataloader(CMS, :author))\n\n field :reply_to, :comment do\n resolve(dataloader(CMS, :reply_to))\n end\n\n field :likes, list_of(:user) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :likes))\n end\n\n field :likes_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(CMS, :likes))\n middleware(M.ConvertToInt)\n end\n\n field :viewer_has_liked, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n # put current user into dataloader's args\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :likes))\n middleware(M.ViewerDidConvert)\n end\n\n field :dislikes, list_of(:user) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :dislikes))\n end\n\n field :viewer_has_disliked, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n # put current user into dataloader's args\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :dislikes))\n middleware(M.ViewerDidConvert)\n end\n\n field :dislikes_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(CMS, :dislikes))\n middleware(M.ConvertToInt)\n end\n\n field :replies, list_of(:comment) do\n arg(:filter, :members_filter)\n\n middleware(M.ForceLoader)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :replies))\n end\n\n field :replies_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(CMS, :replies))\n middleware(M.ConvertToInt)\n end\n\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :post do\n interface(:article)\n field(:id, :id)\n field(:title, :string)\n field(:digest, :string)\n field(:length, :integer)\n field(:link_addr, :string)\n field(:body, :string)\n field(:views, :integer)\n field(:pin, :boolean)\n field(:markDelete, :boolean)\n field(:tags, list_of(:article_tag), resolve: dataloader(CMS, :tags))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n\n field :comments, list_of(:comment) do\n arg(:filter, :members_filter)\n\n # middleware(M.ForceLoader)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :comments))\n end\n\n field :comments_count, :integer do\n arg(:count, :count_type, default_value: :count)\n\n resolve(dataloader(CMS, :comments))\n middleware(M.ConvertToInt)\n end\n\n field :comments_participators, list_of(:user) do\n arg(:filter, :members_filter)\n arg(:unique, :unique_type, default_value: true)\n\n middleware(M.ForceLoader)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :comments))\n end\n\n field :comments_participators2, list_of(:user) do\n arg(:filter, :members_filter)\n arg(:unique, :unique_type, default_value: true)\n\n middleware(M.PageSizeProof)\n\n resolve(fn post, _args, %{context: %{loader: loader}} ->\n loader\n |> Dataloader.load(CMS, {:many, CMS.PostComment}, cp_users: post.id)\n |> on_load(fn loader ->\n {:ok, Dataloader.get(loader, CMS, {:many, CMS.PostComment}, cp_users: post.id)}\n end)\n end)\n end\n\n field :comments_participators_count, :integer do\n resolve(fn post, _args, %{context: %{loader: loader}} ->\n loader\n |> Dataloader.load(CMS, {:one, CMS.PostComment}, cp_count: post.id)\n |> on_load(fn loader ->\n {:ok, Dataloader.get(loader, CMS, {:one, CMS.PostComment}, cp_count: post.id)}\n end)\n end)\n end\n\n field :comments_participators_count_wired, :integer do\n arg(:unique, :unique_type, default_value: true)\n arg(:count, :count_type, default_value: :count)\n\n # middleware(M.ForceLoader)\n resolve(dataloader(CMS, :comments))\n # middleware(M.CountLength)\n end\n\n field :viewer_has_favorited, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n # put current user into dataloader's args\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :favorites))\n middleware(M.ViewerDidConvert)\n end\n\n field :viewer_has_starred, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :stars))\n middleware(M.ViewerDidConvert)\n end\n\n field :favorited_users, list_of(:user) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :favorites))\n end\n\n field :favorited_count, :integer do\n arg(:count, :count_type, default_value: :count)\n arg(:type, :post_type, default_value: :post)\n # middleware(M.SeeMe)\n resolve(dataloader(CMS, :favorites))\n middleware(M.ConvertToInt)\n end\n\n field :starred_count, :integer do\n arg(:count, :count_type, default_value: :count)\n arg(:type, :post_type, default_value: :post)\n\n resolve(dataloader(CMS, :stars))\n middleware(M.ConvertToInt)\n end\n\n field :starred_users, list_of(:user) do\n arg(:filter, :members_filter)\n\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :stars))\n end\n end\n\n object :video do\n interface(:article)\n field(:id, :id)\n field(:title, :string)\n field(:desc, :string)\n field(:duration, :string)\n field(:author, :user, resolve: dataloader(CMS, :author))\n\n field(:source, :string)\n field(:link, :string)\n field(:original_author, :string)\n field(:original_author_link, :string)\n field(:views, :integer)\n\n field(:pin, :boolean)\n field(:markDelete, :boolean)\n\n # field(:tags, list_of(:article_tag), resolve: dataloader(CMS, :tags))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :repo do\n # interface(:article)\n field(:id, :id)\n field(:repo_name, :string)\n field(:desc, :string)\n field(:readme, :string)\n field(:language, :string)\n field(:author, :user, resolve: dataloader(CMS, :author))\n\n field(:repo_link, :string)\n field(:producer, :string)\n field(:producer_link, :integer)\n\n field(:repo_star_count, :integer)\n field(:repo_fork_count, :integer)\n field(:repo_watch_count, :integer)\n field(:views, :integer)\n\n field(:pin, :boolean)\n field(:markDelete, :boolean)\n\n # field(:tags, list_of(:article_tag), resolve: dataloader(CMS, :tags))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :job do\n interface(:article)\n field(:id, :id)\n field(:title, :string)\n field(:desc, :string)\n field(:company, :string)\n field(:company_logo, :string)\n field(:digest, :string)\n field(:location, :string)\n field(:length, :integer)\n field(:link_addr, :string)\n field(:body, :string)\n field(:views, :integer)\n\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:tags, list_of(:article_tag), resolve: dataloader(CMS, :tags))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :thread do\n field(:id, :id)\n field(:title, :string)\n field(:raw, :string)\n field(:index, :integer)\n end\n\n object :contribute do\n field(:date, :date)\n field(:count, :integer)\n end\n\n object :contribute_map do\n field(:start_date, :date)\n field(:end_date, :date)\n field(:total_count, :integer)\n field(:records, list_of(:contribute))\n end\n\n object :community do\n # meta(:cache, max_age: 30)\n\n field(:id, :id)\n field(:title, :string)\n field(:desc, :string)\n field(:raw, :string)\n field(:logo, :string)\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:threads, list_of(:thread), resolve: dataloader(CMS, :threads))\n field(:categories, list_of(:category), resolve: dataloader(CMS, :categories))\n\n # Big thanks: https://elixirforum.com/t/grouping-error-in-absinthe-dadaloader/13671/2\n # see also: https://github.com/absinthe-graphql/dataloader/issues/25\n field :posts_count, :integer do\n resolve(fn community, _args, %{context: %{loader: loader}} ->\n loader\n |> Dataloader.load(CMS, {:one, CMS.Post}, posts_count: community.id)\n |> on_load(fn loader ->\n {:ok, Dataloader.get(loader, CMS, {:one, CMS.Post}, posts_count: community.id)}\n end)\n end)\n end\n\n field :subscribers, list_of(:user) do\n arg(:filter, :members_filter)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :subscribers))\n end\n\n field :subscribers_count, :integer do\n arg(:count, :count_type, default_value: :count)\n arg(:type, :community_type, default_value: :community)\n resolve(dataloader(CMS, :subscribers))\n middleware(M.ConvertToInt)\n end\n\n field :viewer_has_subscribed, :boolean do\n arg(:viewer_did, :viewer_did_type, default_value: :viewer_did)\n\n middleware(M.Authorize, :login)\n middleware(M.PutCurrentUser)\n resolve(dataloader(CMS, :subscribers))\n middleware(M.ViewerDidConvert)\n end\n\n field :editors, list_of(:user) do\n arg(:filter, :members_filter)\n middleware(M.PageSizeProof)\n resolve(dataloader(CMS, :editors))\n end\n\n field :editors_count, :integer do\n arg(:count, :count_type, default_value: :count)\n arg(:type, :community_type, default_value: :community)\n resolve(dataloader(CMS, :editors))\n middleware(M.ConvertToInt)\n end\n\n field :contributes, list_of(:contribute) do\n # TODO add complex here to warning N+1 problem\n resolve(&R.Statistics.list_contributes/3)\n end\n\n field :contributes_digest, list_of(:integer) do\n # TODO add complex here to warning N+1 problem\n resolve(&R.Statistics.list_contributes_digest/3)\n end\n end\n\n object :category do\n field(:id, :id)\n field(:title, :string)\n field(:raw, :string)\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:communities, list_of(:community), resolve: dataloader(CMS, :communities))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :article_tag do\n field(:id, :id)\n field(:title, :string)\n field(:color, :string)\n field(:thread, :string)\n field(:author, :user, resolve: dataloader(CMS, :author))\n field(:community, :community, resolve: dataloader(CMS, :community))\n field(:inserted_at, :datetime)\n field(:updated_at, :datetime)\n end\n\n object :paged_categories do\n field(:entries, list_of(:category))\n pagination_fields()\n end\n\n object :paged_posts do\n field(:entries, list_of(:post))\n pagination_fields()\n end\n\n object :paged_videos do\n field(:entries, list_of(:video))\n pagination_fields()\n end\n\n object :paged_repos do\n field(:entries, list_of(:repo))\n pagination_fields()\n end\n\n object :paged_jobs do\n field(:entries, list_of(:job))\n pagination_fields()\n end\n\n object :paged_comments do\n field(:entries, list_of(:comment))\n pagination_fields()\n end\n\n object :paged_communities do\n field(:entries, list_of(:community))\n pagination_fields()\n end\n\n object :paged_tags do\n field(:entries, list_of(:article_tag))\n pagination_fields()\n end\n\n object :paged_threads do\n field(:entries, list_of(:thread))\n pagination_fields()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/cms_queries.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Queries do\n @moduledoc \"\"\"\n CMS queries\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_queries do\n field :community, :community do\n # arg(:id, non_null(:id))\n arg(:id, :id)\n arg(:title, :string)\n arg(:raw, :string)\n resolve(&R.CMS.community/3)\n end\n\n @desc \"communities with pagination info\"\n field :paged_communities, :paged_communities do\n arg(:filter, non_null(:communities_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_communities/3)\n end\n\n @desc \"paged subscribers of a community\"\n field :community_subscribers, :paged_users do\n arg(:id, non_null(:id))\n arg(:filter, :paged_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.community_subscribers/3)\n end\n\n @desc \"paged subscribers of a community\"\n field :community_editors, :paged_users do\n arg(:id, non_null(:id))\n arg(:filter, :paged_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.community_editors/3)\n end\n\n @desc \"get all categories\"\n field :paged_categories, :paged_categories do\n arg(:filter, :paged_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_categories/3)\n end\n\n @desc \"get all the threads across all communities\"\n field :paged_threads, :paged_threads do\n arg(:filter, :threads_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_threads/3)\n end\n\n @desc \"get post by id\"\n field :post, non_null(:post) do\n arg(:id, non_null(:id))\n resolve(&R.CMS.post/3)\n end\n\n @desc \"get paged posts\"\n field :paged_posts, :paged_posts do\n arg(:filter, non_null(:paged_article_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_posts/3)\n end\n\n @desc \"get video by id\"\n field :video, non_null(:video) do\n arg(:id, non_null(:id))\n resolve(&R.CMS.video/3)\n end\n\n @desc \"get paged videos\"\n field :paged_videos, :paged_videos do\n arg(:filter, non_null(:paged_article_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_videos/3)\n end\n\n @desc \"get repo by id\"\n field :repo, non_null(:repo) do\n arg(:id, non_null(:id))\n resolve(&R.CMS.repo/3)\n end\n\n @desc \"get paged videos\"\n field :paged_repos, :paged_repos do\n arg(:filter, non_null(:paged_article_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_repos/3)\n end\n\n @desc \"get job by id\"\n field :job, non_null(:job) do\n arg(:id, non_null(:id))\n resolve(&R.CMS.job/3)\n end\n\n @desc \"get paged jobs\"\n field :paged_jobs, :paged_jobs do\n arg(:filter, non_null(:paged_article_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_jobs/3)\n end\n\n field :favorite_users, :paged_users do\n arg(:id, non_null(:id))\n arg(:type, :thread, default_value: :post)\n arg(:action, :favorite_action, default_value: :favorite)\n arg(:filter, :paged_article_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.reaction_users/3)\n end\n\n # get all tags\n field :paged_tags, :paged_tags do\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.get_tags/3)\n end\n\n # TODO: remove\n field :tags, :paged_tags do\n arg(:filter, non_null(:paged_filter))\n\n middleware(M.PageSizeProof)\n # TODO: should be passport\n resolve(&R.CMS.get_tags/3)\n end\n\n # partial\n @desc \"get paged tags belongs to community_id or community\"\n field :partial_tags, list_of(:article_tag) do\n arg(:community_id, :id)\n arg(:community, :string)\n arg(:thread, :thread, default_value: :post)\n\n resolve(&R.CMS.get_tags/3)\n end\n\n @desc \"get paged comments\"\n field :paged_comments, :paged_comments do\n arg(:id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n arg(:filter, :comments_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_comments/3)\n end\n\n # comments\n field :comments, :paged_comments do\n arg(:id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n arg(:filter, :comments_filter)\n\n middleware(M.PageSizeProof)\n resolve(&R.CMS.paged_comments/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,5,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/purchase.ex","source":"defmodule GroupherServer.Accounts.Purchase do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id)a\n @optional_fields ~w(theme community_chart brainwash_free)a\n\n @type t :: %Purchase{}\n schema \"purchases\" do\n belongs_to(:user, User)\n\n field(:theme, :boolean)\n field(:community_chart, :boolean)\n field(:brainwash_free, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Purchase{} = purchase, attrs) do\n purchase\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,1,null,null,null,0,null,null,null,0,null,null,null,0,null,null,null,3,null,null],"name":"lib/groupher_server_web/resolvers/statistics_resolver.ex","source":"defmodule GroupherServerWeb.Resolvers.Statistics do\n @moduledoc \"\"\"\n resolvers for Statistics\n \"\"\"\n alias GroupherServer.{Accounts, CMS, Statistics}\n # alias Helper.ORM\n\n # tmp for test\n def list_contributes(_root, %{id: id}, _info) do\n Statistics.list_contributes(%Accounts.User{id: id})\n end\n\n def list_contributes(%Accounts.User{id: id}, _args, _info) do\n Statistics.list_contributes(%Accounts.User{id: id})\n end\n\n def list_contributes(%CMS.Community{id: id}, _args, _info) do\n Statistics.list_contributes(%CMS.Community{id: id})\n end\n\n def list_contributes_digest(%CMS.Community{id: id}, _args, _info) do\n Statistics.list_contributes_digest(%CMS.Community{id: id})\n end\n\n def make_contrubute(_root, %{user_id: user_id}, _info) do\n Statistics.make_contribute(%Accounts.User{id: user_id})\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,99,98,null,null,null,null,null,null,null,null,null,null,95,null,null,97,null,null,null,null,null,null,null,null,null,null,null,95,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,4,4,null,null,4,null,null,4,null,null,4,null,null,4,null,null,null,null,null,null,null,null,null,null,4,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,3,3,null,null,null,null,null,null,null,null,3,3,3,null,null,null,null,null,6,null,null,6,null,null],"name":"lib/groupher_server/accounts/delegates/fans.ex","source":"defmodule GroupherServer.Accounts.Delegate.Fans do\n @moduledoc \"\"\"\n user followers / following related\n \"\"\"\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1]\n import Helper.ErrorCode\n import ShortMaps\n\n alias Helper.{ORM, QueryBuilder, SpecType}\n alias GroupherServer.{Accounts, Repo}\n\n alias GroupherServer.Accounts.{User, UserFollower, UserFollowing}\n\n alias Ecto.Multi\n\n @doc \"\"\"\n follow a user\n \"\"\"\n @spec follow(User.t(), User.t()) :: {:ok, User.t()} | SpecType.gq_error()\n def follow(%User{id: user_id}, %User{id: follower_id}) do\n with true <- to_string(user_id) !== to_string(follower_id),\n {:ok, _follow_user} <- ORM.find(User, follower_id) do\n Multi.new()\n |> Multi.insert(\n :create_follower,\n UserFollower.changeset(%UserFollower{}, ~m(user_id follower_id)a)\n )\n |> Multi.insert(\n :create_following,\n UserFollowing.changeset(%UserFollowing{}, %{user_id: user_id, following_id: follower_id})\n )\n |> Multi.run(:add_achievement, fn _ ->\n Accounts.achieve(%User{id: follower_id}, :add, :follow)\n end)\n |> Repo.transaction()\n |> follow_result()\n else\n false ->\n {:error, [message: \"can't follow yourself\", code: ecode(:self_conflict)]}\n\n {:error, error} ->\n {:error, [message: error, code: ecode(:not_exsit)]}\n end\n end\n\n @spec follow_result({:ok, map()}) :: SpecType.done()\n defp follow_result({:ok, %{create_follower: user_follower}}) do\n User |> ORM.find(user_follower.follower_id)\n end\n\n defp follow_result({:error, :create_follower, _result, _steps}) do\n {:error, [message: \"already followed\", code: ecode(:already_did)]}\n end\n\n defp follow_result({:error, :create_following, _result, _steps}) do\n {:error, [message: \"follow fails\", code: ecode(:react_fails)]}\n end\n\n defp follow_result({:error, :add_achievement, _result, _steps}) do\n {:error, [message: \"follow acieve fails\", code: ecode(:react_fails)]}\n end\n\n @doc \"\"\"\n undo a follow action to a user\n \"\"\"\n @spec undo_follow(User.t(), User.t()) :: {:ok, User.t()} | SpecType.gq_error()\n def undo_follow(%User{id: user_id}, %User{id: follower_id}) do\n with true <- to_string(user_id) !== to_string(follower_id),\n {:ok, _follow_user} <- ORM.find(User, follower_id) do\n Multi.new()\n |> Multi.run(:delete_follower, fn _ ->\n ORM.findby_delete!(UserFollower, ~m(user_id follower_id)a)\n end)\n |> Multi.run(:delete_following, fn _ ->\n ORM.findby_delete!(UserFollowing, %{user_id: user_id, following_id: follower_id})\n end)\n |> Multi.run(:minus_achievement, fn _ ->\n Accounts.achieve(%User{id: follower_id}, :minus, :follow)\n end)\n |> Repo.transaction()\n |> undo_follow_result()\n else\n false ->\n {:error, [message: \"can't undo follow yourself\", code: ecode(:self_conflict)]}\n\n {:error, error} ->\n {:error, [message: error, code: ecode(:not_exsit)]}\n end\n end\n\n defp undo_follow_result({:ok, %{delete_follower: user_follower}}) do\n User |> ORM.find(user_follower.follower_id)\n end\n\n defp undo_follow_result({:error, :delete_follower, _result, _steps}) do\n {:error, [message: \"already unfollowed\", code: ecode(:already_did)]}\n end\n\n defp undo_follow_result({:error, :delete_following, _result, _steps}) do\n {:error, [message: \"unfollow fails\", code: ecode(:react_fails)]}\n end\n\n defp undo_follow_result({:error, :minus_achievement, _result, _steps}) do\n {:error, [message: \"follow acieve fails\", code: ecode(:react_fails)]}\n end\n\n @doc \"\"\"\n get paged followers of a user\n \"\"\"\n @spec fetch_followers(User.t(), map()) :: {:ok, map()} | {:error, String.t()}\n def fetch_followers(%User{id: user_id}, filter) do\n UserFollower\n |> where([uf], uf.follower_id == ^user_id)\n |> join(:inner, [uf], u in assoc(uf, :user))\n |> load_fans(filter)\n end\n\n @doc \"\"\"\n get paged followings of a user\n \"\"\"\n @spec fetch_followings(User.t(), map()) :: {:ok, map()} | {:error, String.t()}\n def fetch_followings(%User{id: user_id}, filter) do\n UserFollowing\n |> where([uf], uf.user_id == ^user_id)\n |> join(:inner, [uf], u in assoc(uf, :following))\n |> load_fans(filter)\n end\n\n @spec load_fans(Ecto.Queryable.t(), map()) :: {:ok, map()} | {:error, String.t()}\n defp load_fans(queryable, ~m(page size)a = filter) do\n queryable\n |> select([uf, u], u)\n |> QueryBuilder.filter_pack(filter)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,18,null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,null],"name":"lib/groupher_server/cms/community_thread.ex","source":"defmodule GroupherServer.CMS.CommunityThread do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.CMS.{Community, Thread}\n\n @required_fields ~w(community_id thread_id)a\n\n @type t :: %CommunityThread{}\n schema \"communities_threads\" do\n belongs_to(:community, Community, foreign_key: :community_id)\n belongs_to(:thread, Thread, foreign_key: :thread_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%CommunityThread{} = community_thread, attrs) do\n community_thread\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:community_id)\n |> foreign_key_constraint(:thread_id)\n |> unique_constraint(:community_id, name: :communities_threads_community_id_thread_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,176,null,12,null,null,164,null,null,null,null,164,null,16,148,null,164,null,null,0,162,null,14,null,null,162,null,150,null,null,null,12,null,null,null],"name":"lib/groupher_server_web/middleware/pagesize_proof.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.PageSizeProof do\n @behaviour Absinthe.Middleware\n\n import Helper.Utils, only: [handle_absinthe_error: 3, get_config: 2]\n import Helper.ErrorCode\n\n @max_page_size get_config(:general, :page_size)\n @inner_page_size get_config(:general, :inner_page_size)\n\n # 1. if has filter:first and filter:size -> makesure it not too large\n # 2. if not has filter: marge to default first: 5\n # 3. large size should trigger error\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(resolution, _) do\n # IO.inspect resolution.arguments, label: \"resolution arguments\"\n # IO.inspect valid_size(resolution.arguments), label: \"valid_size\"\n\n case valid_size(resolution.arguments) do\n {:error, msg} ->\n resolution |> handle_absinthe_error(msg, ecode(:pagination))\n\n arguments ->\n %{resolution | arguments: sort_desc_by_default(arguments)}\n end\n end\n\n defp sort_desc_by_default(%{filter: filter} = arguments) do\n filter =\n if Map.has_key?(filter, :sort),\n do: filter,\n else: filter |> Map.merge(%{sort: :desc_inserted})\n\n arguments |> Map.merge(%{filter: filter})\n end\n\n defp valid_size(%{filter: %{first: size}} = arg), do: do_size_check(size, arg)\n defp valid_size(%{filter: %{size: size}} = arg), do: do_size_check(size, arg)\n\n defp valid_size(arg), do: arg |> Map.merge(%{filter: %{first: @inner_page_size}})\n\n defp do_size_check(size, arg) do\n case size in 1..@max_page_size do\n true ->\n arg\n\n _ ->\n {:error,\n \"SIZE_RANGE_ERROR: size shuold between 0 and #{@max_page_size}, current: #{size}\"}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/operation.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Operation do\n @moduledoc \"\"\"\n CMS mutations for cms operations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_opertion_mutations do\n @desc \"set category to a community\"\n field :set_category, :community do\n arg(:community_id, non_null(:id))\n arg(:category_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.set\")\n\n resolve(&R.CMS.set_category/3)\n end\n\n @desc \"unset category to a community\"\n field :unset_category, :community do\n arg(:community_id, non_null(:id))\n arg(:category_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.unset\")\n\n resolve(&R.CMS.unset_category/3)\n end\n\n @desc \"bind a thread to a exist community\"\n field :set_thread, :community do\n arg(:community_id, non_null(:id))\n arg(:thread_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->thread.set\")\n\n resolve(&R.CMS.set_thread/3)\n end\n\n @desc \"remove a thread from a exist community, thread content is not delete\"\n field :unset_thread, :community do\n arg(:community_id, non_null(:id))\n arg(:thread_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->thread.unset\")\n\n resolve(&R.CMS.unset_thread/3)\n end\n\n @desc \"stamp rules on user's passport\"\n field :stamp_cms_passport, :idlike do\n arg(:user_id, non_null(:id))\n arg(:rules, non_null(:json))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->community.stamp_passport\")\n\n resolve(&R.CMS.stamp_passport/3)\n end\n\n @desc \"subscribe a community so it can appear in sidebar\"\n field :subscribe_community, :community do\n arg(:community_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.subscribe_community/3)\n end\n\n @desc \"unsubscribe a community\"\n field :unsubscribe_community, :community do\n arg(:community_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.unsubscribe_community/3)\n end\n\n @desc \"set a tag within community\"\n field :set_tag, :article_tag do\n arg(:id, non_null(:id))\n arg(:tag_id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.set\")\n\n resolve(&R.CMS.set_tag/3)\n end\n\n @desc \"unset a tag within community\"\n field :unset_tag, :article_tag do\n # thread id\n arg(:id, non_null(:id))\n arg(:tag_id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.unset\")\n\n resolve(&R.CMS.unset_tag/3)\n end\n\n # TODO: use community loader\n field :mirror_article, :community do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->t?.community.mirror\")\n resolve(&R.CMS.mirror_article/3)\n end\n\n # TODO: can't not unset the oldest community\n field :unmirror_article, :community do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->t?.community.unmirror\")\n resolve(&R.CMS.unmirror_article/3)\n end\n\n field :reaction, :article do\n arg(:id, non_null(:id))\n arg(:thread, non_null(:thread))\n arg(:action, non_null(:cms_action))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.reaction/3)\n end\n\n field :undo_reaction, :article do\n arg(:id, non_null(:id))\n arg(:thread, non_null(:thread))\n arg(:action, non_null(:cms_action))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.undo_reaction/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,17,9,7,5,3,null,null,null,2,null,null,null,2,null,null,null,2,null,null,null,8,null,null,null,null,null,0,null,null,null,null,9,9,null,9,null,null,null,null,null,null,7,null,7,null,null,null,null,null,null,5,null,5,null,null,null,null,null],"name":"lib/groupher_server_web/middleware/publish_throttle.ex","source":"defmodule GroupherServerWeb.Middleware.PublishThrottle do\n @behaviour Absinthe.Middleware\n import Helper.Utils, only: [handle_absinthe_error: 3, get_config: 2]\n import Helper.ErrorCode\n\n alias GroupherServer.{Statistics, Accounts}\n\n @interval_minutes get_config(:general, :publish_throttle_interval_minutes)\n @hour_limit get_config(:general, :publish_throttle_hour_limit)\n @day_total get_config(:general, :publish_throttle_day_limit)\n\n def call(%{context: %{cur_user: cur_user}} = resolution, opt) do\n with {:ok, record} <- Statistics.load_throttle_record(%Accounts.User{id: cur_user.id}),\n {:ok, _} <- interval_check(record, opt),\n {:ok, _} <- hour_limit_check(record, opt),\n {:ok, _} <- day_limit_check(record, opt) do\n resolution\n else\n {:error, :interval_check} ->\n resolution\n |> handle_absinthe_error(\"throttle_interval\", ecode(:throttle_inverval))\n\n {:error, :hour_limit_check} ->\n resolution\n |> handle_absinthe_error(\"throttle_hour\", ecode(:throttle_hour))\n\n {:error, :day_limit_check} ->\n resolution\n |> handle_absinthe_error(\"throttle_day\", ecode(:throttle_day))\n\n {:error, _error} ->\n # publish first time ignore\n resolution\n end\n end\n\n def call(resolution, _) do\n resolution\n |> handle_absinthe_error(\"Authorize: need login\", ecode(:account_login))\n end\n\n # TODO: option: passport ..\n defp interval_check(%Statistics.PublishThrottle{last_publish_time: last_publish_time}, opt) do\n interval_opt = Keyword.get(opt, :interval) || @interval_minutes\n latest_valid_time = Timex.shift(last_publish_time, minutes: interval_opt)\n\n case Timex.before?(latest_valid_time, Timex.now()) do\n true -> {:ok, :interval_check}\n false -> {:error, :interval_check}\n end\n end\n\n defp hour_limit_check(%Statistics.PublishThrottle{hour_count: hour_count}, opt) do\n hour_count_opt = Keyword.get(opt, :hour_limit) || @hour_limit\n\n case hour_count < hour_count_opt do\n true -> {:ok, :hour_limit_check}\n false -> {:error, :hour_limit_check}\n end\n end\n\n defp day_limit_check(%Statistics.PublishThrottle{date_count: day_count}, opt) do\n day_limit_opt = Keyword.get(opt, :day_limit) || @day_total\n\n case day_count < day_limit_opt do\n true -> {:ok, :day_limit_check}\n false -> {:error, :day_limit_check}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,9771,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,5,null,null,null,null],"name":"lib/groupher_server/accounts/user.ex","source":"defmodule GroupherServer.Accounts.User do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.Accounts.{\n Achievement,\n Customization,\n GithubUser,\n Purchase,\n UserBill,\n UserFollower,\n UserFollowing\n }\n\n alias GroupherServer.CMS\n\n @type t :: %User{}\n schema \"users\" do\n field(:nickname, :string)\n field(:avatar, :string)\n field(:sex, :string)\n field(:bio, :string)\n field(:email, :string)\n field(:location, :string)\n field(:education, :string)\n field(:company, :string)\n field(:qq, :string)\n field(:weibo, :string)\n field(:weichat, :string)\n field(:from_github, :boolean)\n has_one(:achievement, Achievement)\n has_one(:github_profile, GithubUser)\n has_one(:cms_passport, CMS.Passport)\n\n has_many(:followers, {\"users_followers\", UserFollower})\n has_many(:followings, {\"users_followings\", UserFollowing})\n\n has_many(:subscribed_communities, {\"communities_subscribers\", CMS.CommunitySubscriber})\n has_many(:favorited_posts, {\"posts_favorites\", CMS.PostFavorite})\n has_many(:favorited_jobs, {\"jobs_favorites\", CMS.JobFavorite})\n\n field(:sponsor_member, :boolean)\n field(:paid_member, :boolean)\n field(:platinum_member, :boolean)\n\n has_many(:bills, {\"users_bills\", UserBill})\n has_one(:customization, Customization)\n has_one(:purchase, Purchase)\n\n timestamps(type: :utc_datetime)\n end\n\n @required_fields ~w(nickname avatar)a\n @optional_fields ~w(nickname bio avatar sex location email company education qq weichat weibo)a\n\n @doc false\n def changeset(%User{} = user, attrs) do\n # |> cast(attrs, [:username, :nickname, :bio, :company])\n # |> validate_required([:username])\n # |> cast(attrs, @required_fields, @optional_fields)\n user\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> validate_length(:nickname, min: 3, max: 30)\n |> validate_length(:bio, min: 3, max: 100)\n |> validate_inclusion(:sex, [\"dude\", \"girl\"])\n |> validate_format(:email, ~r/@/)\n |> validate_length(:location, min: 2, max: 30)\n |> validate_length(:company, min: 3, max: 30)\n |> validate_length(:qq, min: 8, max: 15)\n |> validate_length(:weichat, min: 3, max: 30)\n |> validate_length(:weibo, min: 3, max: 30)\n\n # |> unique_constraint(:username)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,4,null,null],"name":"lib/groupher_server/accounts/customization.ex","source":"defmodule GroupherServer.Accounts.Customization do\n @moduledoc false\n\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id)a\n @optional_fields ~w(theme sidebar_layout community_chart brainwash_free)a\n\n @type t :: %Customization{}\n schema \"customizations\" do\n belongs_to(:user, User)\n\n field(:theme, :boolean)\n field(:sidebar_layout, :map)\n field(:community_chart, :boolean)\n field(:brainwash_free, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Customization{} = customization, attrs) do\n customization\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,0,null,null,0,null,null,null,0,null,null,0,null],"name":"lib/groupher_server_web/middleware/count_length.ex","source":"# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.CountLength do\n @behaviour Absinthe.Middleware\n # google: must appear in the GROUP BY clause or be used in an aggregate function\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(%{value: []} = resolution, _) do\n %{resolution | value: 0}\n end\n\n def call(%{value: value} = resolution, _) when is_list(value) do\n %{resolution | value: length(value)}\n end\n\n def call(resolution, _), do: resolution\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,58,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/delivery/sys_notification.ex","source":"defmodule GroupherServer.Delivery.SysNotification do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n @required_fields ~w(source_title source_id source_type)a\n @optional_fields ~w(source_preview)a\n\n @type t :: %SysNotification{}\n schema \"sys_notifications\" do\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:source_preview, :string)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%SysNotification{} = sys_notification, attrs) do\n sys_notification\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,881,null,881,null,null,null,null,null,null,null,null,null,null,null,null,null,null,537,null,537,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,536,null,536,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,542,542,null,542,null,null,null,542,null,null,null,null,null,null,null,null,null,null,null,null,0,null,0,null,null,null,0,null,0,null,null,null,null,0,null,null,null,null,2628,null,null,null,0,null,null,null,59,59,null,null,null,4718,4718,null,4718,4718,null,4718,null,null,null,null,null,null,69,null,69,69,69,null,null,null,null,null,63,null,63,63,null,null,null,null,null,null,null,null,null,null,27,null,27,27,27,null,27,null,null,null,null,8433,null,8433,null,8433,null,null,null,null,null,null,1,null,1,1,1,1,1,1,null,null,null,null,null,null,null,null,null,null,null,235,209,0,27,4,3,6,5,0,0,null,null,0,null,8,1,8,1,null,null,null,null,null,null,null,null,null,854,533,533,536,0,0,2628,61,55,null,null,26,null,8224,4713,59,null,null,0,null,null,3735,null,null,null,null,null,null,null,3735,null,null,null,134,null,2617,2617,null,null,134,null,null,null,null,null,null,6,null,null,null,12,null,12,28,null,28,null,28,null,28,null,null,28,null,null,null,null,12,null,12,62,null,62,null,62,null,62,null,null,null,62,null,null,null],"name":"test/support/factory.ex","source":"defmodule GroupherServer.Factory do\n @moduledoc \"\"\"\n This module defines the mock data/func to be used by\n tests that require insert some mock data to db.\n\n for example you can db_insert(:user) to insert user into db\n \"\"\"\n import Helper.Utils, only: [done: 1]\n\n alias GroupherServer.Repo\n alias GroupherServer.{CMS, Accounts, Delivery}\n\n defp mock_meta(:post) do\n body = Faker.Lorem.sentence(%Range{first: 80, last: 120})\n\n %{\n title: Faker.Lorem.Shakespeare.king_richard_iii(),\n body: body,\n digest: String.slice(body, 1, 150),\n length: String.length(body),\n author: mock(:author),\n views: Enum.random(0..2000),\n communities: [\n mock(:community),\n mock(:community)\n ]\n }\n end\n\n defp mock_meta(:video) do\n desc = Faker.Lorem.sentence(%Range{first: 15, last: 60})\n\n %{\n title: Faker.Lorem.Shakespeare.king_richard_iii(),\n poster: Faker.Avatar.image_url(),\n desc: desc,\n duration: \"03:30\",\n duration_sec: Enum.random(300..12000),\n source: \"youtube\",\n link: \"http://www.youtube.com/video/1\",\n original_author: \"simon\",\n original_author_link: \"http://www.youtube.com/user/1\",\n author: mock(:author),\n views: Enum.random(0..2000),\n communities: [\n mock(:community),\n mock(:community)\n ]\n }\n end\n\n defp mock_meta(:repo) do\n desc = Faker.Lorem.sentence(%Range{first: 15, last: 60})\n\n %{\n repo_name: Faker.Lorem.Shakespeare.king_richard_iii(),\n desc: desc,\n readme: desc,\n language: \"javascript\",\n author: mock(:author),\n repo_link: \"http://www.github.com/mydearxym\",\n producer: \"mydearxym\",\n producer_link: \"http://www.github.com/mydearxym\",\n repo_star_count: Enum.random(0..2000),\n repo_fork_count: Enum.random(0..2000),\n repo_watch_count: Enum.random(0..2000),\n views: Enum.random(0..2000),\n communities: [\n mock(:community),\n mock(:community)\n ]\n }\n end\n\n defp mock_meta(:job) do\n body = Faker.Lorem.sentence(%Range{first: 80, last: 120})\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n title: Faker.Lorem.Shakespeare.king_richard_iii(),\n company: Faker.Company.name(),\n company_logo: Faker.Avatar.image_url(),\n location: \"location #{unique_num}\",\n body: body,\n digest: String.slice(body, 1, 150),\n length: String.length(body),\n author: mock(:author),\n views: Enum.random(0..2000),\n communities: [\n mock(:community)\n ]\n }\n end\n\n defp mock_meta(:comment) do\n body = Faker.Lorem.sentence(%Range{first: 30, last: 80})\n\n %{body: body}\n end\n\n defp mock_meta(:mention) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n from_user: mock(:user),\n to_user: mock(:user),\n source_id: \"1\",\n source_type: \"post\",\n source_preview: \"source_preview #{unique_num}.\"\n }\n end\n\n defp mock_meta(:author) do\n %{role: \"normal\", user: mock(:user)}\n end\n\n defp mock_meta(:communities_threads) do\n %{community_id: 1, thread_id: 1}\n end\n\n defp mock_meta(:thread) do\n unique_num = System.unique_integer([:positive, :monotonic])\n %{title: \"thread #{unique_num}\", raw: \"thread #{unique_num}\", index: :rand.uniform(20)}\n end\n\n defp mock_meta(:community) do\n unique_num = System.unique_integer([:positive, :monotonic])\n random_num = Enum.random(0..2000)\n\n %{\n title: \"community_#{random_num}_#{unique_num}\",\n desc: \"community desc\",\n raw: \"community_#{unique_num}\",\n logo: \"https://coderplanets.oss-cn-beijing.aliyuncs.com/icons/pl/elixir.svg\",\n author: mock(:user)\n }\n end\n\n defp mock_meta(:category) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n title: \"category#{unique_num}\",\n raw: \"category#{unique_num}\",\n author: mock(:author)\n }\n end\n\n defp mock_meta(:article_tag) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n title: \"#{Faker.Pizza.cheese()} #{unique_num}\",\n thread: \"POST\",\n color: \"YELLOW\",\n # community: Faker.Pizza.topping(),\n community: mock(:community),\n author: mock(:author)\n # user_id: 1\n }\n end\n\n defp mock_meta(:sys_notification) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n source_id: \"#{unique_num}\",\n source_title: \"#{Faker.Pizza.cheese()}\",\n source_type: \"post\",\n source_preview: \"#{Faker.Pizza.cheese()}\"\n }\n end\n\n defp mock_meta(:user) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n # username: \"#{Faker.Name.first_name()} #{unique_num}\",\n nickname: \"#{Faker.Name.first_name()} #{unique_num}\",\n bio: Faker.Lorem.Shakespeare.romeo_and_juliet(),\n avatar: Faker.Avatar.image_url()\n }\n end\n\n defp mock_meta(:github_profile) do\n unique_num = System.unique_integer([:positive, :monotonic])\n\n %{\n id: \"#{Faker.Name.first_name()} #{unique_num}\",\n login: \"#{Faker.Name.first_name()} #{unique_num}\",\n github_id: \"#{unique_num + 1000}\",\n node_id: \"#{unique_num + 2000}\",\n access_token: \"#{unique_num + 3000}\",\n bio: Faker.Lorem.Shakespeare.romeo_and_juliet(),\n company: Faker.Company.name(),\n location: \"chengdu\",\n email: Faker.Internet.email(),\n avatar_url: Faker.Avatar.image_url(),\n html_url: Faker.Avatar.image_url(),\n followers: unique_num * unique_num,\n following: unique_num * unique_num * unique_num\n }\n end\n\n def mock_attrs(_, attrs \\\\ %{})\n def mock_attrs(:user, attrs), do: mock_meta(:user) |> Map.merge(attrs)\n def mock_attrs(:author, attrs), do: mock_meta(:author) |> Map.merge(attrs)\n def mock_attrs(:post, attrs), do: mock_meta(:post) |> Map.merge(attrs)\n def mock_attrs(:video, attrs), do: mock_meta(:video) |> Map.merge(attrs)\n def mock_attrs(:repo, attrs), do: mock_meta(:repo) |> Map.merge(attrs)\n def mock_attrs(:job, attrs), do: mock_meta(:job) |> Map.merge(attrs)\n def mock_attrs(:community, attrs), do: mock_meta(:community) |> Map.merge(attrs)\n def mock_attrs(:thread, attrs), do: mock_meta(:thread) |> Map.merge(attrs)\n def mock_attrs(:mention, attrs), do: mock_meta(:mention) |> Map.merge(attrs)\n\n def mock_attrs(:communities_threads, attrs),\n do: mock_meta(:communities_threads) |> Map.merge(attrs)\n\n def mock_attrs(:article_tag, attrs), do: mock_meta(:article_tag) |> Map.merge(attrs)\n def mock_attrs(:sys_notification, attrs), do: mock_meta(:sys_notification) |> Map.merge(attrs)\n def mock_attrs(:category, attrs), do: mock_meta(:category) |> Map.merge(attrs)\n def mock_attrs(:github_profile, attrs), do: mock_meta(:github_profile) |> Map.merge(attrs)\n\n # NOTICE: avoid Recursive problem\n # bad example:\n # mismatch mismatch\n # | |\n # defp mock(:user), do: Accounts.User |> struct(mock_meta(:community))\n\n # this line of code will cause SERIOUS Recursive problem\n\n defp mock(:post), do: CMS.Post |> struct(mock_meta(:post))\n defp mock(:video), do: CMS.Video |> struct(mock_meta(:video))\n defp mock(:repo), do: CMS.Repo |> struct(mock_meta(:repo))\n defp mock(:job), do: CMS.Job |> struct(mock_meta(:job))\n defp mock(:comment), do: CMS.Comment |> struct(mock_meta(:comment))\n defp mock(:mention), do: Delivery.Mention |> struct(mock_meta(:mention))\n defp mock(:author), do: CMS.Author |> struct(mock_meta(:author))\n defp mock(:category), do: CMS.Category |> struct(mock_meta(:category))\n defp mock(:article_tag), do: CMS.Tag |> struct(mock_meta(:article_tag))\n\n defp mock(:sys_notification),\n do: Delivery.SysNotification |> struct(mock_meta(:sys_notification))\n\n defp mock(:user), do: Accounts.User |> struct(mock_meta(:user))\n defp mock(:community), do: CMS.Community |> struct(mock_meta(:community))\n defp mock(:thread), do: CMS.Thread |> struct(mock_meta(:thread))\n\n defp mock(:communities_threads),\n do: CMS.CommunityThread |> struct(mock_meta(:communities_threads))\n\n defp mock(factory_name, attributes) do\n factory_name |> mock() |> struct(attributes)\n end\n\n # \"\"\"\n # not use changeset because in test we may insert some attrs which not in schema\n # like: views, insert/update ... to test filter-sort,when ...\n # \"\"\"\n def db_insert(factory_name, attributes \\\\ []) do\n Repo.insert(mock(factory_name, attributes))\n end\n\n def db_insert_multi(factory_name, count \\\\ 2) do\n results =\n Enum.reduce(1..count, [], fn _, acc ->\n {:ok, value} = db_insert(factory_name)\n acc ++ [value]\n end)\n\n results |> done\n end\n\n alias GroupherServer.Accounts.User\n\n def mock_sys_notification(count \\\\ 3) do\n # {:ok, sys_notifications} = db_insert_multi(:sys_notification, count)\n db_insert_multi(:sys_notification, count)\n end\n\n def mock_mentions_for(%User{id: _to_user_id} = user, count \\\\ 3) do\n {:ok, users} = db_insert_multi(:user, count)\n\n Enum.map(users, fn u ->\n unique_num = System.unique_integer([:positive, :monotonic])\n\n info = %{\n source_id: \"1\",\n source_title: \"Title #{unique_num}\",\n source_type: \"post\",\n source_preview: \"preview #{unique_num}\"\n }\n\n {:ok, _} = Delivery.mention_someone(u, user, info)\n end)\n end\n\n def mock_notifications_for(%User{id: _to_user_id} = user, count \\\\ 3) do\n {:ok, users} = db_insert_multi(:user, count)\n\n Enum.map(users, fn u ->\n unique_num = System.unique_integer([:positive, :monotonic])\n\n info = %{\n source_id: \"1\",\n source_title: \"Title #{unique_num}\",\n source_type: \"post\",\n source_preview: \"preview #{unique_num}\",\n action: \"like\"\n }\n\n {:ok, _} = Delivery.notify_someone(u, user, info)\n end)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,368,null,null,null,null,null,null,null,null,null,null,null,null,140,null,null],"name":"lib/groupher_server/cms/passport.ex","source":"defmodule GroupherServer.CMS.Passport do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n @type t :: %Passport{}\n schema \"cms_passports\" do\n field(:rules, :map)\n belongs_to(:user, Accounts.User)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Passport{} = passport, attrs) do\n passport\n |> cast(attrs, [:rules, :user_id])\n |> validate_required([:rules, :user_id])\n |> unique_constraint(:user_id)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,3,null,3,null,null,null,null,null,null,211,79,null,null,null,null,null,null,null,null,139,null,5,5,null,null,134,134,null,null,null,null,4,4,null,3,null,null,1,null,null,null,null,null,1,null,null,null,139,null,null,null,139,null,null],"name":"lib/groupher_server/cms/delegates/passport_curd.ex","source":"defmodule GroupherServer.CMS.Delegate.PassportCURD do\n @moduledoc \"\"\"\n passport curd\n \"\"\"\n import Helper.Utils, only: [done: 1, deep_merge: 2]\n import Ecto.Query, warn: false\n import ShortMaps\n\n alias Helper.{NestedFilter, ORM}\n alias GroupherServer.CMS.Passport, as: UserPasport\n alias GroupherServer.{Accounts, Repo}\n\n # https://medium.com/front-end-hacking/use-github-oauth-as-your-sso-seamlessly-with-react-3e2e3b358fa1\n # http://www.ubazu.com/using-postgres-jsonb-columns-in-ecto\n # http://www.ubazu.com/using-postgres-jsonb-columns-in-ecto\n\n def paged_passports(community, key) do\n UserPasport\n |> where([p], fragment(\"(?->?->>?)::boolean = ?\", p.rules, ^community, ^key, true))\n |> Repo.all()\n |> done\n end\n\n @doc \"\"\"\n return a user's passport in CMS context\n \"\"\"\n def get_passport(%Accounts.User{} = user) do\n with {:ok, passport} <- ORM.find_by(UserPasport, user_id: user.id) do\n {:ok, passport.rules}\n end\n end\n\n # TODO passport should be public utils\n @doc \"\"\"\n insert or update a user's passport in CMS context\n \"\"\"\n def stamp_passport(rules, %Accounts.User{id: user_id}) do\n case ORM.find_by(UserPasport, user_id: user_id) do\n {:ok, passport} ->\n rules = passport.rules |> deep_merge(rules) |> reject_invalid_rules\n passport |> ORM.update(~m(rules)a)\n\n {:error, _} ->\n rules = rules |> reject_invalid_rules\n UserPasport |> ORM.create(~m(user_id rules)a)\n end\n end\n\n def erase_passport(rules, %Accounts.User{id: user_id}) when is_list(rules) do\n with {:ok, passport} <- ORM.find_by(UserPasport, user_id: user_id) do\n case pop_in(passport.rules, rules) do\n {nil, _} ->\n {:error, \"#{rules} not found\"}\n\n {_, lefts} ->\n passport |> ORM.update(%{rules: lefts})\n end\n end\n end\n\n def delete_passport(%Accounts.User{id: user_id}) do\n ORM.findby_delete!(UserPasport, ~m(user_id)a)\n end\n\n defp reject_invalid_rules(rules) when is_map(rules) do\n rules |> NestedFilter.drop_by_value([false]) |> reject_empty_values\n end\n\n defp reject_empty_values(map) when is_map(map) do\n for {k, v} <- map, v != %{}, into: %{}, do: {k, v}\n end\nend"},{"coverage":[null,null,null,361,null,null,null,null,null,null,null,361,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/router.ex","source":"defmodule GroupherServerWeb.Router do\n use GroupherServerWeb, :router\n\n pipeline :api do\n plug(:accepts, [\"json\"])\n plug(GroupherServerWeb.Context)\n end\n\n scope \"/graphiql\" do\n pipe_through(:api)\n\n forward(\n \"/\",\n Absinthe.Plug.GraphiQL,\n schema: GroupherServerWeb.Schema,\n pipeline: {ApolloTracing.Pipeline, :plug},\n interface: :playground,\n context: %{pubsub: GroupherServerWeb.Endpoint}\n )\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,0,null,0,null,null,0,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,9,null,4,null,null,1,null,null,4,null,null,0,null,null,null,null,null,null],"name":"lib/groupher_server/cms/utils/matcher.ex","source":"defmodule GroupherServer.CMS.Helper.MatcherOld do\n @moduledoc \"\"\"\n this module defined the matches and handy guard ...\n \"\"\"\n import Ecto.Query, warn: false\n\n alias GroupherServer.CMS.{\n Community,\n Post,\n Video,\n Repo,\n Job,\n PostFavorite,\n JobFavorite,\n PostStar,\n JobStar,\n PostComment,\n JobComment,\n Tag,\n Community,\n PostCommentLike,\n PostCommentDislike\n }\n\n @support_thread [:post, :video, :repo, :job]\n @support_react [:favorite, :star, :watch, :comment, :article_tag, :self]\n\n defguard valid_thread(thread) when thread in @support_thread\n defguard invalid_thread(thread) when thread not in @support_thread\n\n defguard valid_reaction(thread, react)\n when valid_thread(thread) and react in @support_react\n\n defguard invalid_reaction(thread, react)\n when invalid_thread(thread) and react not in @support_react\n\n defguard valid_feeling(feel) when feel in [:like, :dislike]\n\n # posts ...\n def match_action(:post, :self), do: {:ok, %{target: Post, reactor: Post, preload: :author}}\n\n def match_action(:post, :favorite),\n do: {:ok, %{target: Post, reactor: PostFavorite, preload: :user, preload_right: :post}}\n\n def match_action(:post, :star), do: {:ok, %{target: Post, reactor: PostStar, preload: :user}}\n def match_action(:post, :article_tag), do: {:ok, %{target: Post, reactor: Tag}}\n def match_action(:post, :community), do: {:ok, %{target: Post, reactor: Community}}\n\n def match_action(:post, :comment),\n do: {:ok, %{target: Post, reactor: PostComment, preload: :author}}\n\n def match_action(:post_comment, :like),\n do: {:ok, %{target: PostComment, reactor: PostCommentLike}}\n\n def match_action(:post_comment, :dislike),\n do: {:ok, %{target: PostComment, reactor: PostCommentDislike}}\n\n # videos ...\n def match_action(:video, :community), do: {:ok, %{target: Video, reactor: Community}}\n\n # repos ...\n def match_action(:repo, :community), do: {:ok, %{target: Repo, reactor: Community}}\n\n # jobs ...\n def match_action(:job, :self), do: {:ok, %{target: Job, reactor: Job, preload: :author}}\n def match_action(:job, :community), do: {:ok, %{target: Job, reactor: Community}}\n def match_action(:job, :star), do: {:ok, %{target: Job, reactor: JobStar, preload: :user}}\n def match_action(:job, :article_tag), do: {:ok, %{target: Job, reactor: Tag}}\n\n def match_action(:job, :comment),\n do: {:ok, %{target: Job, reactor: JobComment, preload: :author}}\n\n def match_action(:job, :favorite),\n do: {:ok, %{target: Job, reactor: JobFavorite, preload: :user}}\n\n def dynamic_where(thread, id) do\n case thread do\n :post ->\n {:ok, dynamic([p], p.post_id == ^id)}\n\n :post_comment ->\n {:ok, dynamic([p], p.post_comment_id == ^id)}\n\n :job ->\n {:ok, dynamic([p], p.job_id == ^id)}\n\n :job_comment ->\n {:ok, dynamic([p], p.job_comment_id == ^id)}\n\n _ ->\n {:error, 'where is not match'}\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,84,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,29,null,null],"name":"lib/groupher_server/delivery/mention.ex","source":"defmodule GroupherServer.Delivery.Mention do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id source_title source_id source_type source_preview)a\n @optional_fields ~w(parent_id parent_type read)a\n\n @type t :: %Mention{}\n schema \"mentions\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:parent_id, :string)\n field(:parent_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Mention{} = mention, attrs) do\n mention\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,10,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/cms/job_star.ex","source":"defmodule GroupherServer.CMS.JobStar do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Job\n\n @required_fields ~w(user_id job_id)a\n\n @type t :: %JobStar{}\n schema \"jobs_stars\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:job, Job, foreign_key: :job_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%JobStar{} = job_star, attrs) do\n # |> unique_constraint(:user_id, name: :favorites_user_id_article_id_index)\n job_star\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:user_id, name: :jobs_stars_user_id_job_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server_web.ex","source":"defmodule GroupherServerWeb do\n @moduledoc \"\"\"\n The entrypoint for defining your web interface, such\n as controllers, views, channels and so on.\n\n This can be used in your application as:\n\n use GroupherServerWeb, :controller\n use GroupherServerWeb, :view\n\n The definitions below will be executed for every view,\n controller, etc, so keep them short and clean, focused\n on imports, uses and aliases.\n\n Do NOT define functions inside the quoted expressions\n below. Instead, define any helper function in modules\n and import those modules here.\n \"\"\"\n\n def controller do\n quote do\n use Phoenix.Controller, namespace: GroupherServerWeb\n import Plug.Conn\n import GroupherServerWeb.Router.Helpers\n import GroupherServerWeb.Gettext\n end\n end\n\n def view do\n quote do\n use Phoenix.View,\n root: \"lib/groupher_server_web/templates\",\n namespace: GroupherServerWeb\n\n # Import convenience functions from controllers\n import Phoenix.Controller, only: [get_flash: 2, view_module: 1]\n\n import GroupherServerWeb.Router.Helpers\n import GroupherServerWeb.ErrorHelpers\n import GroupherServerWeb.Gettext\n end\n end\n\n def router do\n quote do\n use Phoenix.Router\n import Plug.Conn\n import Phoenix.Controller\n end\n end\n\n def channel do\n quote do\n use Phoenix.Channel\n import GroupherServerWeb.Gettext\n end\n end\n\n @doc \"\"\"\n When used, dispatch to the appropriate controller/view/etc.\n \"\"\"\n defmacro __using__(which) when is_atom(which) do\n apply(__MODULE__, which, [])\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,14,null,14,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,105,null,null,null,null,null,null,0,null,null,null,null,null,0,null,null,null,60,60,null,null,null,142,null,null,null,15,null,null,null,5,4,1,null,null,0,0,null,null,null,null,6,null,39,8,null,null,null,null,null,6,null,null,null,null,null,4,null,null,null,null,4,null],"name":"lib/helper/utils.ex","source":"defmodule Helper.Utils do\n @moduledoc \"\"\"\n unitil functions\n \"\"\"\n import Ecto.Query, warn: false\n import Helper.ErrorHandler\n import Helper.ErrorCode\n\n def get_config(section, key, app \\\\ :groupher_server) do\n app\n |> Application.get_env(section)\n # |> IO.inspect(label: \"debug ci\")\n |> case do\n nil -> \"\"\n config -> Keyword.get(config, key)\n end\n end\n\n @doc \"\"\"\n handle General {:ok, ..} or {:error, ..} return\n \"\"\"\n def done(nil, :boolean), do: {:ok, false}\n def done(_, :boolean), do: {:ok, true}\n def done(nil, err_msg), do: {:error, err_msg}\n def done({:ok, _}, with: result), do: {:ok, result}\n\n def done({:ok, %{id: id}}, :status), do: {:ok, %{done: true, id: id}}\n def done({:error, _}, :status), do: {:ok, %{done: false}}\n\n def done(nil, queryable, id), do: {:error, not_found_formater(queryable, id)}\n def done(result, _, _), do: {:ok, result}\n\n def done(nil), do: {:error, \"record not found.\"}\n\n # def done({:error, error}), do: {:error, error}\n def done(result), do: {:ok, result}\n\n @doc \"\"\"\n see: https://hexdocs.pm/absinthe/errors.html#content for error format\n \"\"\"\n def handle_absinthe_error(resolution, err_msg, code) when is_integer(code) do\n resolution\n |> Absinthe.Resolution.put_result({:error, message: err_msg, code: code})\n end\n\n def handle_absinthe_error(resolution, err_msg) when is_list(err_msg) do\n # %{resolution | value: [], errors: transform_errors(changeset)}\n resolution\n # |> Absinthe.Resolution.put_result({:error, err_msg})\n |> Absinthe.Resolution.put_result({:error, message: err_msg, code: ecode()})\n end\n\n def handle_absinthe_error(resolution, err_msg) when is_binary(err_msg) do\n resolution\n # |> Absinthe.Resolution.put_result({:error, err_msg})\n |> Absinthe.Resolution.put_result({:error, message: err_msg, code: ecode()})\n end\n\n def map_key_stringify(%{__struct__: _} = map) when is_map(map) do\n map = Map.from_struct(map)\n map |> Enum.reduce(%{}, fn {key, val}, acc -> Map.put(acc, to_string(key), val) end)\n end\n\n def map_key_stringify(map) when is_map(map) do\n map |> Enum.reduce(%{}, fn {key, val}, acc -> Map.put(acc, to_string(key), val) end)\n end\n\n def deep_merge(left, right) do\n Map.merge(left, right, &deep_resolve/3)\n end\n\n def tobe_integer(val) do\n if is_integer(val),\n do: val,\n else: val |> String.to_integer()\n end\n\n def repeat(times, [x]) when is_integer(x), do: to_string(for _ <- 1..times, do: x)\n def repeat(times, x), do: for(_ <- 1..times, do: x)\n\n def add(num, offset \\\\ 1) when is_integer(num) and is_integer(offset), do: num + offset\n\n def map_atom_value(attrs, :string) do\n results =\n Enum.map(attrs, fn {k, v} ->\n if is_atom(v) do\n {k, to_string(v)}\n else\n {k, v}\n end\n end)\n\n results |> Enum.into(%{})\n end\n\n # Key exists in both maps, and both values are maps as well.\n # These can be merged recursively.\n # defp deep_resolve(_key, left = %{},right = %{}) do\n defp deep_resolve(_key, %{} = left, %{} = right), do: deep_merge(left, right)\n\n # Key exists in both maps, but at least one of the values is\n # NOT a map. We fall back to standard merge behavior, preferring\n # the value on the right.\n defp deep_resolve(_key, _left, right), do: right\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,53,null,null,null,null,null,null,null,null,null,null,null,377,null,377,364,null,null,null,null,null],"name":"test/support/conn_case.ex","source":"defmodule GroupherServerWeb.ConnCase do\n @moduledoc \"\"\"\n This module defines the test case to be used by\n tests that require setting up a connection.\n\n Such tests rely on `Phoenix.ConnTest` and also\n import other functionality to make it easier\n to build common datastructures and query the data layer.\n\n Finally, if the test case interacts with the database,\n it cannot be async. For this reason, every test runs\n inside a transaction which is reset at the beginning\n of the test unless the test case is marked as async.\n \"\"\"\n\n use ExUnit.CaseTemplate\n\n using do\n quote do\n # Import conveniences for testing with connections\n use Phoenix.ConnTest\n import GroupherServerWeb.Router.Helpers\n\n # The default endpoint for testing\n @endpoint GroupherServerWeb.Endpoint\n end\n end\n\n setup tags do\n :ok = Ecto.Adapters.SQL.Sandbox.checkout(GroupherServer.Repo)\n\n unless tags[:async] do\n Ecto.Adapters.SQL.Sandbox.mode(GroupherServer.Repo, {:shared, self()})\n end\n\n {:ok, conn: Phoenix.ConnTest.build_conn()}\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,125,null,null,null,null,null,null,null,null,null,null,null,62,null,null],"name":"lib/groupher_server/cms/job_favorite.ex","source":"defmodule GroupherServer.CMS.JobFavorite do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Job\n\n @required_fields ~w(user_id job_id)a\n\n @type t :: %JobFavorite{}\n schema \"jobs_favorites\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:job, Job, foreign_key: :job_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%JobFavorite{} = job_favorite, attrs) do\n job_favorite\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> unique_constraint(:user_id, name: :jobs_favorites_user_id_job_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,983,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,6,null,null],"name":"lib/groupher_server/cms/job.ex","source":"defmodule GroupherServer.CMS.Job do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, JobFavorite, Tag}\n\n @required_fields ~w(title company company_logo location body digest length)a\n @optional_fields ~w(link_addr link_source min_education)a\n\n @type t :: %Job{}\n schema \"cms_jobs\" do\n field(:title, :string)\n field(:company, :string)\n field(:bonus, :string)\n field(:company_logo, :string)\n field(:location, :string)\n field(:desc, :string)\n field(:body, :string)\n belongs_to(:author, Author)\n field(:views, :integer, default: 0)\n field(:link_addr, :string)\n field(:link_source, :string)\n\n field(:min_salary, :integer, default: 0)\n field(:max_salary, :integer, default: 10_000_000)\n\n field(:min_experience, :integer, default: 1)\n field(:max_experience, :integer, default: 3)\n\n # college - bachelor - master - doctor\n field(:min_education, :string)\n\n field(:digest, :string)\n field(:length, :integer)\n\n # has_many(:comments, {\"jobs_comments\", JobComment})\n has_many(:favorites, {\"jobs_favorites\", JobFavorite})\n # has_many(:stars, {\"posts_stars\", PostStar})\n\n many_to_many(\n :tags,\n Tag,\n join_through: \"jobs_tags\",\n join_keys: [job_id: :id, tag_id: :id],\n # :delete_all will only remove data from the join source\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_jobs\",\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Job{} = job, attrs) do\n job\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,26,26,null,26,26,26,null,26,null,4,4,null,4,4,null,null,22,22,22,22,null,null,null,null,null,17,9,9,null,9,null,9,9,null,9,9,null,9,9,null,null,null,null,null,null,null,null,18,null,null,null,9,9,null,9,null,9,null,null,null,null,6,6,6,null,null,null,null,2,2,null,null,null,null,1,1,1,null,null,null,null,2,2,null,null,null,null,1,1,1,null,null,null],"name":"lib/groupher_server/statistics/delegates/throttle.ex","source":"defmodule GroupherServer.Statistics.Delegate.Throttle do\n import Ecto.Query, warn: false\n import ShortMaps\n\n alias GroupherServer.Accounts.User\n alias GroupherServer.Statistics.PublishThrottle\n alias Helper.{ORM}\n\n def log_publish_action(%User{id: user_id}) do\n cur_date = Timex.today() |> Date.to_iso8601()\n cur_datetime = DateTime.utc_now() |> DateTime.to_iso8601()\n\n last_publish_time = cur_datetime\n publish_hour = cur_datetime\n publish_date = cur_date\n\n case PublishThrottle |> ORM.find_by(~m(user_id)a) do\n {:ok, record} ->\n date_count = record.date_count + 1\n hour_count = record.hour_count + 1\n\n attrs = ~m(user_id publish_date publish_hour date_count hour_count last_publish_time)a\n record |> ORM.update(attrs)\n\n {:error, _} ->\n date_count = 1\n hour_count = 1\n attrs = ~m(user_id publish_date publish_hour date_count hour_count last_publish_time)a\n PublishThrottle |> ORM.create(attrs)\n end\n end\n\n # auto run check for same hour / day\n def load_throttle_record(%User{id: user_id}) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n date_count = if is_same_day?(record.publish_date), do: record.date_count, else: 0\n hour_count = if is_same_hour?(record.publish_hour), do: record.hour_count, else: 0\n\n case date_count !== 0 or hour_count !== 0 do\n true ->\n cur_date = Timex.today() |> Date.to_iso8601()\n cur_datetime = DateTime.utc_now() |> DateTime.to_iso8601()\n\n publish_hour = cur_datetime\n publish_date = cur_date\n\n attrs = ~m(publish_date publish_hour date_count hour_count)a\n record |> ORM.update(attrs)\n\n false ->\n {:ok, record}\n end\n end\n end\n\n defp is_same_day?(datetime) do\n datetime |> Timex.to_date() |> Timex.equal?(Timex.to_date(Timex.now()))\n end\n\n defp is_same_hour?(datetime) do\n {_date, {record_hour, _min, _sec}} = datetime |> Timex.to_erl()\n {_date, {cur_hour, _min, _sec}} = Timex.now() |> Timex.to_erl()\n\n same_hour? = record_hour == cur_hour\n\n is_same_day?(datetime) and same_hour?\n end\n\n # NOTE: the mock_xxx is only use for test\n def mock_throttle_attr(:last_publish_time, %User{id: user_id}, minutes: minutes) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n last_publish_time = Timex.shift(record.last_publish_time, minutes: minutes)\n record |> ORM.update(~m(last_publish_time)a)\n end\n end\n\n def mock_throttle_attr(:hour_count, %User{id: user_id}, count: hour_count) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n record |> ORM.update(~m(hour_count)a)\n end\n end\n\n def mock_throttle_attr(:publish_hour, %User{id: user_id}, hours: hours) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n publish_hour = Timex.shift(record.publish_hour, hours: hours)\n record |> ORM.update(~m(publish_hour)a)\n end\n end\n\n def mock_throttle_attr(:date_count, %User{id: user_id}, count: date_count) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n record |> ORM.update(~m(date_count)a)\n end\n end\n\n def mock_throttle_attr(:publish_date, %User{id: user_id}, days: days) do\n with {:ok, record} <- PublishThrottle |> ORM.find_by(~m(user_id)a) do\n publish_date = Timex.shift(record.publish_hour, days: days)\n record |> ORM.update(~m(publish_date)a)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,250,null,null,null,42,null,null],"name":"lib/groupher_server_web/middleware/authorize.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# ---\ndefmodule GroupherServerWeb.Middleware.Authorize do\n @behaviour Absinthe.Middleware\n import Helper.Utils, only: [handle_absinthe_error: 3]\n import Helper.ErrorCode\n\n def call(%{context: %{cur_user: _}} = resolution, _info), do: resolution\n\n def call(resolution, _) do\n resolution\n |> handle_absinthe_error(\"Authorize: need login\", ecode(:account_login))\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/helper/gql_schema_suite.ex","source":"defmodule Helper.GqlSchemaSuite do\n @moduledoc \"\"\"\n helper for reduce boilerplate import/use/alias in absinthe schema\n \"\"\"\n\n defmacro __using__(_opts) do\n quote do\n use Absinthe.Schema.Notation\n use Absinthe.Ecto, repo: GroupherServer.Repo\n\n alias GroupherServerWeb.Resolvers, as: R\n alias GroupherServerWeb.Middleware, as: M\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,37,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/sys_notification_mail.ex","source":"defmodule GroupherServer.Accounts.SysNotificationMail do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id source_id source_type)a\n @optional_fields ~w(source_preview read)a\n\n @type t :: %SysNotificationMail{}\n schema \"sys_notification_mails\" do\n belongs_to(:user, User)\n\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%SysNotificationMail{} = sys_notication_mail, attrs) do\n sys_notication_mail\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,0,null,null,0,0,null,null,null,0,null,null,null],"name":"lib/groupher_server_web/middleware/github_user.ex","source":"defmodule GroupherServerWeb.Middleware.GithubUser do\n @behaviour Absinthe.Middleware\n\n import Helper.Utils, only: [handle_absinthe_error: 2]\n alias Helper.OAuth2.Github\n\n def call(%{arguments: %{code: code}} = resolution, _) do\n # IO.inspect(access_token, label: \"GithubUser middleware token\")\n\n case Github.user_profile(code) do\n {:ok, user} ->\n # IO.inspect user,label: \"get ok\"\n arguments = resolution.arguments |> Map.merge(%{github_user: user})\n %{resolution | arguments: arguments}\n\n {:error, err_msg} ->\n resolution\n |> handle_absinthe_error(err_msg)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,2,null,null,null,4,null,null,null,2,null,null],"name":"lib/groupher_server_web/middleware/viewer_did_convert.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\n\ndefmodule GroupherServerWeb.Middleware.ViewerDidConvert do\n @behaviour Absinthe.Middleware\n\n def call(%{value: nil} = resolution, _) do\n %{resolution | value: false}\n end\n\n def call(%{value: []} = resolution, _) do\n %{resolution | value: false}\n end\n\n def call(%{value: [_]} = resolution, _) do\n %{resolution | value: true}\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/delivery/delivery_mutations.ex","source":"defmodule GroupherServerWeb.Schema.Delivery.Mutations do\n @moduledoc \"\"\"\n Delivery.Mutations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :delivery_mutations do\n field :mention_someone, :status do\n arg(:user_id, non_null(:id))\n\n arg(:source_id, non_null(:id))\n arg(:source_title, non_null(:string))\n arg(:source_type, non_null(:string))\n arg(:source_preview, non_null(:string))\n arg(:parent_id, :id)\n arg(:parent_type, :string)\n\n middleware(M.Authorize, :login)\n\n resolve(&R.Delivery.mention_someone/3)\n end\n\n field :publish_system_notification, :status do\n arg(:source_id, non_null(:id))\n arg(:source_title, non_null(:string))\n arg(:source_type, non_null(:string))\n arg(:source_preview, :string)\n\n middleware(M.Authorize, :login)\n # TODO: use delivery passport system instead of cms's\n middleware(M.Passport, claim: \"cms->system_notification.publish\")\n\n resolve(&R.Delivery.publish_system_notification/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null,null,5,null,null,null,null,7,null,null,7,null,null,null,1,null,1,1,null,null,null,null,null,null,null,7,7,7,5,5,null,5,null,null,null,null,1,1,null,null,null,null,null,null,1,null,null,null,null,1,null,1,null,null,null,1,null,null,null,null,1,null,1,null,null,null,null,null,null,null,null,null,1,null,null,null,9,9,null,null,null,null,3,3,null,null,null,null,null,null,null,6,6,6,null,6,null,null],"name":"lib/groupher_server/cms/delegates/community_curd.ex","source":"defmodule GroupherServer.CMS.Delegate.CommunityCURD do\n @moduledoc \"\"\"\n community curd\n \"\"\"\n import Ecto.Query, warn: false\n import GroupherServer.CMS.Helper.MatcherOld\n import Helper.Utils, only: [done: 1, map_atom_value: 2]\n import GroupherServer.CMS.Delegate.ArticleCURD, only: [ensure_author_exists: 1]\n import ShortMaps\n\n alias Helper.ORM\n alias Helper.QueryBuilder\n alias GroupherServer.{Accounts, Repo}\n\n alias GroupherServer.CMS.{\n Category,\n Community,\n CommunityEditor,\n CommunitySubscriber,\n Tag,\n Thread\n }\n\n @doc \"\"\"\n return paged community subscribers\n \"\"\"\n def community_members(:editors, %Community{id: id}, filters) do\n load_community_members(id, CommunityEditor, filters)\n end\n\n def community_members(:subscribers, %Community{id: id}, filters) do\n load_community_members(id, CommunitySubscriber, filters)\n end\n\n defp load_community_members(id, model, %{page: page, size: size} = filters) do\n model\n |> where([c], c.community_id == ^id)\n |> QueryBuilder.load_inner_users(filters)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n def update_editor(%Community{id: community_id}, title, %Accounts.User{id: user_id}) do\n clauses = ~m(user_id community_id)a\n\n with {:ok, _} <- CommunityEditor |> ORM.update_by(clauses, ~m(title)a) do\n Accounts.User |> ORM.find(user_id)\n end\n end\n\n @doc \"\"\"\n create a Tag base on type: post / tuts / videos ...\n \"\"\"\n def create_tag(thread, attrs, %Accounts.User{id: user_id}) when valid_thread(thread) do\n with {:ok, action} <- match_action(thread, :article_tag),\n {:ok, author} <- ensure_author_exists(%Accounts.User{id: user_id}),\n {:ok, _community} <- ORM.find(Community, attrs.community_id) do\n attrs = attrs |> Map.merge(%{author_id: author.id})\n attrs = attrs |> map_atom_value(:string)\n\n action.reactor |> ORM.create(attrs)\n end\n end\n\n def update_tag(%{id: _id} = attrs) do\n attrs = attrs |> map_atom_value(:string)\n Tag |> ORM.find_update(%{id: attrs.id, title: attrs.title, color: attrs.color})\n end\n\n @doc \"\"\"\n get tags belongs to a community / thread\n \"\"\"\n def get_tags(%Community{id: community_id}, thread) when not is_nil(community_id) do\n thread = to_string(thread)\n\n Tag\n |> join(:inner, [t], c in assoc(t, :community))\n |> where([t, c], c.id == ^community_id and t.thread == ^thread)\n |> distinct([t], t.title)\n |> Repo.all()\n |> done()\n end\n\n def get_tags(%Community{raw: community_raw}, thread) when not is_nil(community_raw) do\n thread = to_string(thread)\n\n Tag\n |> join(:inner, [t], c in assoc(t, :community))\n |> where([t, c], c.raw == ^community_raw and t.thread == ^thread)\n |> distinct([t], t.title)\n |> Repo.all()\n |> done()\n end\n\n @doc \"\"\"\n get all paged tags\n \"\"\"\n def get_tags(%{page: page, size: size} = filter) do\n Tag\n |> QueryBuilder.filter_pack(filter)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n def create_category(%Category{title: title, raw: raw}, %Accounts.User{id: user_id}) do\n with {:ok, author} <- ensure_author_exists(%Accounts.User{id: user_id}) do\n Category |> ORM.create(%{title: title, raw: raw, author_id: author.id})\n end\n end\n\n def update_category(~m(%Category id title)a) do\n with {:ok, category} <- ORM.find(Category, id) do\n category |> ORM.update(~m(title)a)\n end\n end\n\n @doc \"\"\"\n TODO: create_thread\n \"\"\"\n def create_thread(attrs) do\n raw = to_string(attrs.raw)\n title = attrs.title\n index = attrs |> Map.get(:index, 0)\n\n Thread |> ORM.create(~m(title raw index)a)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/statistics/statistics_mutations.ex","source":"defmodule GroupherServerWeb.Schema.Statistics.Mutations do\n @moduledoc \"\"\"\n Statistics mutations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :statistics_mutations do\n field :make_contrubute, :user_contribute do\n arg(:user_id, non_null(:id))\n\n resolve(&R.Statistics.make_contrubute/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,254,null,null,null,148,148,148,null,148,null,null,null,38,null,38,null,null,null,59,null,59,null,null,null,61,61,null,61,null,61,null,61,null,null,null,6,null,6,null,6,null,null,null,312,312,null,312,null,null,null],"name":"test/support/conn_simulator.ex","source":"defmodule GroupherServer.Test.ConnSimulator do\n @moduledoc \"\"\"\n mock user_conn, owner_conn, guest_conn\n \"\"\"\n import GroupherServer.Factory\n import Phoenix.ConnTest, only: [build_conn: 0]\n import Plug.Conn, only: [put_req_header: 3]\n\n alias GroupherServer.{Accounts, CMS}\n alias Helper.{Guardian, ORM}\n\n def simu_conn(:guest) do\n build_conn()\n end\n\n def simu_conn(:user) do\n user_attr = mock_attrs(:user)\n {:ok, user} = db_insert(:user, user_attr)\n token = gen_jwt_token(id: user.id)\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n def simu_conn(:owner, content) do\n token = gen_jwt_token(id: content.author.user.id)\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n def simu_conn(:user, %Accounts.User{} = user) do\n token = gen_jwt_token(id: user.id)\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n def simu_conn(:user, cms: passport_rules) do\n user_attr = mock_attrs(:user)\n {:ok, user} = db_insert(:user, user_attr)\n\n token = gen_jwt_token(id: user.id)\n\n {:ok, _passport} = CMS.stamp_passport(passport_rules, %Accounts.User{id: user.id})\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n def simu_conn(:user, %Accounts.User{} = user, cms: passport_rules) do\n token = gen_jwt_token(id: user.id)\n\n {:ok, _passport} = CMS.stamp_passport(passport_rules, %Accounts.User{id: user.id})\n\n build_conn() |> put_req_header(\"authorization\", token)\n end\n\n defp gen_jwt_token(clauses) do\n with {:ok, user} <- ORM.find_by(Accounts.User, clauses) do\n {:ok, token, _info} = Guardian.jwt_encode(user)\n\n \"Bearer #{token}\"\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,911,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,9,null,null,null,null,null],"name":"lib/groupher_server/cms/video.ex","source":"defmodule GroupherServer.CMS.Video do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, Tag}\n\n @required_fields ~w(title poster desc duration duration_sec source)a\n @optional_fields ~w(link original_author original_author_link publish_at pin markDelete)\n\n @type t :: %Video{}\n schema \"cms_videos\" do\n field(:title, :string)\n field(:poster, :string)\n field(:desc, :string)\n field(:duration, :string)\n field(:duration_sec, :integer)\n\n field(:source, :string)\n field(:link, :string)\n\n field(:original_author, :string)\n field(:original_author_link, :string)\n\n field(:views, :integer, default: 0)\n field(:pin, :boolean, default_value: false)\n field(:markDelete, :boolean, default_value: false)\n\n field(:publish_at, :utc_datetime)\n\n belongs_to(:author, Author)\n\n # has_many(:comments, {\"posts_comments\", PostComment})\n\n many_to_many(\n :tags,\n Tag,\n join_through: \"videos_tags\",\n join_keys: [video_id: :id, tag_id: :id],\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_videos\",\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Video{} = video, attrs) do\n video\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n\n # |> foreign_key_constraint(:posts_tags, name: :posts_tags_tag_id_fkey)\n # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,0,null,null,null,0,null,0,null],"name":"lib/groupher_server_web/middleware/put_root_source.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.PutRootSource do\n @behaviour Absinthe.Middleware\n\n # def call(%{source: %{id: id}} = resolution, _) do\n # arguments = resolution.arguments |> Map.merge(%{root_source_id: id})\n\n # %{resolution | arguments: arguments}\n # end\n\n def call(%{source: %{id: id}} = resolution, _) do\n arguments = resolution.arguments |> Map.merge(%{jj: id})\n\n %{resolution | arguments: arguments}\n # resolution\n end\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(resolution, _), do: resolution\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,911,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,9,null,null,null,null,null],"name":"lib/groupher_server/cms/repo.ex","source":"defmodule GroupherServer.CMS.Repo do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community, RepoBuilder, Tag}\n\n @required_fields ~w(repo_name desc readme language producer producer_link repo_link repo_star_count repo_fork_count repo_watch_count)a\n @optional_fields ~w(views pin markDelete last_fetch_time)\n\n @type t :: %Repo{}\n schema \"cms_repos\" do\n field(:repo_name, :string)\n field(:desc, :string)\n field(:readme, :string)\n field(:language, :string)\n belongs_to(:author, Author)\n\n field(:repo_link, :string)\n field(:producer, :string)\n field(:producer_link, :string)\n\n field(:repo_star_count, :integer)\n field(:repo_fork_count, :integer)\n field(:repo_watch_count, :integer)\n\n field(:views, :integer, default: 0)\n field(:pin, :boolean, default_value: false)\n field(:markDelete, :boolean, default_value: false)\n\n field(:last_fetch_time, :utc_datetime)\n # TODO: replace RepoBuilder with paged user map\n has_many(:builders, {\"repos_builders\", RepoBuilder})\n\n many_to_many(\n :tags,\n Tag,\n join_through: \"repos_tags\",\n join_keys: [repo_id: :id, tag_id: :id],\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_repos\",\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Repo{} = repo, attrs) do\n repo\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n\n # |> foreign_key_constraint(:posts_tags, name: :posts_tags_tag_id_fkey)\n # |> foreign_key_constraint(name: :posts_tags_tag_id_fkey)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,693,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,432,null,null],"name":"lib/groupher_server/accounts/achievement.ex","source":"defmodule GroupherServer.Accounts.Achievement do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id)a\n @optional_fields ~w(contents_stared_count contents_favorited_count contents_watched_count followers_count reputation)a\n\n @type t :: %Achievement{}\n schema \"user_achievements\" do\n belongs_to(:user, User)\n\n field(:contents_stared_count, :integer, default: 0)\n field(:contents_favorited_count, :integer, default: 0)\n field(:contents_watched_count, :integer, default: 0)\n field(:followers_count, :integer, default: 0)\n field(:reputation, :integer, default: 0)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Achievement{} = achievement, attrs) do\n achievement\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/repo.ex","source":"defmodule GroupherServer.Repo do\n import Helper.Utils, only: [get_config: 2]\n\n use Ecto.Repo, otp_app: :groupher_server\n use Scrivener, page_size: get_config(:general, :page_size)\n\n @dialyzer {:nowarn_function, rollback: 1}\n\n @doc \"\"\"\n Dynamically loads the repository url from the\n DATABASE_URL environment variable.\n \"\"\"\n def init(_, opts) do\n {:ok, Keyword.put(opts, :url, System.get_env(\"DATABASE_URL\"))}\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,18,null,null,null,null,null,null,null,null,null,null,null,null,null,6,null,null,null,null],"name":"lib/groupher_server/statistics/community_contribute.ex","source":"defmodule GroupherServer.Statistics.CommunityContribute do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS\n\n @type t :: %CommunityContribute{}\n schema \"community_contributes\" do\n field(:count, :integer)\n field(:date, :date)\n # field(:community_id, :id)\n belongs_to(:community, CMS.Community)\n\n timestamps()\n end\n\n @doc false\n def changeset(%CommunityContribute{} = community_contribute, attrs) do\n community_contribute\n |> cast(attrs, [:date, :count, :community_id])\n |> validate_required([:date, :count, :community_id])\n |> foreign_key_constraint(:community_id)\n\n # |> unique_constraint(:community_id, name: :communities_threads_community_id_thread_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,33,null,null,null,null,null,null,null,null,null,null,null,null,null,14,null,null],"name":"lib/groupher_server/cms/post_comment_dislike.ex","source":"defmodule GroupherServer.CMS.PostCommentDislike do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.PostComment\n\n @required_fields ~w(post_comment_id user_id)a\n\n @type t :: %PostCommentDislike{}\n schema \"posts_comments_dislikes\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:post_comment, PostComment, foreign_key: :post_comment_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostCommentDislike{} = post_comment_dislike, attrs) do\n post_comment_dislike\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:post_comment_id)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user_id, name: :posts_comments_dislikes_user_id_post_comment_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,4,4,4,null,4,4,4,null,4,null,4,null,null,null,3,null,2,2,null,null,null,null,null,26,null,26,null,null,26,null,null,null,26,26,null,null,null,60,60,null,60,null,null,null,8,null,null,8,null,8,null,8,null,null,null,60,null,60,null,60,null,60,null,60,null,60,null,null,null,60,null,60,null,null,null,null,null,null,null,10,null,null,null,9,null,null,null,7,null,null,null,26,26,26,null,26,null,null,null,null,7,null,7,null,null,7,null,null,null,null,null,null,null,19,19,null,19,null,null,null,1,null,null,null,18,null,null,19,null,null,null,35,35,null,null,null,33,33,null,null,null,26,26,null,null,null,null,null,null,null,68,null,null,null,null,null,null,94,null,94,null,null,41,null,null,null,null,null,null,16,16,null,null,null,null,null,null,null,null,null,null,null,null,null,19,19,null,19,19,null,null,19,19,null,null,null,4,null,4,null,4,4,null,null,null,null,null,null,null,null,null,null,null,41,null,null],"name":"lib/groupher_server/delivery/delegates/utils.ex","source":"defmodule GroupherServer.Delivery.Delegate.Utils do\n @moduledoc \"\"\"\n The Delivery context.\n \"\"\"\n # commons\n import Ecto.Query, warn: false\n import Helper.Utils\n import ShortMaps\n\n alias GroupherServer.Repo\n\n alias GroupherServer.Delivery.{Notification, SysNotification, Mention, Record}\n alias GroupherServer.Accounts.User\n alias Helper.ORM\n\n def mailbox_status(%User{} = user) do\n filter = %{page: 1, size: 1, read: false}\n {:ok, mention_mail} = fetch_mails(user, Mention, filter)\n {:ok, notification_mail} = fetch_mails(user, Notification, filter)\n\n mention_count = mention_mail.total_count\n notification_count = notification_mail.total_count\n total_count = mention_count + notification_count\n\n has_mail = total_count > 0\n\n result = ~m(has_mail total_count mention_count notification_count)a\n {:ok, result}\n end\n\n def fetch_record(%User{id: user_id}), do: Record |> ORM.find_by(user_id: user_id)\n\n def mark_read_all(%User{} = user, :mention), do: Mention |> do_mark_read_all(user)\n def mark_read_all(%User{} = user, :notification), do: Notification |> do_mark_read_all(user)\n\n @doc \"\"\"\n fetch mentions / notifications\n \"\"\"\n def fetch_messages(:sys_notification, %User{} = user, %{page: page, size: size}) do\n {:ok, last_fetch_time} = get_last_fetch_time(SysNotification, user)\n\n mails =\n SysNotification\n |> order_by(desc: :inserted_at)\n |> where([m], m.inserted_at > ^last_fetch_time)\n |> ORM.paginater(~m(page size)a)\n |> done()\n\n record_operation(user, SysNotification, mails)\n mails\n end\n\n def fetch_messages(%User{} = user, queryable, %{page: _page, size: _size, read: read} = filter) do\n mails = fetch_mails_and_delete(user, queryable, filter)\n record_operation(queryable, read, mails)\n\n mails\n end\n\n defp fetch_mails(user, queryable, %{page: page, size: size, read: read}) do\n {:ok, last_fetch_time} = get_last_fetch_time(queryable, read, user)\n\n queryable\n |> where([m], m.to_user_id == ^user.id)\n |> where([m], m.inserted_at > ^last_fetch_time)\n |> where([m], m.read == ^read)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n defp fetch_mails_and_delete(user, queryable, %{page: page, size: size, read: read}) do\n {:ok, last_fetch_time} = get_last_fetch_time(queryable, read, user)\n\n query =\n queryable\n |> where([m], m.to_user_id == ^user.id)\n |> where([m], m.inserted_at > ^last_fetch_time)\n |> where([m], m.read == ^read)\n\n mails =\n query\n |> order_by(desc: :inserted_at)\n |> ORM.paginater(~m(page size)a)\n |> done()\n\n delete_items(query, mails)\n\n mails\n end\n\n defp record_operation(Mention, _read, {:ok, %{entries: []}}), do: {:ok, \"\"}\n defp record_operation(Notification, _read, {:ok, %{entries: []}}), do: {:ok, \"\"}\n defp record_operation(_, SysNotification, {:ok, %{entries: []}}), do: {:ok, \"\"}\n\n defp record_operation(Mention, read, {:ok, %{entries: entries}}) do\n do_record_operation(:mentions_record, read, {:ok, %{entries: entries}})\n end\n\n defp record_operation(Notification, read, {:ok, %{entries: entries}}) do\n do_record_operation(:notifications_record, read, {:ok, %{entries: entries}})\n end\n\n defp record_operation(%User{} = user, SysNotification, {:ok, %{entries: entries}}) do\n do_record_operation(user, :sys_notifications_record, {:ok, %{entries: entries}})\n end\n\n defp get_record_lasttime(entries) do\n first_insert = entries |> List.first() |> Map.get(:inserted_at)\n last_insert = entries |> List.last() |> Map.get(:inserted_at)\n newest_insert = Enum.max([first_insert, last_insert])\n\n newest_insert |> Timex.to_datetime() |> to_string\n end\n\n # sys_notification\n defp do_record_operation(%User{id: user_id}, record_name, {:ok, %{entries: entries}}) do\n record_last_fetch_time = get_record_lasttime(entries)\n\n attrs =\n %{user_id: user_id} |> Map.put(record_name, %{last_fetch_time: record_last_fetch_time})\n\n Record |> ORM.upsert_by([user_id: user_id], attrs)\n end\n\n # last_fetch_read_time\n # > the last fetch time of mails that is read\n # last_fetch_unread_time\n # > the last fetch time of mails that is read\n defp do_record_operation(record_name, read, {:ok, %{entries: entries}}) do\n record_last_fetch_time = get_record_lasttime(entries)\n user_id = entries |> List.first() |> Map.get(:to_user_id)\n\n attrs =\n case read do\n true ->\n %{user_id: user_id}\n |> Map.put(record_name, %{last_fetch_read_time: record_last_fetch_time})\n\n false ->\n %{user_id: user_id}\n |> Map.put(record_name, %{last_fetch_unread_time: record_last_fetch_time})\n end\n\n Record |> ORM.upsert_by([user_id: user_id], attrs)\n end\n\n defp get_last_fetch_time(Mention, read, user) do\n timekey = get_record_lasttime_key(read)\n do_get_last_fetch_time(:mentions_record, user, timekey)\n end\n\n defp get_last_fetch_time(Notification, read, user) do\n timekey = get_record_lasttime_key(read)\n do_get_last_fetch_time(:notifications_record, user, timekey)\n end\n\n defp get_last_fetch_time(SysNotification, user) do\n timekey = get_record_lasttime_key(:sys_notifications_record)\n do_get_last_fetch_time(:sys_notifications_record, user, timekey)\n end\n\n defp get_record_lasttime_key(:sys_notifications_record) do\n \"last_fetch_time\"\n end\n\n defp get_record_lasttime_key(read) do\n case read do\n true -> \"last_fetch_read_time\"\n false -> \"last_fetch_unread_time\"\n end\n end\n\n defp do_get_last_fetch_time(record_key, %User{id: user_id}, timekey) do\n long_long_ago = Timex.shift(Timex.now(), years: -10)\n\n with {:ok, record} <- Record |> ORM.find_by(user_id: user_id) do\n record\n |> has_valid_value(record_key)\n |> case do\n false ->\n {:ok, long_long_ago}\n\n true ->\n record\n |> Map.get(record_key)\n |> Map.get(timekey, to_string(long_long_ago))\n |> NaiveDateTime.from_iso8601()\n end\n else\n {:error, _} ->\n {:ok, long_long_ago}\n end\n end\n\n defp delete_items(_queryable, {:ok, %{entries: []}}), do: {:ok, \"\"}\n\n defp delete_items(queryable, {:ok, %{entries: entries}}) do\n # delete_all only support queryable and where syntax\n # TODO: move logic to queue job\n\n first_id = entries |> List.first() |> Map.get(:id)\n last_id = entries |> List.last() |> Map.get(:id)\n\n min_id = Enum.min([first_id, last_id])\n max_id = Enum.max([first_id, last_id])\n\n queryable\n |> where([m], m.id >= ^min_id and m.id <= ^max_id)\n |> Repo.delete_all()\n end\n\n defp do_mark_read_all(queryable, %User{} = user) do\n query =\n queryable\n |> where([m], m.to_user_id == ^user.id)\n\n try do\n Repo.update_all(\n query,\n set: [read: true]\n )\n\n {:ok, %{status: true}}\n rescue\n _ -> {:error, %{status: false}}\n end\n end\n\n defp has_valid_value(map, key) when is_map(map) do\n Map.has_key?(map, key) and not is_nil(Map.get(map, key))\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,67,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,59,null,null],"name":"lib/groupher_server/cms/community_editor.ex","source":"defmodule GroupherServer.CMS.CommunityEditor do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias Helper.Certification\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.Community\n\n @required_fields ~w(user_id community_id title)a\n\n @type t :: %CommunityEditor{}\n\n schema \"communities_editors\" do\n field(:title, :string)\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:community, Community, foreign_key: :community_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%CommunityEditor{} = community_editor, attrs) do\n community_editor\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> validate_inclusion(:title, Certification.editor_titles(:cms))\n |> foreign_key_constraint(:community_id)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user_id, name: :communities_editors_user_id_community_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,2,null,1,1,null,null,null,null,null,null,null,7,3,3,null,null,null,null,3,null,null,null,null,null,null,10,null,null,null,6,3,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/accounts/delegates/customization.ex","source":"defmodule GroupherServer.Accounts.Delegate.Customization do\n import Ecto.Query, warn: false\n\n alias GroupherServer.Accounts\n alias GroupherServer.Accounts.{User, Customization}\n alias Helper.ORM\n # ...\n # TODO: Constants\n\n @doc \"\"\"\n add custom setting to user\n \"\"\"\n # for map_size\n # see https://stackoverflow.com/questions/33248816/pattern-match-function-against-empty-map\n def add_custom_setting(%User{} = _user, map) when map_size(map) == 0 do\n {:error, \"AccountCustomization: invalid option or not purchased\"}\n end\n\n def add_custom_setting(%User{} = user, map) when is_map(map) do\n valid? = map |> Map.keys() |> Enum.all?(&can_set?(user, &1, :boolean))\n\n case valid? do\n true ->\n attrs = Map.merge(%{user_id: user.id}, map)\n Customization |> ORM.upsert_by([user_id: user.id], attrs)\n\n false ->\n {:error, \"AccountCustomization: invalid option or not purchased\"}\n end\n end\n\n def add_custom_setting(%User{} = user, key, value \\\\ true) do\n with {:ok, key} <- can_set?(user, key) do\n attrs = Map.put(%{user_id: user.id}, key, value)\n Customization |> ORM.upsert_by([user_id: user.id], attrs)\n end\n end\n\n defp can_set?(%User{} = user, key, :boolean) do\n case can_set?(%User{} = user, key) do\n {:ok, _} -> true\n {:error, _} -> false\n end\n end\n\n def can_set?(%User{} = user, key) do\n cond do\n key in valid_custom_items(:free) ->\n {:ok, key}\n\n key in valid_custom_items(:advance) ->\n Accounts.has_purchased?(user, key)\n\n true ->\n {:error, \"AccountCustomization: invalid option\"}\n end\n end\n\n @doc \"\"\"\n # theme -- user can set a default theme\n # sidebar_layout -- user can arrange subscribed community index\n \"\"\"\n def valid_custom_items(:free) do\n [:theme, :sidebar_layout]\n end\n\n @doc \"\"\"\n # :brainwash_free -- ads free\n # ::community_chart -- user can access comunity charts\n \"\"\"\n def valid_custom_items(:advance) do\n # NOTE: :brainwash_free aka. \"ads_free\"\n # use brainwash to avoid brower-block-plugins\n [:brainwash_free, :community_chart]\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,7,null,0,null,null,4,null,4,4,null,null,0,0,null,null,4,null,null],"name":"lib/groupher_server_web/middleware/statistics/make_contribute.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.Statistics.MakeContribute do\n @behaviour Absinthe.Middleware\n # google: must appear in the GROUP BY clause or be used in an aggregate function\n alias GroupherServer.Statistics\n alias GroupherServer.CMS.Community\n alias GroupherServer.Accounts.User\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(%{value: nil, errors: _} = resolution, _), do: resolution\n\n def call(%{value: value, context: %{cur_user: cur_user}} = resolution, for: threads) do\n case is_list(threads) do\n true ->\n if :user in threads, do: Statistics.make_contribute(%User{id: cur_user.id})\n if :community in threads, do: Statistics.make_contribute(%Community{id: value.id})\n\n false ->\n if :user == threads, do: Statistics.make_contribute(%User{id: cur_user.id})\n if :community == threads, do: Statistics.make_contribute(%Community{id: value.id})\n end\n\n resolution\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,172,312,null,312,2,null,310,null,null,null,null,null,null,0,null,null,null,279,null,null,null,null,null,null,null,null,139,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,0,null,0,null,null,null,0,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/helper/nested_filter.ex","source":"defmodule Helper.NestedFilter do\n @moduledoc \"\"\"\n Documentation for NestedFilter.\n see: https://github.com/treble37/nested_filter\n \"\"\"\n @type key :: any\n @type val :: any\n @type keys_to_select :: list\n @type predicate :: (key, val -> boolean)\n\n # @spec drop_by(struct, predicate) :: struct\n def drop_by(%_{} = struct, _), do: struct\n\n # @spec drop_by(map, predicate) :: map\n def drop_by(map, predicate) when is_map(map) do\n map\n |> Enum.reduce(%{}, fn {key, val}, acc ->\n cleaned_val = drop_by(val, predicate)\n\n if predicate.(key, cleaned_val) do\n acc\n else\n Map.put(acc, key, cleaned_val)\n end\n end)\n end\n\n # @spec drop_by(list, predicate) :: list\n def drop_by(list, predicate) when is_list(list) do\n Enum.map(list, &drop_by(&1, predicate))\n end\n\n def drop_by(elem, _) do\n elem\n end\n\n @doc \"\"\"\n Take a (nested) map and filter out any keys with specified values in the\n values_to_reject list.\n \"\"\"\n # @spec drop_by_value(%{any => any}, [any]) :: %{any => any}\n def drop_by_value(map, values_to_reject) when is_map(map) do\n drop_by(map, fn _, val -> val in values_to_reject end)\n end\n\n @doc \"\"\"\n Take a (nested) map and filter out any values with specified keys in the\n keys_to_reject list.\n \"\"\"\n # @spec drop_by_key(%{any => any}, [any]) :: %{any => any}\n def drop_by_key(map, keys_to_reject) when is_map(map) do\n drop_by(map, fn key, _ -> key in keys_to_reject end)\n end\n\n # @spec take_by(map, keys_to_select) :: map\n def take_by(map, keys_to_select) when is_map(map) do\n map\n |> Enum.reduce(%{}, fn {_key, val}, acc ->\n Map.merge(acc, take_by(val, keys_to_select))\n end)\n |> Map.merge(Map.take(map, keys_to_select))\n end\n\n def take_by(_elem, _) do\n %{}\n end\n\n @doc \"\"\"\n Take a (nested) map and keep any values with specified keys in the\n keys_to_select list.\n \"\"\"\n # @spec take_by_key(%{any => any}, [any]) :: %{any => any}\n def take_by_key(map, keys_to_select) when is_map(map) do\n Map.merge(take_by(map, keys_to_select), Map.take(map, keys_to_select))\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,1,1,null,1,null,null,null,1,null,null],"name":"lib/groupher_server_web/resolvers/delivery_resolver.ex","source":"defmodule GroupherServerWeb.Resolvers.Delivery do\n @moduledoc false\n\n alias GroupherServer.Delivery\n alias GroupherServer.Accounts.User\n # alias Helper.ORM\n\n def mention_someone(_root, args, %{context: %{cur_user: cur_user}}) do\n from_user_id = cur_user.id\n to_user_id = args.user_id\n\n Delivery.mention_someone(%User{id: from_user_id}, %User{id: to_user_id}, args)\n end\n\n def publish_system_notification(_root, args, %{context: %{cur_user: _}}) do\n Delivery.publish_system_notification(args)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,96,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,47,null,null],"name":"lib/groupher_server/statistics/publish_throttle.ex","source":"defmodule GroupherServer.Statistics.PublishThrottle do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n @optional_fields ~w(user_id publish_hour publish_date hour_count date_count last_publish_time)a\n @required_fields ~w(user_id)a\n\n @type t :: %PublishThrottle{}\n schema \"publish_throttles\" do\n field(:publish_hour, :utc_datetime)\n field(:publish_date, :date)\n field(:hour_count, :integer)\n field(:date_count, :integer)\n belongs_to(:user, Accounts.User)\n\n field(:last_publish_time, :utc_datetime)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PublishThrottle{} = publish_throttle, attrs) do\n publish_throttle\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user, name: :publish_throttles_user_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,45,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/mention_mail.ex","source":"defmodule GroupherServer.Accounts.MentionMail do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id source_id source_type source_preview)a\n @optional_fields ~w(parent_id parent_type read)a\n\n @type t :: %MentionMail{}\n schema \"mention_mails\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:parent_id, :string)\n field(:parent_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%MentionMail{} = mention, attrs) do\n mention\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,139,139,139,null,null,139,null,null,139,139,null,null,139,null,null,null,139,null,null,null,null,null,null,null,null,139,null,139,null,139,null,null,null,null,null,null,null,null,5,5,5,null,null,5,null,null,5,5,null,null,5,null,null,null,5,null,null,null,null,null,null,null,null,5,5,null,5,null,5,null,null,null,null,5,null,null,null,null,null,3,null,null,null,2,null,null,null,0,null,null,null,0,null,null],"name":"lib/groupher_server/cms/delegates/article_reaction.ex","source":"defmodule GroupherServer.CMS.Delegate.ArticleReaction do\n @moduledoc \"\"\"\n reaction[favorite, star, watch ...] on article [post, job, video...]\n \"\"\"\n import Helper.Utils, only: [done: 1, done: 2]\n import GroupherServer.CMS.Helper.MatcherOld\n import Ecto.Query, warn: false\n import Helper.ErrorCode\n\n alias Helper.ORM\n alias GroupherServer.{Accounts, Repo}\n\n alias Accounts.User\n alias Ecto.Multi\n\n @doc \"\"\"\n favorite / star / watch CMS contents like post / tuts / video ...\n \"\"\"\n # when valid_reaction(thread, react) do\n def reaction(thread, react, content_id, %User{id: user_id}) do\n with {:ok, action} <- match_action(thread, react),\n {:ok, content} <- ORM.find(action.target, content_id, preload: [author: :user]),\n {:ok, user} <- ORM.find(Accounts.User, user_id) do\n Multi.new()\n |> Multi.run(:create_reaction_record, fn _ ->\n create_reaction_record(action, user, thread, content)\n end)\n |> Multi.run(:add_achievement, fn _ ->\n achiever_id = content.author.user_id\n Accounts.achieve(%User{id: achiever_id}, :add, react)\n end)\n |> Repo.transaction()\n |> reaction_result()\n end\n end\n\n defp reaction_result({:ok, %{create_reaction_record: result}}), do: result |> done()\n\n defp reaction_result({:error, :create_reaction_record, _result, _steps}),\n do: {:error, [message: \"create reaction fails\", code: ecode(:react_fails)]}\n\n defp reaction_result({:error, :add_achievement, _result, _steps}),\n do: {:error, [message: \"achieve fails\", code: ecode(:react_fails)]}\n\n defp create_reaction_record(action, %User{id: user_id}, thread, content) do\n attrs = %{} |> Map.put(\"user_id\", user_id) |> Map.put(\"#{thread}_id\", content.id)\n\n action.reactor\n |> ORM.create(attrs)\n |> done(with: content)\n end\n\n # ------\n @doc \"\"\"\n unfavorite / unstar / unwatch CMS contents like post / tuts / video ...\n \"\"\"\n # when valid_reaction(thread, react) do\n def undo_reaction(thread, react, content_id, %User{id: user_id}) do\n with {:ok, action} <- match_action(thread, react),\n {:ok, content} <- ORM.find(action.target, content_id, preload: [author: :user]),\n {:ok, user} <- ORM.find(Accounts.User, user_id) do\n Multi.new()\n |> Multi.run(:delete_reaction_record, fn _ ->\n delete_reaction_record(action, user, thread, content)\n end)\n |> Multi.run(:minus_achievement, fn _ ->\n achiever_id = content.author.user_id\n Accounts.achieve(%User{id: achiever_id}, :minus, react)\n end)\n |> Repo.transaction()\n |> undo_reaction_result()\n end\n end\n\n defp undo_reaction_result({:ok, %{delete_reaction_record: result}}), do: result |> done()\n\n defp undo_reaction_result({:error, :delete_reaction_record, _result, _steps}),\n do: {:error, [message: \"delete reaction fails\", code: ecode(:react_fails)]}\n\n defp undo_reaction_result({:error, :minus_achievement, _result, _steps}),\n do: {:error, [message: \"achieve fails\", code: ecode(:react_fails)]}\n\n defp delete_reaction_record(action, %User{id: user_id}, thread, content) do\n user_where = dynamic([u], u.user_id == ^user_id)\n reaction_where = dynamic_reaction_where(thread, content.id, user_where)\n\n query = from(f in action.reactor, where: ^reaction_where)\n\n case Repo.one(query) do\n nil ->\n {:error, \"record not found\"}\n\n record ->\n Repo.delete(record)\n {:ok, content}\n end\n end\n\n defp dynamic_reaction_where(:post, id, user_where) do\n dynamic([p], p.post_id == ^id and ^user_where)\n end\n\n defp dynamic_reaction_where(:job, id, user_where) do\n dynamic([p], p.job_id == ^id and ^user_where)\n end\n\n defp dynamic_reaction_where(:video, id, user_where) do\n dynamic([p], p.video_id == ^id and ^user_where)\n end\n\n defp dynamic_reaction_where(:repo, id, user_where) do\n dynamic([p], p.repo_id == ^id and ^user_where)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,11,null,null,null,null,null,27,null,27,27,null,null,null,0,null,null,null,null,null,null,22,22,22,22,null,null,null,20,null,null,null,2,null,null,null,null,null,0,null,null,null,20,20,null,null,null,null,20,null,null,20,null,20,20,null,null,null,22,null,22,null,7,null,null,15,null,null,null,null,20,null,null,5,null,null,15,null,null,20,null,7,7,null,null,13,null,null,null,null,null,7,null,null,null,15,null,null,null,44,0,44,null,null,null,14,30,null,null,35,null,null],"name":"lib/groupher_server_web/middleware/passport_loader.ex","source":"defmodule GroupherServerWeb.Middleware.PassportLoader do\n @behaviour Absinthe.Middleware\n import GroupherServer.CMS.Helper.MatcherOld\n import Helper.Utils\n import Helper.ErrorCode\n\n import ShortMaps\n\n alias GroupherServer.CMS\n alias Helper.ORM\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(\n %{context: %{cur_user: _}, arguments: ~m(community_id)a} = resolution,\n source: :community\n ) do\n case ORM.find(CMS.Community, community_id) do\n {:ok, community} ->\n arguments = resolution.arguments |> Map.merge(%{passport_communities: [community]})\n %{resolution | arguments: arguments}\n\n {:error, err_msg} ->\n resolution\n |> handle_absinthe_error(err_msg, ecode(:passport))\n end\n end\n\n # def call(%{context: %{cur_user: cur_user}, arguments: %{id: id}} = resolution, [source: .., base: ..]) do\n # Loader 应该使用 Map 作为参数,以方便模式匹配\n def call(%{context: %{cur_user: _}, arguments: %{id: id}} = resolution, args) do\n with {:ok, thread, react} <- parse_source(args, resolution),\n {:ok, action} <- match_action(thread, react),\n {:ok, preload} <- parse_preload(action, args),\n {:ok, content} <- ORM.find(action.reactor, id, preload: preload) do\n resolution\n |> load_owner_info(react, content)\n |> load_source(content)\n |> load_community_info(content, args)\n else\n {:error, err_msg} ->\n resolution\n |> handle_absinthe_error(err_msg, ecode(:passport))\n end\n end\n\n def call(resolution, _) do\n # TODO communiy in args\n resolution\n end\n\n def load_source(resolution, content) do\n arguments = resolution.arguments |> Map.merge(%{passport_source: content})\n %{resolution | arguments: arguments}\n end\n\n # 取得 content 里面的 conmunities 字段\n def load_community_info(resolution, content, args) do\n communities = content |> Map.get(parse_base(args))\n\n # check if communities is a List\n communities = if is_list(communities), do: communities, else: [communities]\n\n arguments = resolution.arguments |> Map.merge(%{passport_communities: communities})\n %{resolution | arguments: arguments}\n end\n\n defp parse_preload(action, args) do\n {:ok, _, react} = parse_source(args)\n\n case react == :comment do\n true ->\n {:ok, action.preload}\n\n false ->\n {:ok, [action.preload, parse_base(args)]}\n end\n end\n\n def load_owner_info(%{context: %{cur_user: cur_user}} = resolution, react, content) do\n content_author_id =\n cond do\n react == :comment ->\n content.author.id\n\n true ->\n content.author.user_id\n end\n\n case content_author_id == cur_user.id do\n true ->\n arguments = resolution.arguments |> Map.merge(%{passport_is_owner: true})\n %{resolution | arguments: arguments}\n\n _ ->\n resolution\n end\n end\n\n # typical usage is delete_comment, should load conent by thread\n defp parse_source([source: [:arg_thread, react]], %{arguments: %{thread: thread}}) do\n parse_source(source: [thread, react])\n end\n\n defp parse_source(args, _resolution) do\n parse_source(args)\n end\n\n defp parse_source(args) do\n case Keyword.has_key?(args, :source) do\n false -> {:error, \"Invalid.option: #{args}\"}\n true -> args |> Keyword.get(:source) |> match_source\n end\n end\n\n defp match_source([thread, react]), do: {:ok, thread, react}\n defp match_source(thread), do: {:ok, thread, :self}\n\n defp parse_base(args) do\n Keyword.get(args, :base) || :communities\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,1,null,null,null,null,null,null,null,null,null,null,1,1,null,null,null,null,null,0,null,null,null],"name":"lib/groupher_server/application.ex","source":"defmodule GroupherServer.Application do\n use Application\n\n # See https://hexdocs.pm/elixir/Application.html\n # for more information on OTP Applications\n def start(_type, _args) do\n import Supervisor.Spec\n\n # Define workers and child supervisors to be supervised\n children = [\n # Start the Ecto repository\n supervisor(GroupherServer.Repo, []),\n # Start the endpoint when the application starts\n supervisor(GroupherServerWeb.Endpoint, [])\n # Start your own worker by calling: GroupherServer.Worker.start_link(arg1, arg2, arg3)\n # worker(GroupherServer.Worker, [arg1, arg2, arg3]),\n ]\n\n # See https://hexdocs.pm/elixir/Supervisor.html\n # for other strategies and supported options\n opts = [strategy: :one_for_one, name: GroupherServer.Supervisor]\n Supervisor.start_link(children, opts)\n end\n\n # Tell Phoenix to update the endpoint configuration\n # whenever the application is updated.\n def config_change(changed, _new, removed) do\n GroupherServerWeb.Endpoint.config_change(changed, removed)\n :ok\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,119,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,47,null,null],"name":"lib/groupher_server/cms/job_comment.ex","source":"defmodule GroupherServer.CMS.JobComment do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n alias GroupherServer.CMS.{Job, JobCommentReply}\n\n @required_fields ~w(body author_id job_id floor)a\n @optional_fields ~w(reply_id)a\n\n @type t :: %JobComment{}\n schema \"jobs_comments\" do\n field(:body, :string)\n field(:floor, :integer)\n belongs_to(:author, Accounts.User, foreign_key: :author_id)\n belongs_to(:job, Job, foreign_key: :job_id)\n belongs_to(:reply_to, JobComment, foreign_key: :reply_id)\n # belongs_to(:reply_to, JobComment, foreign_key: :job_id)\n has_many(:replies, {\"jobs_comments_replies\", JobCommentReply})\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%JobComment{} = job_comment, attrs) do\n job_comment\n |> cast(attrs, @required_fields ++ @optional_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:job_id)\n |> foreign_key_constraint(:author_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,15,null,null,null,null,null,null,null,null,null,null,null,null,7,null,null],"name":"lib/groupher_server/cms/post_comment_reply.ex","source":"defmodule GroupherServer.CMS.PostCommentReply do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.PostComment\n\n @required_fields ~w(post_comment_id reply_id)a\n\n @type t :: %PostCommentReply{}\n schema \"posts_comments_replies\" do\n belongs_to(:post_comment, PostComment, foreign_key: :post_comment_id)\n belongs_to(:reply, PostComment, foreign_key: :reply_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostCommentReply{} = post_comment_reply, attrs) do\n post_comment_reply\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:post_comment_id)\n |> foreign_key_constraint(:reply_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/community.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Community do\n @moduledoc \"\"\"\n CMS mations for community\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_mutation_community do\n @desc \"create a global community\"\n field :create_community, :community do\n arg(:title, non_null(:string))\n arg(:desc, non_null(:string))\n arg(:raw, non_null(:string))\n arg(:logo, non_null(:string))\n # arg(:category, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->community.create\")\n\n resolve(&R.CMS.create_community/3)\n # middleware(M.Statistics.MakeContribute, for: :user)\n middleware(M.Statistics.MakeContribute, for: [:user, :community])\n end\n\n @desc \"update a community\"\n field :update_community, :community do\n arg(:id, non_null(:id))\n arg(:title, :string)\n arg(:desc, :string)\n arg(:raw, :string)\n arg(:logo, :string)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->community.update\")\n\n resolve(&R.CMS.update_community/3)\n middleware(M.Statistics.MakeContribute, for: [:user, :community])\n end\n\n @desc \"delete a global community\"\n field :delete_community, :community do\n arg(:id, non_null(:id))\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->community.delete\")\n\n resolve(&R.CMS.delete_community/3)\n end\n\n @desc \"create category\"\n field :create_category, :category do\n arg(:title, non_null(:string))\n arg(:raw, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.create\")\n\n resolve(&R.CMS.create_category/3)\n end\n\n @desc \"delete category\"\n field :delete_category, :category do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.delete\")\n\n resolve(&R.CMS.delete_category/3)\n end\n\n @desc \"update category\"\n field :update_category, :category do\n arg(:id, non_null(:id))\n arg(:title, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->category.update\")\n\n resolve(&R.CMS.update_category/3)\n end\n\n @desc \"create independent thread\"\n field :create_thread, :thread do\n arg(:title, non_null(:string))\n arg(:raw, non_null(:thread))\n arg(:index, :integer, default_value: 0)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->thread.create\")\n\n resolve(&R.CMS.create_thread/3)\n end\n\n @desc \"add a editor for a community\"\n field :set_editor, :user do\n arg(:community_id, non_null(:id))\n arg(:user_id, non_null(:id))\n arg(:title, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->editor.set\")\n\n resolve(&R.CMS.set_editor/3)\n end\n\n @desc \"unset a editor from a community, the user's passport also deleted\"\n field :unset_editor, :user do\n arg(:community_id, non_null(:id))\n arg(:user_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->editor.unset\")\n\n resolve(&R.CMS.unset_editor/3)\n end\n\n # TODO: remove, should remove both editor and cms->passport\n @desc \"update cms editor's title, passport is not effected\"\n field :update_cms_editor, :user do\n arg(:community_id, non_null(:id))\n arg(:user_id, non_null(:id))\n arg(:title, non_null(:string))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->editor.update\")\n\n resolve(&R.CMS.update_editor/3)\n end\n\n @desc \"create a tag\"\n field :create_tag, :article_tag do\n arg(:title, non_null(:string))\n arg(:color, non_null(:rainbow_color_enum))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.create\")\n\n resolve(&R.CMS.create_tag/3)\n end\n\n @desc \"update a tag\"\n field :update_tag, :article_tag do\n arg(:id, non_null(:id))\n arg(:title, non_null(:string))\n # arg(:color, non_null(:rainbow_color_enum))\n arg(:color, non_null(:rainbow_color_enum))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.update\")\n\n resolve(&R.CMS.update_tag/3)\n end\n\n @desc \"delete a tag by thread\"\n field :delete_tag, :article_tag do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :community)\n middleware(M.Passport, claim: \"cms->c?->t?.tag.delete\")\n\n resolve(&R.CMS.delete_tag/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,6,null,6,null,null,2,null,null,0,null,null],"name":"lib/groupher_server_web/middleware/put_current_user.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.PutCurrentUser do\n @behaviour Absinthe.Middleware\n\n def call(%{context: %{cur_user: cur_user}} = resolution, _) do\n arguments = resolution.arguments |> Map.merge(%{cur_user: cur_user})\n\n %{resolution | arguments: arguments}\n end\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(resolution, _) do\n resolution\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,7,null,7,1,null,null,6,null,null,null,null,12,null,12,2,null,null,10,null,null,null,null,null,null,null,4,null,null,4,null,4,null,null,4,null,null,null,null,null,1,null,null,null,null,null,null,0,null,null,null,1,null,null,1,null,1,null,1,null,null,null,4,4,4,null,4,null,4,null,null,null,null,5,4,null,null,null,null,null,null,null,null,null,null,null,null,0,null,0,null,null,0,0,0,null,0,null,0,0,null,0,null,null,0,null,null,null,null,4,4,null,null,null,null,1,1,null,null,null,null,2,2,null,null,null,3,null,null,null,null,null,null,3,null,null],"name":"lib/groupher_server/statistics/delegates/contribute.ex","source":"defmodule GroupherServer.Statistics.Delegate.Contribute do\n import Ecto.Query, warn: false\n import Helper.Utils\n import ShortMaps\n\n alias GroupherServer.Repo\n alias GroupherServer.Accounts.User\n alias GroupherServer.CMS.Community\n alias GroupherServer.Statistics.{UserContribute, CommunityContribute}\n alias Helper.{ORM, QueryBuilder}\n\n @community_contribute_days get_config(:general, :community_contribute_days)\n @user_contribute_months get_config(:general, :user_contribute_months)\n\n def make_contribute(%Community{id: id}) do\n today = Timex.today() |> Date.to_iso8601()\n\n with {:ok, contribute} <- ORM.find_by(CommunityContribute, community_id: id, date: today) do\n contribute |> inc_contribute_count(:community) |> done\n else\n {:error, _} ->\n CommunityContribute |> ORM.create(%{community_id: id, date: today, count: 1})\n end\n end\n\n def make_contribute(%User{id: id}) do\n today = Timex.today() |> Date.to_iso8601()\n\n with {:ok, contribute} <- ORM.find_by(UserContribute, user_id: id, date: today) do\n contribute |> inc_contribute_count(:user) |> done\n else\n {:error, _} ->\n UserContribute |> ORM.create(%{user_id: id, date: today, count: 1})\n end\n end\n\n @doc \"\"\"\n Returns the list of user_contribute by latest 6 months.\n \"\"\"\n def list_contributes(%User{id: id}) do\n user_id = tobe_integer(id)\n\n \"user_contributes\"\n |> where([c], c.user_id == ^user_id)\n |> QueryBuilder.recent_inserted(months: @user_contribute_months)\n |> select([c], %{date: c.date, count: c.count})\n |> Repo.all()\n |> to_contrubutes_map()\n |> done\n end\n\n def list_contributes(%Community{id: id}) do\n %Community{id: id}\n |> get_contributes()\n |> done\n end\n\n def list_contributes_digest(%Community{id: id}) do\n %Community{id: id}\n |> get_contributes()\n |> to_counts_digest(days: @community_contribute_days)\n |> done\n end\n\n defp get_contributes(%Community{id: id}) do\n community_id = tobe_integer(id)\n\n \"community_contributes\"\n |> where([c], c.community_id == ^community_id)\n |> QueryBuilder.recent_inserted(days: @community_contribute_days)\n |> select([c], %{date: c.date, count: c.count})\n |> Repo.all()\n |> to_contribute_records()\n end\n\n defp to_contrubutes_map(data) do\n end_date = Timex.today()\n start_date = Timex.shift(Timex.today(), months: -6)\n total_count = Enum.reduce(data, 0, &(&1.count + &2))\n\n records = to_contribute_records(data)\n\n ~m(start_date end_date total_count records)a\n end\n\n defp to_contribute_records(data) do\n data\n |> Enum.map(fn %{count: count, date: date} ->\n %{\n date: convert_date(date),\n count: count\n }\n end)\n end\n\n # 返回 count 数组,方便前端绘图\n # example:\n # from: [0,0,0,0,0,0]\n # to: [0,30,3,8,0,0]\n # 如果 7 天都有 count, 不用计算直接 map 返回\n defp to_counts_digest(record, days: count) do\n case length(record) == @community_contribute_days + 1 do\n true ->\n Enum.map(record, & &1.count)\n\n false ->\n today = Timex.today() |> Date.to_erl()\n return_count = abs(count) + 1\n enmpty_tuple = return_count |> repeat(0) |> List.to_tuple()\n\n results =\n Enum.reduce(record, enmpty_tuple, fn record, acc ->\n diff = Timex.diff(Timex.to_date(record.date), today, :days)\n index = diff + abs(count)\n\n put_elem(acc, index, record.count)\n end)\n\n results |> Tuple.to_list()\n end\n end\n\n defp convert_date(date) do\n {:ok, edate} = Date.from_erl(date)\n edate\n end\n\n defp inc_contribute_count(contribute, :community) do\n CommunityContribute\n |> where([c], c.community_id == ^contribute.community_id and c.date == ^contribute.date)\n |> do_inc_count(contribute)\n end\n\n defp inc_contribute_count(contribute, :user) do\n UserContribute\n |> where([c], c.user_id == ^contribute.user_id and c.date == ^contribute.date)\n |> do_inc_count(contribute)\n end\n\n defp do_inc_count(query, contribute, count \\\\ 1) do\n {1, [result]} =\n Repo.update_all(\n query,\n [inc: [count: count]],\n returning: [:count]\n )\n\n put_in(contribute.count, result.count)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,361,null,null,null,null,2,null,null,null,null,null,null,2,null,null,null,null,null,1,null,null,null,null,null,1,null,null,null,2,null,null,null,1,null,null,null,1,null,null,5,null,null,null,null,2,null,null],"name":"lib/groupher_server/accounts/utils/loader.ex","source":"defmodule GroupherServer.Accounts.Helper.Loader do\n @moduledoc \"\"\"\n dataloader for accounts\n \"\"\"\n import Ecto.Query, warn: false\n\n alias Helper.QueryBuilder\n alias GroupherServer.{Accounts, CMS, Repo}\n\n alias Accounts.{UserFollower, UserFollowing}\n\n def data, do: Dataloader.Ecto.new(Repo, query: &query/2)\n\n def query({\"communities_subscribers\", CMS.CommunitySubscriber}, %{count: _}) do\n CMS.CommunitySubscriber\n |> group_by([f], f.user_id)\n |> select([f], count(f.id))\n end\n\n def query({\"communities_subscribers\", CMS.CommunitySubscriber}, %{filter: filter}) do\n CMS.CommunitySubscriber\n |> QueryBuilder.filter_pack(filter)\n |> join(:inner, [u], c in assoc(u, :community))\n |> select([u, c], c)\n end\n\n def query({\"users_followers\", UserFollower}, %{count: _}) do\n UserFollower\n |> group_by([f], f.user_id)\n |> select([f], count(f.id))\n end\n\n def query({\"users_followings\", UserFollowing}, %{count: _}) do\n UserFollowing\n |> group_by([f], f.user_id)\n |> select([f], count(f.id))\n end\n\n def query({\"users_followers\", UserFollower}, %{viewer_did: _, cur_user: cur_user}) do\n UserFollower |> where([f], f.follower_id == ^cur_user.id)\n end\n\n def query({\"posts_favorites\", CMS.PostFavorite}, %{count: _}) do\n CMS.PostFavorite |> count_cotents\n end\n\n def query({\"jobs_favorites\", CMS.JobFavorite}, %{count: _}) do\n CMS.JobFavorite |> count_cotents\n end\n\n def query(queryable, _args), do: queryable\n\n defp count_cotents(queryable) do\n queryable\n |> group_by([f], f.user_id)\n |> select([f], count(f.id))\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,5,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null],"name":"lib/groupher_server/cms/job_comment_reply.ex","source":"defmodule GroupherServer.CMS.JobCommentReply do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.JobComment\n\n @required_fields ~w(job_comment_id reply_id)a\n\n @type t :: %JobCommentReply{}\n schema \"jobs_comments_replies\" do\n belongs_to(:job_comment, JobComment, foreign_key: :job_comment_id)\n belongs_to(:reply, JobComment, foreign_key: :reply_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%JobCommentReply{} = job_comment_reply, attrs) do\n job_comment_reply\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:job_comment_id)\n |> foreign_key_constraint(:reply_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,60,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,1,null,1,1,null,1,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/helper/certification.ex","source":"defmodule Helper.Certification do\n @moduledoc \"\"\"\n valid editors and passport details\n \"\"\"\n def editor_titles(:cms) do\n [\"chief editor\", \"post editor\"]\n end\n\n def passport_rules(cms: \"chief editor\") do\n %{\n \"post.tag.create\" => true,\n \"post.tag.edit\" => true,\n \"post.article.markDelete\" => true\n }\n end\n\n # a |> Enum.map(fn(x) -> {x, false} end) |> Map.new\n # %{\n # cms: %{\n # system: ..,\n # community: ...,\n # },\n # statistics: %{\n # ....\n # },\n # otherMoudle: %{\n\n # }\n # }\n\n @doc \"\"\"\n 基础权限,社区权限\n \"\"\"\n def all_rules(:cms) do\n %{\n general: [\n \"system_notification.publish\",\n \"stamp_passport\",\n # community\n \"editor.set\",\n \"editor.unset\",\n \"editor.update\",\n \"community.create\",\n \"community.update\",\n \"community.delete\",\n \"category.create\",\n \"category.delete\",\n \"category.update\",\n \"category.set\",\n \"category.unset\",\n \"thread.create\",\n \"post.community.mirror\",\n \"post.community.unmirror\",\n \"job.community.mirror\",\n \"job.community.unmirror\",\n \"post.pin\",\n \"post.undo_pin\",\n \"post.markDelete\",\n \"post.undo_trash\"\n ],\n community: [\n # thread\n \"thread.set\",\n \"thread.unset\",\n \"post.edit\",\n \"post.markDelete\",\n \"post.delete\",\n \"job.edit\",\n \"job.markDelete\",\n \"job.delete\",\n # post tag\n \"post.tag.create\",\n \"post.tag.update\",\n \"post.tag.delete\",\n \"post.tag.set\",\n \"post.tag.unset\",\n # job tag\n \"job.tag.create\",\n \"job.tag.update\",\n \"job.tag.delete\",\n \"job.tag.set\",\n \"job.tag.unset\"\n ]\n }\n end\n\n def all_rules(:cms, :stringify) do\n rules = all_rules(:cms)\n\n %{\n general: rules.general |> Enum.map(fn x -> {x, false} end) |> Map.new() |> Jason.encode!(),\n community:\n rules.community |> Enum.map(fn x -> {x, false} end) |> Map.new() |> Jason.encode!()\n }\n end\nend\n\n# 可以编辑某个社区 post 版块的文章, 支持 owner\n# middleware(M.Passport, claim: \"cms->c?->posts.article.edit\")\n# middleware(M.Passport, claim: \"owner;cms->c?->posts.article.edit\")\n\n# 可以添加某个社区 posts 版块的 tag 标签, 同时可支持 owner\n# middleware(M.Passport, claim: \"cms->c?->posts.tag.add\")\n# middleware(M.Passport, claim: \"cms->c?->posts.tag.edit\")\n# middleware(M.Passport, claim: \"cms->c?->posts.tag.delete\")\n# middleware(M.Passport, claim: \"cms->c?->posts.tag.markDelete\")\n# middleware(M.Passport, claim: \"owner;cms->c?->posts.tag.delete\")\n\n# 可以给某个社区 posts 版块的 posts 设置标签(setTag), 同时可支持 owner?\n# middleware(M.Passport, claim: \"c?->posts.tag.set\")\n\n# 可以某个社区的 posts 版块置顶\n# middleware(M.Passport, claim: \"cms->c?->posts.setTop\")\n\n# 可以编辑某个社区所有版块的文章\n# middleware(M.Passport, claim: \"cms->c?->posts.articles.edit\")\n# middleware(M.Passport, claim: \"cms->c?->job.articles.edit\")\n# ....全部显示声明....\n# middleware(M.Passport, claim: \"cms->c?->radar.articles.edit\")\n\n# 可以给某个社区的某个版块添加/删除管理员, 实际上就是在给其他成员分配上面的权限,同时该用户会被添加到相应的管理员中\n# middleware(M.Passport, claim: \"cms->c?->posts.managers.add\")\n# middleware(M.Passport, claim: \"cms->c?->jobs.managers.add\")\n# middleware(M.Passport, claim: \"cms->c?->videos.managers.add\")\n# middleware(M.Passport, claim: \"cms->c?->videos.managers.delete\")\n\n# 可以给社区的版块设置审核后发布\n# middleware(M.Passport, claim: \"cms->c?->settings.posts.needReview\")\n# middleware(M.Passport, claim: \"cms->c?->posts.reviewer\") # 审核员 (一开始没必要加)\n\n# 在某个社区的某个版块屏蔽某个用户\n# middleware(M.Passport, claim: \"cms->c?->viewer->block\")\n\n# 查看某个社区的总访问量\n# middleware(M.Passport, claim: \"statistics->c?->click\")\n# middleware(M.Passport, claim: \"logs->c?->posts ...\")\n\n# defguard the_fuck(value) when String.contains?(value, \"->?\")\n# classify the require of this gateway"},{"coverage":[null,null,null,null,null,null,null,null,null,null,2,null,null,30,30,30,null,null,null,null,28,28,28,null,null,null,null,25,null,25,25,null,25,null,null,null,25,25,null,25,null,null,null,null,null,null,null,null,null,58,null,null,null,null,58,null,58,null,null,null,2,null,null,null,2,null,null,null,2,2,null,null,null,null,2,null,null,null,2,null,null,null,4,4,null,null,null,null,4,null,4,null,4,null,4,null,null,null,null,null,9,8,null,null,25,null,null,null,null,17,null,null,null,null,null,null,17,null,null,null,null,null,null,47,null,null,null,null,null],"name":"lib/groupher_server/accounts/delegates/mails.ex","source":"defmodule GroupherServer.Accounts.Delegate.Mails do\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1, done: 2]\n import ShortMaps\n\n alias GroupherServer.Repo\n alias GroupherServer.Accounts.{User, MentionMail, NotificationMail, SysNotificationMail}\n alias GroupherServer.Delivery\n alias Helper.ORM\n\n def mailbox_status(%User{} = user), do: Delivery.mailbox_status(user)\n\n def fetch_mentions(%User{} = user, filter) do\n with {:ok, mentions} <- Delivery.fetch_mentions(user, filter),\n {:ok, washed_mentions} <- wash_data(MentionMail, mentions.entries) do\n MentionMail |> messages_handler(washed_mentions, user, filter)\n end\n end\n\n def fetch_notifications(%User{} = user, filter) do\n with {:ok, notifications} <- Delivery.fetch_notifications(user, filter),\n {:ok, washed_notifications} <- wash_data(NotificationMail, notifications.entries) do\n NotificationMail |> messages_handler(washed_notifications, user, filter)\n end\n end\n\n def fetch_sys_notifications(%User{} = user, %{page: page, size: size, read: read}) do\n with {:ok, sys_notifications} <-\n Delivery.fetch_sys_notifications(user, %{page: page, size: size}),\n {:ok, washed_notifications} <-\n wash_data(SysNotificationMail, user, sys_notifications.entries) do\n SysNotificationMail\n |> Repo.insert_all(washed_notifications)\n\n SysNotificationMail\n |> order_by(desc: :inserted_at)\n |> where([m], m.user_id == ^user.id)\n |> where([m], m.read == ^read)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n end\n\n defp messages_handler(queryable, washed_data, %User{id: user_id}, %{\n page: page,\n size: size,\n read: read\n }) do\n queryable\n |> Repo.insert_all(washed_data)\n\n queryable\n |> order_by(desc: :inserted_at)\n |> where([m], m.to_user_id == ^user_id)\n |> where([m], m.read == ^read)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n\n def mark_mail_read(%MentionMail{id: id}, %User{} = user) do\n do_mark_mail_read(MentionMail, id, user)\n end\n\n def mark_mail_read(%NotificationMail{id: id}, %User{} = user) do\n do_mark_mail_read(NotificationMail, id, user)\n end\n\n def mark_mail_read(%SysNotificationMail{id: id}, %User{} = user) do\n with {:ok, mail} <- SysNotificationMail |> ORM.find_by(id: id, user_id: user.id) do\n mail |> ORM.update(%{read: true}) |> done(:status)\n end\n end\n\n def mark_mail_read_all(%User{} = user, :mention) do\n user |> do_mark_mail_read_all(MentionMail, :mention)\n end\n\n def mark_mail_read_all(%User{} = user, :notification) do\n user |> do_mark_mail_read_all(NotificationMail, :notification)\n end\n\n defp do_mark_mail_read(queryable, id, %User{} = user) do\n with {:ok, mail} <- queryable |> ORM.find_by(id: id, to_user_id: user.id) do\n mail |> ORM.update(%{read: true}) |> done(:status)\n end\n end\n\n defp do_mark_mail_read_all(%User{} = user, mail, atom) do\n query =\n mail\n |> where([m], m.to_user_id == ^user.id)\n\n Repo.update_all(query, set: [read: true])\n\n Delivery.mark_read_all(user, atom)\n end\n\n defp wash_data(MentionMail, []), do: {:ok, []}\n defp wash_data(NotificationMail, []), do: {:ok, []}\n\n defp wash_data(MentionMail, list), do: do_wash_data(list)\n defp wash_data(NotificationMail, list), do: do_wash_data(list)\n\n defp wash_data(SysNotificationMail, user, list) do\n convert =\n list\n |> Enum.map(\n &(Map.from_struct(&1)\n |> Map.delete(:__meta__)\n |> Map.put(:user_id, user.id))\n )\n\n {:ok, convert}\n end\n\n defp do_wash_data(list) do\n convert =\n list\n |> Enum.map(\n &(Map.from_struct(&1)\n |> Map.delete(:__meta__)\n |> Map.delete(:id)\n |> Map.delete(:from_user)\n |> Map.delete(:to_user))\n )\n\n {:ok, convert}\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,19,5,0,null,26,17,12,null],"name":"lib/groupher_server/statistics/statistics.ex","source":"defmodule GroupherServer.Statistics do\n @moduledoc \"\"\"\n The Statistics context.\n \"\"\"\n\n alias GroupherServer.Statistics.Delegate.{\n Contribute,\n Throttle\n }\n\n defdelegate make_contribute(info), to: Contribute\n defdelegate list_contributes(info), to: Contribute\n defdelegate list_contributes_digest(community), to: Contribute\n\n defdelegate log_publish_action(user), to: Throttle\n defdelegate load_throttle_record(user), to: Throttle\n defdelegate mock_throttle_attr(scope, user, opt), to: Throttle\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,315,null,null,null,null,null,null,null,315,null,null,null,null,204,null,null],"name":"lib/helper/guardian.ex","source":"defmodule Helper.Guardian do\n @moduledoc \"\"\"\n This module defines some helper function used by\n encode/decode jwt\n \"\"\"\n use Guardian, otp_app: :groupher_server\n\n @token_expireation 24 * 14\n\n def subject_for_token(resource, _claims) do\n {:ok, to_string(resource.id)}\n end\n\n def resource_from_claims(claims) do\n {:ok, %{id: claims[\"sub\"]}}\n end\n\n def jwt_encode(source, args \\\\ %{}) do\n encode_and_sign(source, args, ttl: {@token_expireation, :hour})\n end\n\n # jwt_decode\n def jwt_decode(token) do\n resource_from_token(token)\n end\nend"},{"coverage":[null,null,null,null,null,null,4,null,null,2,26,null,null,29,31,null,null,62,29,null,3,4,null],"name":"lib/groupher_server/delivery/delivery.ex","source":"defmodule GroupherServer.Delivery do\n @moduledoc \"\"\"\n The Delivery context.\n \"\"\"\n alias GroupherServer.Delivery.Delegate.{Mentions, Notifications, Utils}\n\n defdelegate mailbox_status(user), to: Utils\n\n # system_notifications\n defdelegate publish_system_notification(info), to: Notifications\n defdelegate fetch_sys_notifications(user, filter), to: Notifications\n\n # mentions\n defdelegate mention_someone(from_user, to_user, info), to: Mentions\n defdelegate fetch_mentions(user, filter), to: Mentions\n\n # notifications\n defdelegate notify_someone(from_user, to_user, info), to: Notifications\n defdelegate fetch_notifications(user, filter), to: Notifications\n\n defdelegate fetch_record(user), to: Utils\n defdelegate mark_read_all(user, opt), to: Utils\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/comment.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Comment do\n @moduledoc \"\"\"\n CMS mutations for comments\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_comment_mutations do\n @desc \"create a comment\"\n field :create_comment, :comment do\n # TODO use thread and force community pass-in\n arg(:thread, :thread, default_value: :post)\n arg(:id, non_null(:id))\n arg(:body, non_null(:string))\n\n # TDOO: use a comment resolver\n middleware(M.Authorize, :login)\n # TODO: 文章作者可以删除评论,文章可以设置禁止评论\n resolve(&R.CMS.create_comment/3)\n end\n\n field :delete_comment, :comment do\n arg(:thread, :thread, default_value: :post)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n # middleware(M.PassportLoader, source: [:post, :comment])\n middleware(M.PassportLoader, source: [:arg_thread, :comment])\n # TODO: 文章可以设置禁止评论\n # middleware(M.Passport, claim: \"owner;cms->c?->post.comment.delete\")\n middleware(M.Passport, claim: \"owner\")\n # middleware(M.Authorize, :login)\n resolve(&R.CMS.delete_comment/3)\n end\n\n @desc \"reply a exsiting comment\"\n field :reply_comment, :comment do\n arg(:thread, non_null(:thread), default_value: :post)\n arg(:id, non_null(:id))\n arg(:body, non_null(:string))\n\n middleware(M.Authorize, :login)\n\n resolve(&R.CMS.reply_comment/3)\n end\n\n @desc \"like a comment\"\n field :like_comment, :comment do\n arg(:thread, non_null(:cms_comment), default_value: :post_comment)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.like_comment/3)\n end\n\n @desc \"undo like comment\"\n # field :undo_like_comment, :idlike do\n field :undo_like_comment, :comment do\n arg(:thread, non_null(:cms_comment), default_value: :post_comment)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.undo_like_comment/3)\n end\n\n field :dislike_comment, :comment do\n arg(:thread, non_null(:cms_comment), default_value: :post_comment)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.dislike_comment/3)\n end\n\n field :undo_dislike_comment, :comment do\n arg(:thread, non_null(:cms_comment), default_value: :post_comment)\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.undo_dislike_comment/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server.ex","source":"defmodule GroupherServer do\n @moduledoc \"\"\"\n GroupherServer keeps the contexts that define your domain\n and business logic.\n\n Contexts are also responsible for managing your data, regardless\n if it comes from the database, an external API or others.\n \"\"\"\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,123,null,null,null,null,null,null,null,null,null,null,null,null,null,26,null,null],"name":"lib/groupher_server/delivery/record.ex","source":"defmodule GroupherServer.Delivery.Record do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(user_id)a\n @optional_fields ~w(mentions_record notifications_record sys_notifications_record)a\n\n @type t :: %Record{}\n schema \"delivery_records\" do\n field(:mentions_record, :map)\n field(:notifications_record, :map)\n field(:sys_notifications_record, :map)\n belongs_to(:user, User)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Record{} = record, attrs) do\n record\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/gettext.ex","source":"defmodule GroupherServerWeb.Gettext do\n @moduledoc \"\"\"\n A module providing Internationalization with a gettext-based API.\n\n By using [Gettext](https://hexdocs.pm/gettext),\n your module gains a set of macros for translations, for example:\n\n import GroupherServerWeb.Gettext\n\n # Simple translation\n gettext \"Here is the string to translate\"\n\n # Plural translation\n ngettext \"Here is the string to translate\",\n \"Here are the strings to translate\",\n 3\n\n # Domain-based translation\n dgettext \"errors\", \"Here is the error message to translate\"\n\n See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.\n \"\"\"\n use Gettext, otp_app: :groupher_server\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server_web/middleware/see_me.ex","source":"# ---\n# Absinthe.Middleware behaviour\n# see https://hexdocs.pm/absinthe/Absinthe.Middleware.html#content\n# ---\ndefmodule GroupherServerWeb.Middleware.SeeMe do\n @behaviour Absinthe.Middleware\n\n def call(res, _) do\n # IO.inspect(\"see me\")\n res\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,45,null,null,null,null,null,null,null,null,null,null,null,null,null,19,null,null],"name":"lib/groupher_server/cms/post_comment_like.ex","source":"defmodule GroupherServer.CMS.PostCommentLike do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n alias GroupherServer.CMS.PostComment\n\n @required_fields ~w(post_comment_id user_id)a\n\n @type t :: %PostCommentLike{}\n schema \"posts_comments_likes\" do\n belongs_to(:user, Accounts.User, foreign_key: :user_id)\n belongs_to(:post_comment, PostComment, foreign_key: :post_comment_id)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostCommentLike{} = post_comment_like, attrs) do\n post_comment_like\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:post_comment_id)\n |> foreign_key_constraint(:user_id)\n |> unique_constraint(:user_id, name: :posts_comments_likes_user_id_post_comment_id_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,0,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,0,null,null],"name":"lib/groupher_server/accounts/bill.ex","source":"defmodule GroupherServer.Accounts.Bill do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id source_type source_title price)a\n @optional_fields ~w(source_id)a\n\n @type t :: %Bill{}\n schema \"bills\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n\n field(:source_id, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:price, :integer)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Bill{} = bill, attrs) do\n bill\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/delivery/delivery_queries.ex","source":"defmodule GroupherServerWeb.Schema.Delivery.Queries do\n @moduledoc \"\"\"\n Delivery.Queries\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :delivery_queries do\n @desc \"get mention list?\"\n field :xxxx_todo, :boolean do\n arg(:id, non_null(:id))\n\n resolve(&R.Delivery.mention_someone/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,8,null,null,null,2,null,null,null,null,null,0,null,null,null,null,null,3,null,null,null,null,null,24,24,null,null,null,null,null,null,null,null,null,4,null,null,null,null,null,null,4,4,null,null,null,4,null,null,null,null,null,null,1,1,null,null,null,1,null,null,null,null,null,null,null,null,null,null,null,null,null,2,null,null,44,null,null,372,null,null,123,null,null,8,null,null,1,null,null,1,null,null,null,null,7,null,null,null,0,null,null,0,null,null,0,null,null,1,null,null,1,null,null,null,null,null,7,null,null,null,7,null,null,7,null,null,null,7,null,null,7,null,null,null,7,null,null,7,null,null,null,7,null,null,null,91,null,null,null,0,null,null,null,null,null,null,2,null,null,null,null,null,null,7,null,null,null,null,null,null,14,null,null,null,82,null,null,null,44,null,null,298,null,null,null],"name":"lib/helper/query_builder.ex","source":"defmodule Helper.QueryBuilder do\n # alias GroupherServer.Repo\n import Ecto.Query, warn: false\n\n @doc \"\"\"\n handle [3] situation:\n\n 1. basic query with filter\n 2. reaction_user's count\n 3. is viewer reacted?\n\n bewteen [THREAD] and [REACT]\n [THREAD]: cms thread, include: Post, Job, Video, Repo ...\n [REACT]; favorites, stars, watchs ...\n \"\"\"\n def members_pack(queryable, %{filter: filter}) do\n queryable |> load_inner_users(filter)\n end\n\n def members_pack(queryable, %{viewer_did: _, cur_user: cur_user}) do\n queryable |> where([f], f.user_id == ^cur_user.id)\n end\n\n def members_pack(queryable, %{count: _, type: :post}) do\n queryable\n |> group_by([f], f.post_id)\n |> select([f], count(f.id))\n end\n\n def members_pack(queryable, %{count: _, type: :community}) do\n queryable\n |> group_by([f], f.community_id)\n |> select([f], count(f.id))\n end\n\n def load_inner_users(queryable, filter) do\n queryable\n |> join(:inner, [f], u in assoc(f, :user))\n |> select([f, u], u)\n |> filter_pack(filter)\n end\n\n @doc \"\"\"\n load replies of the given comment\n \"\"\"\n def load_inner_replies(queryable, filter) do\n queryable\n |> filter_pack(filter)\n |> join(:inner, [c], r in assoc(c, :reply))\n |> select([c, r], r)\n end\n\n @doc \"\"\"\n inserted in latest x mounth\n \"\"\"\n def recent_inserted(queryable, months: count) do\n end_of_today = Timex.now() |> Timex.end_of_day()\n x_months_ago = Timex.today() |> Timex.shift(months: -count) |> Timex.to_datetime()\n\n queryable\n |> where([q], q.inserted_at >= ^x_months_ago)\n |> where([q], q.inserted_at <= ^end_of_today)\n end\n\n @doc \"\"\"\n inserted in latest x days\n \"\"\"\n def recent_inserted(queryable, days: count) do\n end_of_today = Timex.now() |> Timex.end_of_day()\n x_days_ago = Timex.today() |> Timex.shift(days: -count) |> Timex.to_datetime()\n\n queryable\n |> where([q], q.inserted_at >= ^x_days_ago)\n |> where([q], q.inserted_at <= ^end_of_today)\n end\n\n # this is strategy will cause\n # defp sort_strategy(:desc_inserted), do: [desc: :inserted_at, desc: :views]\n # defp sort_strategy(:most_views), do: [desc: :views, desc: :inserted_at]\n # defp sort_strategy(:least_views), do: [asc: :views, desc: :inserted_at]\n # defp strategy(:most_stars), do: [desc: :views, desc: :inserted_at]\n\n defp sort_by_count(queryable, field, direction) do\n queryable\n |> join(:left, [p], s in assoc(p, ^field))\n |> group_by([p], p.id)\n |> select([p], p)\n |> order_by([_, s], {^direction, fragment(\"count(?)\", s.id)})\n end\n\n def default_article_filters, do: %{pin: false, markDelete: false}\n\n def filter_pack(queryable, filter) when is_map(filter) do\n Enum.reduce(filter, queryable, fn\n {:sort, :desc_inserted}, queryable ->\n # queryable |> order_by(^sort_strategy(:desc_inserted))\n queryable |> order_by(desc: :inserted_at)\n\n {:sort, :asc_inserted}, queryable ->\n queryable |> order_by(asc: :inserted_at)\n\n {:sort, :desc_index}, queryable ->\n queryable |> order_by(desc: :index)\n\n {:sort, :asc_index}, queryable ->\n queryable |> order_by(asc: :index)\n\n {:sort, :most_views}, queryable ->\n # this will cause error in Dialyzer\n # queryable |> order_by(^sort_strategy(:most_views))\n queryable |> order_by(desc: :views, desc: :inserted_at)\n\n {:sort, :least_views}, queryable ->\n # queryable |> order_by(^sort_strategy(:least_views))\n queryable |> order_by(asc: :views, desc: :inserted_at)\n\n {:sort, :most_stars}, queryable ->\n queryable |> sort_by_count(:stars, :desc)\n\n {:sort, :least_stars}, queryable ->\n queryable |> sort_by_count(:stars, :asc)\n\n {:sort, :most_likes}, queryable ->\n queryable |> sort_by_count(:likes, :desc)\n\n {:sort, :most_dislikes}, queryable ->\n queryable |> sort_by_count(:dislikes, :desc)\n\n {:when, :today}, queryable ->\n # date = DateTime.utc_now() |> Timex.to_datetime()\n # use timezone info is server is not in the some timezone\n # Timex.now(\"America/Chicago\")\n date = Timex.now()\n\n queryable\n |> where([p], p.inserted_at >= ^Timex.beginning_of_day(date))\n |> where([p], p.inserted_at <= ^Timex.end_of_day(date))\n\n {:when, :this_week}, queryable ->\n date = Timex.now()\n\n queryable\n |> where([p], p.inserted_at >= ^Timex.beginning_of_week(date))\n |> where([p], p.inserted_at <= ^Timex.end_of_week(date))\n\n {:when, :this_month}, queryable ->\n date = Timex.now()\n\n queryable\n |> where([p], p.inserted_at >= ^Timex.beginning_of_month(date))\n |> where([p], p.inserted_at <= ^Timex.end_of_month(date))\n\n {:when, :this_year}, queryable ->\n date = Timex.now()\n\n queryable\n |> where([p], p.inserted_at >= ^Timex.beginning_of_year(date))\n |> where([p], p.inserted_at <= ^Timex.end_of_year(date))\n\n # TODO: remove\n {_, :all}, queryable ->\n queryable\n\n # TODO: use raw instead title\n {:article_tag, tag_name}, queryable ->\n from(\n q in queryable,\n join: t in assoc(q, :tags),\n where: t.title == ^tag_name\n )\n\n {:category, catetory_raw}, queryable ->\n from(\n q in queryable,\n join: t in assoc(q, :categories),\n where: t.raw == ^catetory_raw\n )\n\n {:community, community_raw}, queryable ->\n from(\n q in queryable,\n join: t in assoc(q, :communities),\n where: t.raw == ^community_raw\n )\n\n {:first, first}, queryable ->\n queryable |> limit(^first)\n\n {:pin, bool}, queryable ->\n queryable\n |> where([p], p.pin == ^bool)\n\n {:markDelete, bool}, queryable ->\n queryable\n |> where([p], p.markDelete == ^bool)\n\n {_, _}, queryable ->\n queryable\n end)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,4092,null,null,null,6600,null,null,null,65366,null,null,null,null,null,null,null,null,null,null,null,361,null,null,null,361,null,null],"name":"lib/groupher_server_web/schema.ex","source":"defmodule GroupherServerWeb.Schema do\n @moduledoc \"\"\"\n scham index\n \"\"\"\n use Absinthe.Schema\n\n alias GroupherServerWeb.Schema.{Account, CMS, Delivery, Statistics, Utils}\n alias GroupherServerWeb.Middleware, as: M\n\n import_types(Absinthe.Type.Custom)\n\n # utils\n import_types(Utils.CommonTypes)\n\n # account\n import_types(Account.Types)\n import_types(Account.Queries)\n import_types(Account.Mutations)\n\n # statistics\n import_types(Statistics.Types)\n import_types(Statistics.Queries)\n import_types(Statistics.Mutations)\n\n # delivery\n import_types(Delivery.Types)\n import_types(Delivery.Queries)\n import_types(Delivery.Mutations)\n\n # cms\n import_types(CMS.Types)\n import_types(CMS.Queries)\n import_types(CMS.Mutations.Community)\n import_types(CMS.Mutations.Operation)\n import_types(CMS.Mutations.Post)\n import_types(CMS.Mutations.Job)\n import_types(CMS.Mutations.Comment)\n\n query do\n import_fields(:account_queries)\n import_fields(:statistics_queries)\n import_fields(:delivery_queries)\n import_fields(:cms_queries)\n end\n\n mutation do\n # account\n import_fields(:account_mutations)\n # statistics\n import_fields(:statistics_mutations)\n # delivery\n import_fields(:delivery_mutations)\n # cms\n import_fields(:cms_mutation_community)\n import_fields(:cms_opertion_mutations)\n import_fields(:cms_post_mutations)\n import_fields(:cms_job_mutations)\n import_fields(:cms_comment_mutations)\n end\n\n def middleware(middleware, _field, %{identifier: :query}) do\n middleware ++ [M.GeneralError]\n end\n\n def middleware(middleware, _field, %{identifier: :mutation}) do\n middleware ++ [M.ChangesetErrors]\n end\n\n def middleware(middleware, _field, _object) do\n [ApolloTracing.Middleware.Tracing, ApolloTracing.Middleware.Caching] ++ middleware\n end\n\n def plugins do\n [Absinthe.Middleware.Dataloader | Absinthe.Plugin.defaults()]\n end\n\n def dataloader do\n alias GroupherServer.{Accounts, CMS}\n\n Dataloader.new()\n |> Dataloader.add_source(Accounts, Accounts.Helper.Loader.data())\n |> Dataloader.add_source(CMS, CMS.Helper.Loader.data())\n end\n\n def context(ctx) do\n ctx |> Map.put(:loader, dataloader())\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/post.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Post do\n @moduledoc \"\"\"\n CMS mutations for post\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_post_mutations do\n @desc \"create a user\"\n field :create_post, :post do\n arg(:title, non_null(:string))\n arg(:body, non_null(:string))\n arg(:digest, non_null(:string))\n arg(:length, non_null(:integer))\n arg(:link_addr, :string)\n arg(:community_id, non_null(:id))\n arg(:thread, :thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.PublishThrottle)\n # middleware(M.PublishThrottle, interval: 3, hour_limit: 15, day_limit: 30)\n resolve(&R.CMS.create_article/3)\n end\n\n @desc \"pin a post\"\n field :pin_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->post.pin\")\n resolve(&R.CMS.pin_post/3)\n end\n\n @desc \"unpin a post\"\n field :undo_pin_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->post.undo_pin\")\n resolve(&R.CMS.undo_pin_post/3)\n end\n\n @desc \"markDelete a post, not delete\"\n field :trash_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->post.markDelete\")\n\n resolve(&R.CMS.trash_post/3)\n end\n\n @desc \"markDelete a post, not delete\"\n field :undo_trash_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->post.undo_trash\")\n\n resolve(&R.CMS.undo_trash_post/3)\n end\n\n @desc \"delete a cms/post\"\n # TODO: if post belongs to multi communities, unset instead delete\n field :delete_post, :post do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :post)\n middleware(M.Passport, claim: \"owner;cms->c?->post.delete\")\n\n resolve(&R.CMS.delete_content/3)\n end\n\n @desc \"update a cms/post\"\n field :update_post, :post do\n arg(:id, non_null(:id))\n arg(:title, :string)\n arg(:body, :string)\n arg(:digest, :string)\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :post)\n middleware(M.Passport, claim: \"owner;cms->c?->post.edit\")\n\n resolve(&R.CMS.update_article/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,106,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,12,null,null],"name":"lib/groupher_server/cms/category.ex","source":"defmodule GroupherServer.CMS.Category do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.CMS.{Author, Community}\n # alias GroupherServer.Accounts\n # alias Helper.Certification\n\n @required_fields ~w(title raw author_id)a\n\n @type t :: %Category{}\n\n schema \"categories\" do\n field(:title, :string)\n field(:raw, :string)\n belongs_to(:author, Author)\n\n many_to_many(\n :communities,\n Community,\n join_through: \"communities_categories\",\n join_keys: [category_id: :id, community_id: :id],\n on_delete: :delete_all,\n on_replace: :delete\n )\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Category{} = category, attrs) do\n category\n |> cast(attrs, @required_fields)\n |> validate_required(@required_fields)\n # |> validate_inclusion(:title, Certification.editor_titles(:cms))\n # |> foreign_key_constraint(:community_id)\n # |> foreign_key_constraint(:author_id)\n |> unique_constraint(:title, name: :categories_title_index)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/cms/mutations/job.ex","source":"defmodule GroupherServerWeb.Schema.CMS.Mutations.Job do\n @moduledoc \"\"\"\n CMS mutations for job\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :cms_job_mutations do\n @desc \"create a user\"\n field :create_job, :job do\n arg(:title, non_null(:string))\n arg(:company, non_null(:string))\n arg(:company_logo, non_null(:string))\n arg(:location, non_null(:string))\n arg(:body, non_null(:string))\n arg(:digest, non_null(:string))\n arg(:length, non_null(:integer))\n arg(:community_id, non_null(:id))\n arg(:link_addr, :string)\n arg(:link_source, :string)\n\n arg(:thread, :thread, default_value: :job)\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.create_article/3)\n end\n\n @desc \"delete a job\"\n field :delete_job, :job do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :job)\n middleware(M.Passport, claim: \"owner;cms->c?->job.delete\")\n\n resolve(&R.CMS.delete_content/3)\n end\n\n @desc \"update a cms/job\"\n field :update_job, :job do\n arg(:id, non_null(:id))\n arg(:title, :string)\n arg(:body, :string)\n arg(:digest, :string)\n # ...\n\n middleware(M.Authorize, :login)\n middleware(M.PassportLoader, source: :job)\n middleware(M.Passport, claim: \"owner;cms->c?->job.edit\")\n\n resolve(&R.CMS.update_article/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/account/account_mutations.ex","source":"defmodule GroupherServerWeb.Schema.Account.Mutations do\n @moduledoc \"\"\"\n accounts mutations\n \"\"\"\n use Helper.GqlSchemaSuite\n\n object :account_mutations do\n # @desc \"hehehef: create a user\"\n # field :create_user, :user do\n # arg(:username, non_null(:string))\n # arg(:nickname, non_null(:string))\n # arg(:bio, non_null(:string))\n # arg(:company, non_null(:string))\n\n # resolve(&R.Accounts.create_user/3)\n # end\n\n @desc \"update user's profile\"\n field :update_profile, :user do\n arg(:profile, non_null(:user_profile_input))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.update_profile/3)\n end\n\n field :github_signin, :token_info do\n arg(:code, non_null(:string))\n # arg(:profile, non_null(:github_profile_input))\n\n middleware(M.GithubUser)\n resolve(&R.Accounts.github_signin/3)\n end\n\n @doc \"follow a user\"\n field :follow, :user do\n arg(:user_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.follow/3)\n end\n\n @doc \"undo follow to a user\"\n field :undo_follow, :user do\n arg(:user_id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.undo_follow/3)\n end\n\n @desc \"mark a mention as read\"\n field :mark_mention_read, :status do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_mention_read/3)\n end\n\n @desc \"mark a all unread mention as read\"\n field :mark_mention_read_all, :status do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_mention_read_all/3)\n end\n\n @desc \"mark a notification as read\"\n field :mark_notification_read, :status do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_notification_read/3)\n end\n\n @desc \"mark a all unread notifications as read\"\n field :mark_notification_read_all, :status do\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_notification_read_all/3)\n end\n\n @desc \"mark a system notification as read\"\n field :mark_sys_notification_read, :status do\n arg(:id, non_null(:id))\n\n middleware(M.Authorize, :login)\n resolve(&R.Accounts.mark_sys_notification_read/3)\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/schema/utils/common_types.ex","source":"defmodule GroupherServerWeb.Schema.Helper.Metrics do\n use Absinthe.Schema.Notation\n use Absinthe.Ecto, repo: GroupherServer.Repo\n\n object :status do\n field(:done, :boolean)\n field(:id, :id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,151,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,62,null,null],"name":"lib/groupher_server/delivery/notification.ex","source":"defmodule GroupherServer.Delivery.Notification do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n\n alias GroupherServer.Accounts.User\n\n @required_fields ~w(from_user_id to_user_id action source_title source_id source_preview source_type)a\n @optional_fields ~w(parent_id parent_type read)a\n\n @type t :: %Notification{}\n schema \"notifications\" do\n belongs_to(:from_user, User)\n belongs_to(:to_user, User)\n field(:action, :string)\n\n field(:source_id, :string)\n field(:source_preview, :string)\n field(:source_title, :string)\n field(:source_type, :string)\n field(:parent_id, :string)\n field(:parent_type, :string)\n field(:read, :boolean)\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%Notification{} = notification, attrs) do\n notification\n |> cast(attrs, @optional_fields ++ @required_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:from_user_id)\n |> foreign_key_constraint(:to_user_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,12,12,12,null,12,null,null,12,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server/accounts/delegates/reacted_contents.ex","source":"defmodule GroupherServer.Accounts.Delegate.ReactedContents do\n @moduledoc \"\"\"\n get contents(posts, jobs, videos ...) that user reacted (star, favorite ..)\n \"\"\"\n import GroupherServer.CMS.Helper.MatcherOld\n import Ecto.Query, warn: false\n import Helper.Utils, only: [done: 1]\n import ShortMaps\n\n alias Helper.{ORM, QueryBuilder}\n alias GroupherServer.Accounts.User\n\n def reacted_contents(thread, react, ~m(page size)a = filter, %User{id: user_id}) do\n with {:ok, action} <- match_action(thread, react) do\n action.reactor\n |> where([f], f.user_id == ^user_id)\n |> join(:inner, [f], p in assoc(f, ^thread))\n |> select([f, p], p)\n |> QueryBuilder.filter_pack(filter)\n |> ORM.paginater(~m(page size)a)\n |> done()\n end\n end\n\n # def reacted_count(thread, react, %User{id: user_id}) do\n # with {:ok, action} <- match_action(thread, react) do\n # action.reactor\n # |> where([f], f.user_id == ^user_id)\n # |> group_by([f], f.post_id)\n # |> select([f], count(f.id))\n # end\n # end\nend"},{"coverage":[null,null,null,null,null,null,null,null,4,null,4,null,null,null,0,null,null,0,null,null],"name":"lib/groupher_server_web/middleware/force_loader.ex","source":"# this is a tmp solution for load related-users like situations\n# it turn dataloader into nomal N+1 resolver\n# NOTE: it should be replaced using \"Select-Top-N-By-Group\" solution\n\ndefmodule GroupherServerWeb.Middleware.ForceLoader do\n @behaviour Absinthe.Middleware\n\n def call(%{source: %{id: id}} = resolution, _) do\n arguments = resolution.arguments |> Map.merge(%{what_ever: id})\n\n %{resolution | arguments: arguments}\n # resolution\n end\n\n def call(%{errors: errors} = resolution, _) when length(errors) > 0, do: resolution\n\n def call(resolution, _) do\n resolution\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,475,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,167,null,null],"name":"lib/groupher_server/cms/post_comment.ex","source":"defmodule GroupherServer.CMS.PostComment do\n @moduledoc false\n alias __MODULE__\n\n use Ecto.Schema\n import Ecto.Changeset\n alias GroupherServer.Accounts\n\n alias GroupherServer.CMS.{\n Post,\n PostCommentDislike,\n PostCommentLike,\n PostCommentReply\n }\n\n @required_fields ~w(body author_id post_id floor)a\n @optional_fields ~w(reply_id)a\n\n @type t :: %PostComment{}\n schema \"posts_comments\" do\n field(:body, :string)\n field(:floor, :integer)\n belongs_to(:author, Accounts.User, foreign_key: :author_id)\n belongs_to(:post, Post, foreign_key: :post_id)\n belongs_to(:reply_to, PostComment, foreign_key: :reply_id)\n\n has_many(:replies, {\"posts_comments_replies\", PostCommentReply})\n has_many(:likes, {\"posts_comments_likes\", PostCommentLike})\n has_many(:dislikes, {\"posts_comments_dislikes\", PostCommentDislike})\n\n timestamps(type: :utc_datetime)\n end\n\n @doc false\n def changeset(%PostComment{} = post_comment, attrs) do\n post_comment\n |> cast(attrs, @required_fields ++ @optional_fields)\n |> validate_required(@required_fields)\n |> foreign_key_constraint(:post_id)\n |> foreign_key_constraint(:author_id)\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,96,96,96,null,null,96,null,null,null,null,null,null,null,6,6,6,null,null,6,null,null,null,null,null,null,null,5,5,5,null,null,5,null,null,null,null,null,null,null,4,4,4,null,null,4,null,null,null,null,null,null,null,136,136,136,null,null,136,null,null,null,null,null,null,null,5,5,5,null,5,null,null,5,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,30,null,null,null,null,24,null,null,null],"name":"lib/groupher_server/accounts/delegates/achievements.ex","source":"defmodule GroupherServer.Accounts.Delegate.Achievements do\n @moduledoc \"\"\"\n user achievements related\n acheiveements formula:\n 1. create content been stared by other user + 1\n 2. create content been watched by other user + 1\n 3. create content been favorited by other user + 2\n 4. followed by other user + 3\n \"\"\"\n import Helper.Utils, only: [get_config: 2]\n import ShortMaps\n\n alias Helper.{ORM, SpecType}\n alias GroupherServer.Accounts.{Achievement, User}\n\n @favorite_weight get_config(:general, :user_achieve_favorite_weight)\n @star_weight get_config(:general, :user_achieve_star_weight)\n # @watch_weight get_config(:general, :user_achieve_watch_weight)\n @follow_weight get_config(:general, :user_achieve_follow_weight)\n\n @doc \"\"\"\n add user's achievement by add followers_count of favorite_weight\n \"\"\"\n @spec achieve(User.t(), atom, atom) :: SpecType.done()\n def achieve(%User{id: user_id}, :add, :follow) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n followers_count = achievement.followers_count + @follow_weight\n reputation = achievement.reputation + @follow_weight\n\n achievement\n |> ORM.update(~m(followers_count reputation)a)\n end\n end\n\n @doc \"\"\"\n minus user's achievement by add followers_count of favorite_weight\n \"\"\"\n def achieve(%User{id: user_id}, :minus, :follow) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n followers_count = achievement.followers_count |> safe_minus(@follow_weight)\n reputation = achievement.reputation |> safe_minus(@follow_weight)\n\n achievement\n |> ORM.update(~m(followers_count reputation)a)\n end\n end\n\n @doc \"\"\"\n add user's achievement by contents_stared_count of star_weight\n \"\"\"\n def achieve(%User{id: user_id} = _user, :add, :star) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n contents_stared_count = achievement.contents_stared_count + @star_weight\n reputation = achievement.reputation + @star_weight\n\n achievement\n |> ORM.update(~m(contents_stared_count reputation)a)\n end\n end\n\n @doc \"\"\"\n minus user's achievement by contents_stared_count of star_weight\n \"\"\"\n def achieve(%User{id: user_id} = _user, :minus, :star) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n contents_stared_count = achievement.contents_stared_count |> safe_minus(@star_weight)\n reputation = achievement.reputation |> safe_minus(@star_weight)\n\n achievement\n |> ORM.update(~m(contents_stared_count reputation)a)\n end\n end\n\n @doc \"\"\"\n minus user's achievement by contents_favorited_count of favorite_weight\n \"\"\"\n def achieve(%User{id: user_id} = _user, :add, :favorite) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n contents_favorited_count = achievement.contents_favorited_count + @favorite_weight\n reputation = achievement.reputation + @favorite_weight\n\n achievement\n |> ORM.update(~m(contents_favorited_count reputation)a)\n end\n end\n\n @doc \"\"\"\n add user's achievement by contents_favorited_count of favorite_weight\n \"\"\"\n def achieve(%User{id: user_id} = _user, :minus, :favorite) do\n with {:ok, achievement} <- ORM.findby_or_insert(Achievement, ~m(user_id)a, ~m(user_id)a) do\n contents_favorited_count =\n achievement.contents_favorited_count |> safe_minus(@favorite_weight)\n\n reputation = achievement.reputation |> safe_minus(@favorite_weight)\n\n achievement\n |> ORM.update(~m(contents_favorited_count reputation)a)\n end\n end\n\n # def achieve(%User{} = _user, :+, :watch) do\n # IO.inspect(\"acheiveements add :conent_watched\")\n # end\n\n # def achieve(%User{} = _user, :+, key) do\n # IO.inspect(\"acheiveements add #{key}\")\n # end\n\n # def achieve(%User{} = _user, :-, _key) do\n # IO.inspect(\"acheiveements plus\")\n # end\n\n @spec safe_minus(non_neg_integer(), non_neg_integer()) :: non_neg_integer()\n defp safe_minus(count, unit) when is_integer(count) and is_integer(unit) and unit > 0 do\n case count <= 0 do\n true ->\n 0\n\n false ->\n count - unit\n end\n end\nend"},{"coverage":[null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null],"name":"lib/groupher_server_web/channels/user_socket.ex","source":"defmodule GroupherServerWeb.UserSocket do\n use Phoenix.Socket\n\n ## Channels\n # channel \"room:*\", GroupherServerWeb.RoomChannel\n\n ## Transports\n transport(:websocket, Phoenix.Transports.WebSocket)\n # transport :longpoll, Phoenix.Transports.LongPoll\n\n # Socket params are passed from the client and can\n # be used to verify and authenticate a user. After\n # verification, you can put default assigns into\n # the socket that will be set for all channels, ie\n #\n # {:ok, assign(socket, :user_id, verified_user_id)}\n #\n # To deny connection, return `:error`.\n #\n # See `Phoenix.Token` documentation for examples in\n # performing token verification on connect.\n def connect(_params, socket) do\n {:ok, socket}\n end\n\n # Socket id's are topics that allow you to identify all sockets for a given user:\n #\n # def id(socket), do: \"user_socket:#{socket.assigns.user_id}\"\n #\n # Would allow you to broadcast a \"disconnect\" event and terminate\n # all active sockets and channels for a given user:\n #\n # GroupherServerWeb.Endpoint.broadcast(\"user_socket:#{user.id}\", \"disconnect\", %{})\n #\n # Returning `nil` makes this socket anonymous.\n def id(_socket), do: nil\nend"}]} \ No newline at end of file diff --git a/lib/groupher_server/cms/cms.ex b/lib/groupher_server/cms/cms.ex index 4d89e2ecd..fabe0203b 100644 --- a/lib/groupher_server/cms/cms.ex +++ b/lib/groupher_server/cms/cms.ex @@ -31,6 +31,8 @@ defmodule GroupherServer.CMS do # see https://github.com/elixir-lang/elixir/issues/5306 # Community CURD: editors, thread, tag + defdelegate create_community(args), to: CommunityCURD + defdelegate update_community(id, args), to: CommunityCURD # >> editor .. defdelegate update_editor(user, community, title), to: CommunityCURD # >> geo info .. diff --git a/lib/groupher_server/cms/community.ex b/lib/groupher_server/cms/community.ex index 1c3da40bf..c6cffecfc 100644 --- a/lib/groupher_server/cms/community.ex +++ b/lib/groupher_server/cms/community.ex @@ -9,6 +9,7 @@ defmodule GroupherServer.CMS.Community do alias GroupherServer.{Accounts, CMS} alias CMS.{ + Embeds, Category, CommunityThread, CommunitySubscriber, @@ -36,6 +37,7 @@ defmodule GroupherServer.CMS.Community do field(:index, :integer) field(:geo_info, :map) + embeds_one(:meta, Embeds.CommunityMeta, on_replace: :delete) belongs_to(:author, Accounts.User, foreign_key: :user_id) has_many(:threads, {"communities_threads", CommunityThread}) @@ -68,6 +70,7 @@ defmodule GroupherServer.CMS.Community do community |> cast(attrs, @optional_fields ++ @required_fields) |> validate_required(@required_fields) + |> cast_embed(:meta, with: &Embeds.CommunityMeta.changeset/2) |> validate_length(:title, min: 1, max: 30) |> foreign_key_constraint(:user_id) |> unique_constraint(:title, name: :communities_title_index) @@ -82,6 +85,7 @@ defmodule GroupherServer.CMS.Community do # |> unique_constraint(:title, name: :communities_title_index) community |> cast(attrs, @optional_fields ++ @required_fields) + |> cast_embed(:meta, with: &Embeds.CommunityMeta.changeset/2) |> validate_length(:title, min: 1, max: 30) |> foreign_key_constraint(:user_id) |> unique_constraint(:title, name: :communities_title_index) diff --git a/lib/groupher_server/cms/delegates/article_comment.ex b/lib/groupher_server/cms/delegates/article_comment.ex index 22edf5cab..716cb8c14 100644 --- a/lib/groupher_server/cms/delegates/article_comment.ex +++ b/lib/groupher_server/cms/delegates/article_comment.ex @@ -106,7 +106,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do add_participator_to_article(article, user) end) |> Repo.transaction() - |> upsert_comment_result() + |> result() end end @@ -130,7 +130,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do ORM.update(comment, %{body_html: @delete_hint, is_deleted: true}) end) |> Repo.transaction() - |> upsert_comment_result() + |> result() end # add participator to article-like content (Post, Job ...) and update count @@ -274,16 +274,16 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do Map.merge(paged_comments, %{entries: entries}) end - defp upsert_comment_result({:ok, %{create_article_comment: result}}), do: {:ok, result} - defp upsert_comment_result({:ok, %{delete_article_comment: result}}), do: {:ok, result} + defp result({:ok, %{create_article_comment: result}}), do: {:ok, result} + defp result({:ok, %{delete_article_comment: result}}), do: {:ok, result} - defp upsert_comment_result({:error, :create_article_comment, result, _steps}) do + defp result({:error, :create_article_comment, result, _steps}) do raise_error(:create_comment, result) end - defp upsert_comment_result({:error, :add_participator, result, _steps}) do + defp result({:error, :add_participator, result, _steps}) do {:error, result} end - defp upsert_comment_result({:error, _, result, _steps}), do: {:error, result} + defp result({:error, _, result, _steps}), do: {:error, result} end diff --git a/lib/groupher_server/cms/delegates/community_curd.ex b/lib/groupher_server/cms/delegates/community_curd.ex index fb10f3060..41ae53a55 100644 --- a/lib/groupher_server/cms/delegates/community_curd.ex +++ b/lib/groupher_server/cms/delegates/community_curd.ex @@ -9,9 +9,37 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do alias Helper.ORM alias Helper.QueryBuilder - alias GroupherServer.{Accounts, CMS} + alias GroupherServer.{Accounts, CMS, Repo} + + alias Accounts.User + + alias CMS.{ + Embeds, + ArticleTag, + Category, + Community, + CommunityEditor, + CommunitySubscriber, + Thread + } + + @default_meta Embeds.CommunityMeta.default_meta() + + def create_community(%{user_id: user_id} = args) do + with {:ok, author} <- ensure_author_exists(%User{id: user_id}) do + args = args |> Map.merge(%{user_id: author.user_id, meta: @default_meta}) + Community |> ORM.create(args) + end + end - alias CMS.{ArticleTag, Category, Community, CommunityEditor, CommunitySubscriber, Thread} + def update_community(id, args) do + with {:ok, community} <- ORM.find(Community, id) do + case community.meta do + nil -> ORM.update(community, args |> Map.merge(%{meta: @default_meta})) + _ -> ORM.update(community, args) + end + end + end @doc """ return paged community subscribers @@ -39,16 +67,16 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do @doc """ update community editor """ - def update_editor(%Community{id: community_id}, title, %Accounts.User{id: user_id}) do + def update_editor(%Community{id: community_id}, title, %User{id: user_id}) do clauses = ~m(user_id community_id)a with {:ok, _} <- CommunityEditor |> ORM.update_by(clauses, ~m(title)a) do - Accounts.User |> ORM.find(user_id) + User |> ORM.find(user_id) end end - def create_category(attrs, %Accounts.User{id: user_id}) do - with {:ok, author} <- ensure_author_exists(%Accounts.User{id: user_id}) do + def create_category(attrs, %User{id: user_id}) do + with {:ok, author} <- ensure_author_exists(%User{id: user_id}) do attrs = attrs |> Map.merge(%{author_id: author.id}) Category |> ORM.create(attrs) end diff --git a/lib/groupher_server/cms/embeds/community_meta.ex b/lib/groupher_server/cms/embeds/community_meta.ex new file mode 100644 index 000000000..f20377bbb --- /dev/null +++ b/lib/groupher_server/cms/embeds/community_meta.ex @@ -0,0 +1,43 @@ +defmodule GroupherServer.CMS.Embeds.CommunityMeta do + @moduledoc """ + general article meta info for article-like content, like post, job, works ... + """ + use Ecto.Schema + use Accessible + import Ecto.Changeset + + @optional_fields ~w(articles_count posts_count jobs_count repos_count subscribers_count subscribed_user_ids editors_count contributes_digest)a + + @default_meta %{ + articles_count: 0, + posts_count: 0, + jobs_count: 0, + repos_count: 0, + subscribers_count: 0, + subscribed_user_ids: [], + editors_count: 0, + contributes_digest: [] + } + + @doc "for test usage" + def default_meta(), do: @default_meta + + embedded_schema do + field(:articles_count, :integer, default: 0) + # TODO: use macros to extract + field(:posts_count, :integer, default: 0) + field(:jobs_count, :integer, default: 0) + field(:repos_count, :integer, default: 0) + + # 关注相关 + field(:subscribers_count, :integer, default: 0) + field(:subscribed_user_ids, {:array, :integer}, default: []) + + field(:editors_count, :integer, default: 0) + field(:contributes_digest, {:array, :integer}, default: []) + end + + def changeset(struct, params) do + struct |> cast(params, @optional_fields) + end +end diff --git a/lib/groupher_server_web/schema/account/account_misc.ex b/lib/groupher_server_web/schema/account/account_metrics.ex similarity index 98% rename from lib/groupher_server_web/schema/account/account_misc.ex rename to lib/groupher_server_web/schema/account/account_metrics.ex index 1ed6a7dea..436998bc7 100644 --- a/lib/groupher_server_web/schema/account/account_misc.ex +++ b/lib/groupher_server_web/schema/account/account_metrics.ex @@ -1,4 +1,4 @@ -defmodule GroupherServerWeb.Schema.Account.Misc do +defmodule GroupherServerWeb.Schema.Account.Metrics do @moduledoc false use Absinthe.Schema.Notation diff --git a/lib/groupher_server_web/schema/account/account_types.ex b/lib/groupher_server_web/schema/account/account_types.ex index b8f450e67..9cf7c2987 100644 --- a/lib/groupher_server_web/schema/account/account_types.ex +++ b/lib/groupher_server_web/schema/account/account_types.ex @@ -10,7 +10,7 @@ defmodule GroupherServerWeb.Schema.Account.Types do alias GroupherServer.Accounts alias GroupherServerWeb.Schema - import_types(Schema.Account.Misc) + import_types(Schema.Account.Metrics) object :session_state do field(:user, :user) diff --git a/lib/groupher_server_web/schema/cms/cms_misc.ex b/lib/groupher_server_web/schema/cms/cms_metrics.ex similarity index 99% rename from lib/groupher_server_web/schema/cms/cms_misc.ex rename to lib/groupher_server_web/schema/cms/cms_metrics.ex index 4d61a4be0..4f6be23a2 100644 --- a/lib/groupher_server_web/schema/cms/cms_misc.ex +++ b/lib/groupher_server_web/schema/cms/cms_metrics.ex @@ -1,4 +1,4 @@ -defmodule GroupherServerWeb.Schema.CMS.Misc do +defmodule GroupherServerWeb.Schema.CMS.Metrics do @moduledoc """ common metrics in queries """ diff --git a/lib/groupher_server_web/schema/cms/cms_types.ex b/lib/groupher_server_web/schema/cms/cms_types.ex index db4f1edb0..91cc334fd 100644 --- a/lib/groupher_server_web/schema/cms/cms_types.ex +++ b/lib/groupher_server_web/schema/cms/cms_types.ex @@ -11,7 +11,7 @@ defmodule GroupherServerWeb.Schema.CMS.Types do alias GroupherServer.CMS alias GroupherServerWeb.Schema - import_types(Schema.CMS.Misc) + import_types(Schema.CMS.Metrics) ###### # common stands for minimal info of the type @@ -261,6 +261,7 @@ defmodule GroupherServerWeb.Schema.CMS.Types do field(:threads, list_of(:thread_item), resolve: dataloader(CMS, :threads)) field(:categories, list_of(:category), resolve: dataloader(CMS, :categories)) + # field(:posts_count) @desc "total count of post contents" content_counts_field(:post, CMS.Post) diff --git a/priv/repo/migrations/20210519142252_add_meta_to_community.exs b/priv/repo/migrations/20210519142252_add_meta_to_community.exs new file mode 100644 index 000000000..00780d1e3 --- /dev/null +++ b/priv/repo/migrations/20210519142252_add_meta_to_community.exs @@ -0,0 +1,9 @@ +defmodule GroupherServer.Repo.Migrations.AddMetaToCommunity do + use Ecto.Migration + + def change do + alter table(:communities) do + add(:meta, :map) + end + end +end diff --git a/test/groupher_server/cms/community/community_meta_test.exs b/test/groupher_server/cms/community/community_meta_test.exs new file mode 100644 index 000000000..6b60dae87 --- /dev/null +++ b/test/groupher_server/cms/community/community_meta_test.exs @@ -0,0 +1,44 @@ +defmodule GroupherServer.Test.CMS.Community.CommunityMeta do + @moduledoc false + + use GroupherServer.TestTools + + import Helper.Utils, only: [strip_struct: 1] + + alias GroupherServer.CMS + alias CMS.{Community, Embeds} + + alias Helper.{ORM} + + @default_meta Embeds.CommunityMeta.default_meta() + + setup do + {:ok, user} = db_insert(:user) + {:ok, community} = db_insert(:community) + + community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) + + {:ok, ~m(user community community_attrs)a} + end + + describe "[cms community meta]" do + @tag :wip2 + test "created community should have default meta ", ~m(user community_attrs)a do + {:ok, community} = CMS.create_community(community_attrs) + assert community.meta |> strip_struct == @default_meta + end + + @tag :wip2 + test "update legacy community should add default meta", ~m(user community)a do + assert is_nil(community.meta) + + {:ok, community} = CMS.update_community(community.id, %{title: "new title"}) + assert community.meta |> strip_struct == @default_meta + end + + # @tag :wip2 + # test "create a post should inc posts_count in meta" , ~m(user)a do + + # end + end +end diff --git a/test/groupher_server_web/query/cms/cms_test.exs b/test/groupher_server_web/query/cms/cms_test.exs index 27f3972d3..56fadab75 100644 --- a/test/groupher_server_web/query/cms/cms_test.exs +++ b/test/groupher_server_web/query/cms/cms_test.exs @@ -68,9 +68,9 @@ defmodule GroupherServer.Test.Query.CMS.Basic do test "can get tags count ", ~m(community guest_conn user)a do article_tag_attrs = mock_attrs(:article_tag) - {:ok, article_tag} = CMS.create_article_tag(community, :post, article_tag_attrs, user) + {:ok, _article_tag} = CMS.create_article_tag(community, :post, article_tag_attrs, user) article_tag_attrs = mock_attrs(:article_tag) - {:ok, article_tag} = CMS.create_article_tag(community, :post, article_tag_attrs, user) + {:ok, _article_tag} = CMS.create_article_tag(community, :post, article_tag_attrs, user) variables = %{raw: community.raw} results = guest_conn |> query_result(@query, variables, "community") diff --git a/test/groupher_server_web/query/cms/paged_articles/paged_jobs_test.exs b/test/groupher_server_web/query/cms/paged_articles/paged_jobs_test.exs index 491db8310..9f71a0c3d 100644 --- a/test/groupher_server_web/query/cms/paged_articles/paged_jobs_test.exs +++ b/test/groupher_server_web/query/cms/paged_articles/paged_jobs_test.exs @@ -69,7 +69,6 @@ defmodule GroupherServer.Test.Query.PagedArticles.PagedJobs do assert results["entries"] |> List.first() |> Map.get("articleTags") |> is_list end - @tag :wip2 test "support article_tag filter", ~m(guest_conn user)a do {:ok, community} = db_insert(:community) job_attrs = mock_attrs(:job, %{community_id: community.id}) @@ -87,7 +86,6 @@ defmodule GroupherServer.Test.Query.PagedArticles.PagedJobs do assert exist_in?(article_tag, job["articleTags"], :string_key) end - @tag :wip2 test "support community filter", ~m(guest_conn user)a do {:ok, community} = db_insert(:community) diff --git a/test/groupher_server_web/query/cms/paged_articles/paged_repos_test.exs b/test/groupher_server_web/query/cms/paged_articles/paged_repos_test.exs index ec85a6e6b..1684aeaa6 100644 --- a/test/groupher_server_web/query/cms/paged_articles/paged_repos_test.exs +++ b/test/groupher_server_web/query/cms/paged_articles/paged_repos_test.exs @@ -66,7 +66,6 @@ defmodule GroupherServer.Test.Query.PagedArticles.PagedRepos do assert results["entries"] |> List.first() |> Map.get("articleTags") |> is_list end - @tag :wip2 test "support article_tag filter", ~m(guest_conn user)a do {:ok, community} = db_insert(:community) repo_attrs = mock_attrs(:repo, %{community_id: community.id}) @@ -84,7 +83,6 @@ defmodule GroupherServer.Test.Query.PagedArticles.PagedRepos do assert exist_in?(article_tag, repo["articleTags"], :string_key) end - @tag :wip2 test "support community filter", ~m(guest_conn user)a do {:ok, community} = db_insert(:community) From 04bd8a9717b1f138d80315efabd1e2dc6f5c1780 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Thu, 20 May 2021 12:45:40 +0800 Subject: [PATCH 04/23] refactor(community): wip --- .../cms/delegates/article_curd.ex | 5 +- .../cms/delegates/community_curd.ex | 29 +- .../cms/embeds/community_meta.ex | 48 ++- .../schema/account/account_misc.ex | 113 ++++++ .../schema/cms/cms_misc.ex | 337 ++++++++++++++++++ .../cms/community/community_meta_test.exs | 30 +- .../mutation/cms/articles/post_test.exs | 2 +- 7 files changed, 542 insertions(+), 22 deletions(-) create mode 100644 lib/groupher_server_web/schema/account/account_misc.ex create mode 100644 lib/groupher_server_web/schema/cms/cms_misc.ex diff --git a/lib/groupher_server/cms/delegates/article_curd.ex b/lib/groupher_server/cms/delegates/article_curd.ex index d7354787b..13a071bb8 100644 --- a/lib/groupher_server/cms/delegates/article_curd.ex +++ b/lib/groupher_server/cms/delegates/article_curd.ex @@ -19,7 +19,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do alias Accounts.User alias CMS.{Author, Community, PinnedArticle, Embeds, Delegate} - alias Delegate.{ArticleCommunity, ArticleTag} + alias Delegate.{ArticleCommunity, ArticleTag, CommunityCURD} alias Ecto.Multi @@ -135,6 +135,9 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do |> Multi.run(:set_article_tags, fn _, %{create_article: article} -> ArticleTag.set_article_tags(community, thread, article, attrs) end) + |> Multi.run(:update_community_article_count, fn _, _ -> + CommunityCURD.update_article_count(community, thread) + end) |> Multi.run(:mention_users, fn _, %{create_article: article} -> Delivery.mention_from_content(community.raw, thread, article, attrs, %User{id: uid}) {:ok, :pass} diff --git a/lib/groupher_server/cms/delegates/community_curd.ex b/lib/groupher_server/cms/delegates/community_curd.ex index 41ae53a55..6d3450bac 100644 --- a/lib/groupher_server/cms/delegates/community_curd.ex +++ b/lib/groupher_server/cms/delegates/community_curd.ex @@ -3,8 +3,9 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do community curd """ import Ecto.Query, warn: false - import Helper.Utils, only: [done: 1] + import Helper.Utils, only: [done: 1, strip_struct: 1, get_config: 2] import GroupherServer.CMS.Delegate.ArticleCURD, only: [ensure_author_exists: 1] + import GroupherServer.CMS.Helper.Matcher import ShortMaps alias Helper.ORM @@ -24,7 +25,11 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do } @default_meta Embeds.CommunityMeta.default_meta() + @article_threads get_config(:article, :article_threads) + @doc """ + create a community + """ def create_community(%{user_id: user_id} = args) do with {:ok, author} <- ensure_author_exists(%User{id: user_id}) do args = args |> Map.merge(%{user_id: author.user_id, meta: @default_meta}) @@ -32,6 +37,9 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do end end + @doc """ + update community + """ def update_community(id, args) do with {:ok, community} <- ORM.find(Community, id) do case community.meta do @@ -41,6 +49,25 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do end end + def update_article_count(%Community{} = community, thread) do + with {:ok, info} <- match(thread) do + count_query = + from(a in info.model, join: c in assoc(a, :communities), where: ^community.id == c.id) + + thread_article_count = Repo.aggregate(count_query, :count) + community_meta = if is_nil(community.meta), do: @default_meta, else: community.meta + + meta = community_meta |> Map.put(:"#{thread}s_count", thread_article_count) + meta = meta |> Map.put(:articles_count, recount_articles_count(meta)) |> strip_struct + + community |> ORM.update_meta(meta) + end + end + + defp recount_articles_count(meta) do + @article_threads |> Enum.reduce(0, &(&2 + Map.get(meta, :"#{&1}s_count"))) + end + @doc """ return paged community subscribers """ diff --git a/lib/groupher_server/cms/embeds/community_meta.ex b/lib/groupher_server/cms/embeds/community_meta.ex index f20377bbb..5e89f0fc9 100644 --- a/lib/groupher_server/cms/embeds/community_meta.ex +++ b/lib/groupher_server/cms/embeds/community_meta.ex @@ -1,33 +1,55 @@ +defmodule GroupherServer.CMS.Embeds.CommunityMeta.Macro do + @moduledoc false + + import Helper.Utils, only: [get_config: 2] + + @article_threads get_config(:article, :article_threads) + + defmacro thread_count_fields() do + @article_threads + |> Enum.map(fn thread -> + quote do + field(unquote(:"#{thread}s_count"), :integer, default: 0) + end + end) + end +end + defmodule GroupherServer.CMS.Embeds.CommunityMeta do @moduledoc """ - general article meta info for article-like content, like post, job, works ... + general community meta """ use Ecto.Schema use Accessible + import Ecto.Changeset + import Helper.Utils, only: [get_config: 2] + import GroupherServer.CMS.Embeds.CommunityMeta.Macro - @optional_fields ~w(articles_count posts_count jobs_count repos_count subscribers_count subscribed_user_ids editors_count contributes_digest)a + @article_threads get_config(:article, :article_threads) - @default_meta %{ + @general_options %{ articles_count: 0, - posts_count: 0, - jobs_count: 0, - repos_count: 0, subscribers_count: 0, - subscribed_user_ids: [], editors_count: 0, + subscribed_user_ids: [], contributes_digest: [] } - @doc "for test usage" - def default_meta(), do: @default_meta + @optional_fields Map.keys(@general_options) ++ Enum.map(@article_threads, &:"#{&1}s_count") + + def default_meta() do + threads_counts = + @article_threads + |> Enum.reduce([], &(&2 ++ ["#{&1}s_count": 0])) + |> Enum.into(%{}) + + @general_options |> Map.merge(threads_counts) + end embedded_schema do + thread_count_fields() field(:articles_count, :integer, default: 0) - # TODO: use macros to extract - field(:posts_count, :integer, default: 0) - field(:jobs_count, :integer, default: 0) - field(:repos_count, :integer, default: 0) # 关注相关 field(:subscribers_count, :integer, default: 0) diff --git a/lib/groupher_server_web/schema/account/account_misc.ex b/lib/groupher_server_web/schema/account/account_misc.ex new file mode 100644 index 000000000..1ed6a7dea --- /dev/null +++ b/lib/groupher_server_web/schema/account/account_misc.ex @@ -0,0 +1,113 @@ +defmodule GroupherServerWeb.Schema.Account.Misc do + @moduledoc false + + use Absinthe.Schema.Notation + import GroupherServerWeb.Schema.Helper.Fields + + @desc "article_filter doc" + input_object :paged_users_filter do + pagination_args() + # field(:when, :when_enum) + # field(:sort, :sort_enum) + # field(:community, :string) + end + + input_object :github_profile_input do + # is github_id in db table + field(:id, non_null(:string)) + field(:login, non_null(:string)) + field(:avatar_url, non_null(:string)) + field(:url, :string) + field(:html_url, :string) + field(:name, :string) + field(:company, :string) + field(:blog, :string) + field(:location, :string) + field(:email, :string) + field(:bio, :string) + field(:public_repos, :integer) + field(:public_gists, :integer) + end + + input_object :work_background_input do + field(:company, :string) + field(:title, :string) + end + + input_object :edu_background_input do + field(:school, :string) + field(:major, :string) + end + + input_object :user_profile_input do + field(:nickname, :string) + field(:bio, :string) + field(:sex, :string) + field(:location, :string) + field(:email, :string) + end + + input_object :social_input do + social_fields() + end + + enum :cus_banner_layout_num do + value(:digest) + value(:brief) + end + + enum :cus_contents_layout_num do + value(:digest) + value(:list) + end + + input_object :customization_input do + field(:theme, :string) + field(:community_chart, :boolean) + field(:brainwash_free, :boolean) + + field(:banner_layout, :cus_banner_layout_num) + field(:contents_layout, :cus_contents_layout_num) + field(:content_divider, :boolean) + field(:content_hover, :boolean) + field(:mark_viewed, :boolean) + field(:display_density, :string) + end + + input_object :community_index do + field(:community, :string) + field(:index, :integer) + end + + # see: https://github.com/absinthe-graphql/absinthe/issues/206 + # https://github.com/absinthe-graphql/absinthe/wiki/Scalar-Recipes + scalar :json, name: "Json" do + description(""" + The `Json` scalar type represents arbitrary json string data, represented as UTF-8 + character sequences. The Json type is most often used to represent a free-form + human-readable json string. + """) + + serialize(&encode/1) + parse(&decode/1) + end + + @spec decode(Absinthe.Blueprint.Input.String.t()) :: {:ok, term()} | :error + @spec decode(Absinthe.Blueprint.Input.Null.t()) :: {:ok, nil} + defp decode(%Absinthe.Blueprint.Input.String{value: value}) do + case Jason.decode(value) do + {:ok, result} -> {:ok, result} + _ -> :error + end + end + + defp decode(%Absinthe.Blueprint.Input.Null{}) do + {:ok, nil} + end + + defp decode(_) do + :error + end + + defp encode(value), do: value +end diff --git a/lib/groupher_server_web/schema/cms/cms_misc.ex b/lib/groupher_server_web/schema/cms/cms_misc.ex new file mode 100644 index 000000000..4d61a4be0 --- /dev/null +++ b/lib/groupher_server_web/schema/cms/cms_misc.ex @@ -0,0 +1,337 @@ +defmodule GroupherServerWeb.Schema.CMS.Misc do + @moduledoc """ + common metrics in queries + """ + use Absinthe.Schema.Notation + + import GroupherServerWeb.Schema.Helper.Fields + + alias GroupherServer.CMS + + @default_inner_page_size 5 + + enum(:post_thread, do: value(:post)) + enum(:job_thread, do: value(:job)) + enum(:repo_thread, do: value(:repo)) + + enum(:community_type, do: value(:community)) + enum(:comment_replies_type, do: value(:comment_replies_type)) + + enum(:count_type, do: value(:count)) + enum(:viewer_did_type, do: value(:viewer_did)) + + enum(:star_action, do: value(:star)) + enum(:comment_action, do: value(:comment)) + + enum :unique_type do + value(true) + value(false) + end + + enum :react_action do + value(:collect) + value(:upvote) + # value(:watch) + end + + enum :reactable_action do + value(:upvote) + # value(:collect) + # value(:watch) + end + + enum :cms_comment do + value(:post_comment) + end + + enum :thread do + value(:post) + value(:job) + value(:user) + value(:repo) + value(:wiki) + value(:cheatsheet) + # home community + value(:tech) + value(:city) + value(:share) + end + + enum :when_enum do + value(:today) + value(:this_week) + value(:this_month) + value(:this_year) + end + + enum :inserted_sort_enum do + value(:asc_inserted) + value(:desc_inserted) + end + + enum :thread_sort_enum do + value(:asc_index) + value(:desc_index) + value(:asc_inserted) + value(:desc_inserted) + end + + enum :sort_enum do + value(:most_views) + value(:most_updated) + value(:most_upvotes) + value(:most_stars) + value(:most_comments) + value(:least_views) + value(:least_updated) + value(:least_upvotes) + value(:least_stars) + value(:least_watched) + value(:least_comments) + value(:recent_updated) + end + + enum :repo_sort_enum do + value(:most_github_star) + value(:most_github_fork) + value(:most_github_watch) + value(:most_github_pr) + value(:most_github_issue) + value(:most_views) + value(:most_comments) + value(:recent_updated) + value(:most_upvotes) + end + + enum :length_enum do + value(:most_words) + value(:least_words) + end + + enum :rainbow_color do + value(:red) + value(:orange) + value(:yellow) + value(:green) + value(:cyan) + value(:blue) + value(:purple) + value(:dodgerblue) + value(:yellowgreen) + value(:brown) + value(:grey) + end + + @desc "emotion options of article" + enum(:article_emotion, do: emotion_enum()) + + @desc "emotion options of comment" + enum(:article_comment_emotion, do: comment_emotion_enum()) + + @desc "the filter mode for list comments" + enum :article_comments_mode do + value(:replies) + value(:timeline) + end + + @desc "inline members-like filter for dataloader usage" + input_object :members_filter do + field(:first, :integer, default_value: @default_inner_page_size) + end + + input_object :comments_filter do + pagination_args() + field(:sort, :inserted_sort_enum, default_value: :asc_inserted) + end + + input_object :communities_filter do + @desc "limit of records (default 20), if first > 30, only return 30 at most" + pagination_args() + field(:sort, :sort_enum) + field(:category, :string) + end + + input_object :threads_filter do + pagination_args() + field(:sort, :thread_sort_enum) + end + + input_object :article_tags_filter do + field(:community_id, :id) + field(:thread, :thread) + pagination_args() + end + + input_object :paged_filter do + @desc "limit of records (default 20), if first > 30, only return 30 at most" + pagination_args() + field(:sort, :sort_enum) + end + + @desc "article_filter doc" + input_object :article_filter do + @desc "limit of records (default 20), if first > 30, only return 30 at most" + field(:first, :integer) + + @desc "Matching a tag" + field(:article_tag, :string) + # field(:sort, :sort_input) + field(:when, :when_enum) + field(:sort, :sort_enum) + field(:length, :length_enum) + # @desc "Matching a tag" + # @desc "Added to the menu after this date" + # field(:added_after, :datetime) + end + + @desc "article_filter doc" + input_object :paged_article_filter do + @desc "limit of records (default 20), if first > 30, only return 30 at most" + pagination_args() + article_filter_fields() + field(:sort, :sort_enum) + end + + @desc "posts_filter doc" + input_object :paged_posts_filter do + @desc "limit of records (default 20), if first > 30, only return 30 at most" + pagination_args() + article_filter_fields() + field(:sort, :sort_enum) + end + + @desc "job_filter doc" + input_object :paged_jobs_filter do + @desc "limit of records (default 20), if first > 30, only return 30 at most" + pagination_args() + article_filter_fields() + field(:sort, :sort_enum) + + field(:salary, :string) + field(:exp, :string) + field(:education, :string) + field(:field, :string) + field(:finance, :string) + field(:scale, :string) + end + + @desc "article_filter doc" + input_object :paged_repos_filter do + @desc "limit of records (default 20), if first > 30, only return 30 at most" + pagination_args() + article_filter_fields() + + field(:sort, :repo_sort_enum) + end + + @desc "common filter for upvoted articles" + input_object :upvoted_articles_filter do + field(:thread, :thread) + pagination_args() + end + + @desc "common filter for collect folders" + input_object :collect_folders_filter do + field(:thread, :thread) + pagination_args() + end + + @desc "common filter for collect articles" + input_object :collected_articles_filter do + field(:thread, :thread) + pagination_args() + end + + @desc """ + cms github repo contribotor + """ + input_object :repo_contributor_input do + field(:avatar, :string) + field(:html_url, :string) + field(:nickname, :string) + end + + @desc """ + cms github repo contribotor, detail version + """ + input_object :github_contributor_input do + field(:github_id, non_null(:string)) + field(:avatar, non_null(:string)) + field(:html_url, non_null(:string)) + field(:nickname, non_null(:string)) + field(:bio, :string) + field(:location, :string) + field(:company, :string) + end + + @desc """ + cms github repo lang + """ + input_object :repo_lang_input do + field(:name, :string) + field(:color, :string) + end + + enum :report_content_type do + value(:post) + value(:job) + value(:repo) + value(:account) + value(:article_comment) + # value(:community) + end + + @desc """ + abuse report filter + """ + input_object :report_filter do + field(:content_type, :report_content_type) + field(:content_id, :id) + pagination_args() + # operate_user_id, + # min_case_count, + # max_case_count, + end + + @doc """ + only used for reaction result, like: upvote/collect/watch ... + """ + interface :article do + field(:id, :id) + # field(:title, :string) + + resolve_type(fn + %CMS.Post{}, _ -> :post + %CMS.Job{}, _ -> :job + %CMS.Repo{}, _ -> :repo + _, _ -> nil + end) + end + + # @desc """ + # The `DateTime` scalar type represents a date and time in the UTC + # timezone. The DateTime appears in a JSON response as an ISO8601 formatted + # string, including UTC timezone ("Z"). The parsed date and time string will + # be converted to UTC and any UTC offset other than 0 will be rejected. + # """ + # scalar :datetime, name: "DateTime" do + # serialize &DateTime.to_iso8601/1 + # parse &parse_datetime/1 + # end + + # @spec parse_datetime(Absinthe.Blueprint.Input.String.t) :: {:ok, DateTime.t} | :error + # @spec parse_datetime(Absinthe.Blueprint.Input.Null.t) :: {:ok, nil} + # defp parse_datetime(%Absinthe.Blueprint.Input.String{value: value}) do + # case DateTime.from_iso8601(value) do + # {:ok, datetime, 0} -> {:ok, datetime} + # {:ok, _datetime, _offset} -> :error + # _error -> :error + # end + # end + # defp parse_datetime(%Absinthe.Blueprint.Input.Null{}) do + # {:ok, nil} + # end + # defp parse_datetime(_) do + # :error + # end +end diff --git a/test/groupher_server/cms/community/community_meta_test.exs b/test/groupher_server/cms/community/community_meta_test.exs index 6b60dae87..130ba4ecf 100644 --- a/test/groupher_server/cms/community/community_meta_test.exs +++ b/test/groupher_server/cms/community/community_meta_test.exs @@ -15,20 +15,22 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do setup do {:ok, user} = db_insert(:user) {:ok, community} = db_insert(:community) + {:ok, community2} = db_insert(:community) + {:ok, community3} = db_insert(:community) community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) - {:ok, ~m(user community community_attrs)a} + {:ok, ~m(user community community2 community3 community_attrs)a} end describe "[cms community meta]" do - @tag :wip2 + @tag :wip test "created community should have default meta ", ~m(user community_attrs)a do {:ok, community} = CMS.create_community(community_attrs) assert community.meta |> strip_struct == @default_meta end - @tag :wip2 + @tag :wip test "update legacy community should add default meta", ~m(user community)a do assert is_nil(community.meta) @@ -36,9 +38,25 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do assert community.meta |> strip_struct == @default_meta end - # @tag :wip2 - # test "create a post should inc posts_count in meta" , ~m(user)a do + @tag :wip2 + test "create a post should inc posts_count in meta", + ~m(user community community2 community3)a do + post_attrs = mock_attrs(:post) + post_attrs2 = mock_attrs(:post) + + {:ok, _post} = CMS.create_article(community, :post, post_attrs, user) + {:ok, _post} = CMS.create_article(community, :post, post_attrs2, user) - # end + {:ok, _post} = CMS.create_article(community2, :post, post_attrs, user) + {:ok, _post} = CMS.create_article(community3, :post, post_attrs, user) + + {:ok, community} = ORM.find(Community, community.id) + assert community.meta.articles_count == 2 + assert community.meta.posts_count == 2 + + {:ok, community2} = ORM.find(Community, community2.id) + assert community2.meta.articles_count == 1 + assert community2.meta.posts_count == 1 + end end end diff --git a/test/groupher_server_web/mutation/cms/articles/post_test.exs b/test/groupher_server_web/mutation/cms/articles/post_test.exs index 7d2899caa..b6f60c03a 100644 --- a/test/groupher_server_web/mutation/cms/articles/post_test.exs +++ b/test/groupher_server_web/mutation/cms/articles/post_test.exs @@ -1,7 +1,7 @@ defmodule GroupherServer.Test.Mutation.Articles.Post do use GroupherServer.TestTools - alias Helper.{ORM, Utils} + alias Helper.ORM alias GroupherServer.{CMS, Delivery} setup do From 9ceb26d0ca82bd0f1dcb460f786d6a694f6ffd27 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Thu, 20 May 2021 14:59:07 +0800 Subject: [PATCH 05/23] refactor(community): more test --- .../cms/community/community_meta_test.exs | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/test/groupher_server/cms/community/community_meta_test.exs b/test/groupher_server/cms/community/community_meta_test.exs index 130ba4ecf..ef15255d7 100644 --- a/test/groupher_server/cms/community/community_meta_test.exs +++ b/test/groupher_server/cms/community/community_meta_test.exs @@ -58,5 +58,76 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do assert community2.meta.articles_count == 1 assert community2.meta.posts_count == 1 end + + @tag :wip2 + test "create a job should inc jobs_count in meta", + ~m(user community community2 community3)a do + job_attrs = mock_attrs(:job) + job_attrs2 = mock_attrs(:job) + + {:ok, _job} = CMS.create_article(community, :job, job_attrs, user) + {:ok, _job} = CMS.create_article(community, :job, job_attrs2, user) + + {:ok, _job} = CMS.create_article(community2, :job, job_attrs, user) + {:ok, _job} = CMS.create_article(community3, :job, job_attrs, user) + + {:ok, community} = ORM.find(Community, community.id) + assert community.meta.articles_count == 2 + assert community.meta.jobs_count == 2 + + {:ok, community2} = ORM.find(Community, community2.id) + assert community2.meta.articles_count == 1 + assert community2.meta.jobs_count == 1 + end + + @tag :wip2 + test "create a repo should inc repos_count in meta", + ~m(user community community2 community3)a do + repo_attrs = mock_attrs(:repo) + repo_attrs2 = mock_attrs(:repo) + + {:ok, _repo} = CMS.create_article(community, :repo, repo_attrs, user) + {:ok, _repo} = CMS.create_article(community, :repo, repo_attrs2, user) + + {:ok, _repo} = CMS.create_article(community2, :repo, repo_attrs, user) + {:ok, _repo} = CMS.create_article(community3, :repo, repo_attrs, user) + + {:ok, community} = ORM.find(Community, community.id) + assert community.meta.articles_count == 2 + assert community.meta.repos_count == 2 + + {:ok, community2} = ORM.find(Community, community2.id) + assert community2.meta.articles_count == 1 + assert community2.meta.repos_count == 1 + end + + @tag :wip2 + test "create a multi article should inc repos_count in meta", + ~m(user community community2)a do + post_attrs = mock_attrs(:post) + post_attrs2 = mock_attrs(:post) + + job_attrs = mock_attrs(:job) + repo_attrs = mock_attrs(:repo) + + {:ok, _} = CMS.create_article(community, :post, post_attrs, user) + {:ok, _} = CMS.create_article(community, :post, post_attrs2, user) + {:ok, _} = CMS.create_article(community, :job, job_attrs, user) + + {:ok, _} = CMS.create_article(community2, :job, job_attrs, user) + {:ok, _} = CMS.create_article(community2, :repo, repo_attrs, user) + + {:ok, community} = ORM.find(Community, community.id) + assert community.meta.articles_count == 3 + assert community.meta.posts_count == 2 + assert community.meta.jobs_count == 1 + assert community.meta.repos_count == 0 + + {:ok, community2} = ORM.find(Community, community2.id) + assert community2.meta.articles_count == 2 + assert community2.meta.posts_count == 0 + assert community2.meta.jobs_count == 1 + assert community2.meta.repos_count == 1 + end end end From 4508d66e1f8956aecb4f4cf3a2f895f3689f5554 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Thu, 20 May 2021 15:46:21 +0800 Subject: [PATCH 06/23] refactor(community): wip --- .../cms/delegates/article_curd.ex | 2 +- .../cms/delegates/community_curd.ex | 5 +- lib/groupher_server/cms/helper/loader.ex | 60 +----------- .../resolvers/cms_resolver.ex | 4 +- .../schema/Helper/fields.ex | 26 ++--- .../schema/cms/cms_types.ex | 24 +++-- .../cms/community/community_meta_test.exs | 2 +- .../query/cms/cms_counts_test.exs | 96 ------------------- .../query/cms/community_meta_test.exs | 52 ++++++++++ 9 files changed, 85 insertions(+), 186 deletions(-) delete mode 100644 test/groupher_server_web/query/cms/cms_counts_test.exs create mode 100644 test/groupher_server_web/query/cms/community_meta_test.exs diff --git a/lib/groupher_server/cms/delegates/article_curd.ex b/lib/groupher_server/cms/delegates/article_curd.ex index 13a071bb8..6415875da 100644 --- a/lib/groupher_server/cms/delegates/article_curd.ex +++ b/lib/groupher_server/cms/delegates/article_curd.ex @@ -136,7 +136,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do ArticleTag.set_article_tags(community, thread, article, attrs) end) |> Multi.run(:update_community_article_count, fn _, _ -> - CommunityCURD.update_article_count(community, thread) + CommunityCURD.update_community_meta(community, thread, :count) end) |> Multi.run(:mention_users, fn _, %{create_article: article} -> Delivery.mention_from_content(community.raw, thread, article, attrs, %User{id: uid}) diff --git a/lib/groupher_server/cms/delegates/community_curd.ex b/lib/groupher_server/cms/delegates/community_curd.ex index 6d3450bac..c9ab59f9e 100644 --- a/lib/groupher_server/cms/delegates/community_curd.ex +++ b/lib/groupher_server/cms/delegates/community_curd.ex @@ -49,7 +49,10 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do end end - def update_article_count(%Community{} = community, thread) do + @doc """ + update thread / article count in community meta + """ + def update_community_meta(%Community{} = community, thread, :count) do with {:ok, info} <- match(thread) do count_query = from(a in info.model, join: c in assoc(a, :communities), where: ^community.id == c.id) diff --git a/lib/groupher_server/cms/helper/loader.ex b/lib/groupher_server/cms/helper/loader.ex index 50c726585..d3c41074a 100644 --- a/lib/groupher_server/cms/helper/loader.ex +++ b/lib/groupher_server/cms/helper/loader.ex @@ -5,76 +5,20 @@ defmodule GroupherServer.CMS.Helper.Loader do import Ecto.Query, warn: false alias GroupherServer.{CMS, Repo} - alias CMS.Repo, as: CMSRepo alias CMS.{ Author, CommunityEditor, CommunitySubscriber, CommunityThread, - ArticleTag, - # POST - Post, PostComment, PostCommentLike, - PostCommentReply, - # JOB - Job - # JobStar, - # Repo, + PostCommentReply } alias Helper.QueryBuilder - def data, do: Dataloader.Ecto.new(Repo, query: &query/2, run_batch: &run_batch/5) - - # Big thanks: https://elixirforum.com/t/grouping-error-in-absinthe-dadaloader/13671/2 - # see also: https://github.com/absinthe-graphql/dataloader/issues/25 - def run_batch(Post, content_query, :posts_count, community_ids, repo_opts) do - query_content_counts(content_query, community_ids, repo_opts) - end - - def run_batch(Job, content_query, :jobs_count, community_ids, repo_opts) do - query_content_counts(content_query, community_ids, repo_opts) - end - - def run_batch(CMSRepo, content_query, :repos_count, community_ids, repo_opts) do - query_content_counts(content_query, community_ids, repo_opts) - end - - defp query_content_counts(content_query, community_ids, repo_opts) do - query = - from( - content in content_query, - join: c in assoc(content, :communities), - where: c.id in ^community_ids, - group_by: c.id, - select: {c.id, [count(content.id)]} - ) - - results = - query - |> Repo.all(repo_opts) - |> Map.new() - - for id <- community_ids, do: Map.get(results, id, [0]) - end - - def run_batch(PostComment, comment_query, :cp_count, post_ids, repo_opts) do - results = - comment_query - |> join(:inner, [c], a in assoc(c, :author)) - # |> distinct([c, a], a.id) - |> group_by([c, a], a.id) - |> group_by([c, a], c.post_id) - |> select([c, a], {c.post_id, count(a.id)}) - |> Repo.all(repo_opts) - |> Enum.group_by(fn {x, _} -> x end) - |> Enum.map(fn {x, y} -> {x, [length(y)]} end) - |> Map.new() - - for id <- post_ids, do: Map.get(results, id, [0]) - end + def data, do: Dataloader.Ecto.new(Repo, query: &query/2) def query(Author, _args) do from(a in Author, join: u in assoc(a, :user), select: u) diff --git a/lib/groupher_server_web/resolvers/cms_resolver.ex b/lib/groupher_server_web/resolvers/cms_resolver.ex index 63b51ce54..07f170c97 100644 --- a/lib/groupher_server_web/resolvers/cms_resolver.ex +++ b/lib/groupher_server_web/resolvers/cms_resolver.ex @@ -14,7 +14,9 @@ defmodule GroupherServerWeb.Resolvers.CMS do # ####################### # community .. # ####################### - def community(_root, %{id: id}, _info), do: Community |> ORM.find(id) + def community(_root, %{id: id}, _info) do + Community |> ORM.find(id) + end def community(_root, %{title: title}, _info) do case Community |> ORM.find_by(title: title) do diff --git a/lib/groupher_server_web/schema/Helper/fields.ex b/lib/groupher_server_web/schema/Helper/fields.ex index d3da69786..7acab3621 100644 --- a/lib/groupher_server_web/schema/Helper/fields.ex +++ b/lib/groupher_server_web/schema/Helper/fields.ex @@ -11,6 +11,8 @@ defmodule GroupherServerWeb.Schema.Helper.Fields do @supported_comment_emotions get_config(:article, :comment_supported_emotions) @supported_collect_folder_threads Accounts.CollectFolder.supported_threads() + @article_threads get_config(:article, :article_threads) + defmacro timestamp_fields do quote do field(:inserted_at, :datetime) @@ -67,25 +69,13 @@ defmodule GroupherServerWeb.Schema.Helper.Fields do alias GroupherServerWeb.Middleware, as: M alias GroupherServerWeb.Resolvers, as: R - # Big thanks: https://elixirforum.com/t/grouping-error-in-absinthe-dadaloader/13671/2 - # see also: https://github.com/absinthe-graphql/dataloader/issues/25 - defmacro content_counts_field(thread, schema) do - quote do - field unquote(String.to_atom("#{to_string(thread)}s_count")), :integer do - resolve(fn community, _args, %{context: %{loader: loader}} -> - loader - |> Dataloader.load(CMS, {:one, unquote(schema)}, [ - {unquote(String.to_atom("#{to_string(thread)}s_count")), community.id} - ]) - |> on_load(fn loader -> - {:ok, - Dataloader.get(loader, CMS, {:one, unquote(schema)}, [ - {unquote(String.to_atom("#{to_string(thread)}s_count")), community.id} - ])} - end) - end) + defmacro threads_count_fields() do + @article_threads + |> Enum.map(fn thread -> + quote do + field(unquote(:"#{thread}s_count"), :integer) end - end + end) end defmacro viewer_has_state_fields do diff --git a/lib/groupher_server_web/schema/cms/cms_types.ex b/lib/groupher_server_web/schema/cms/cms_types.ex index 91cc334fd..5bdadc8db 100644 --- a/lib/groupher_server_web/schema/cms/cms_types.ex +++ b/lib/groupher_server_web/schema/cms/cms_types.ex @@ -260,16 +260,7 @@ defmodule GroupherServerWeb.Schema.CMS.Types do field(:author, :user, resolve: dataloader(CMS, :author)) field(:threads, list_of(:thread_item), resolve: dataloader(CMS, :threads)) field(:categories, list_of(:category), resolve: dataloader(CMS, :categories)) - - # field(:posts_count) - @desc "total count of post contents" - content_counts_field(:post, CMS.Post) - - @desc "total count of job contents" - content_counts_field(:job, CMS.Job) - - @desc "total count of repo contents" - content_counts_field(:repo, CMS.Repo) + field(:meta, :community_meta) field :subscribers, list_of(:user) do arg(:filter, :members_filter) @@ -277,6 +268,7 @@ defmodule GroupherServerWeb.Schema.CMS.Types do resolve(dataloader(CMS, :subscribers)) end + # TODO: remove field :subscribers_count, :integer do arg(:count, :count_type, default_value: :count) arg(:type, :community_type, default_value: :community) @@ -284,6 +276,7 @@ defmodule GroupherServerWeb.Schema.CMS.Types do middleware(M.ConvertToInt) end + # TODO: remove field :viewer_has_subscribed, :boolean do arg(:viewer_did, :viewer_did_type, default_value: :viewer_did) @@ -306,10 +299,12 @@ defmodule GroupherServerWeb.Schema.CMS.Types do middleware(M.ConvertToInt) end + # TODO: remove field :threads_count, :integer do resolve(&R.CMS.threads_count/3) end + # TODO: remove field :article_tags_count, :integer do resolve(&R.CMS.article_tags_count/3) end @@ -505,4 +500,13 @@ defmodule GroupherServerWeb.Schema.CMS.Types do field(:is_comment_locked, :boolean) # field(:linked_posts_count, :integer) end + + object :community_meta do + field(:articles_count, :integer) + threads_count_fields() + + field(:subscribers_count, :integer) + field(:editors_count, :integer) + field(:contributes_digest, list_of(:integer)) + end end diff --git a/test/groupher_server/cms/community/community_meta_test.exs b/test/groupher_server/cms/community/community_meta_test.exs index ef15255d7..a5ce48647 100644 --- a/test/groupher_server/cms/community/community_meta_test.exs +++ b/test/groupher_server/cms/community/community_meta_test.exs @@ -23,7 +23,7 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do {:ok, ~m(user community community2 community3 community_attrs)a} end - describe "[cms community meta]" do + describe "[article count meta]" do @tag :wip test "created community should have default meta ", ~m(user community_attrs)a do {:ok, community} = CMS.create_community(community_attrs) diff --git a/test/groupher_server_web/query/cms/cms_counts_test.exs b/test/groupher_server_web/query/cms/cms_counts_test.exs deleted file mode 100644 index 5f0a66e77..000000000 --- a/test/groupher_server_web/query/cms/cms_counts_test.exs +++ /dev/null @@ -1,96 +0,0 @@ -defmodule GroupherServer.Test.Query.CMS.ContentCounts do - use GroupherServer.TestTools - - # alias GroupherServer.Accounts.User - alias GroupherServer.CMS - - setup do - guest_conn = simu_conn(:guest) - {:ok, community} = db_insert(:community) - {:ok, user} = db_insert(:user) - - {:ok, ~m(guest_conn community user)a} - end - - describe "[cms contents count]" do - @query """ - query($id: ID) { - community(id: $id) { - id - title - postsCount - } - } - """ - test "community have valid posts_count", ~m(guest_conn community user)a do - variables = %{id: community.id} - results = guest_conn |> query_result(@query, variables, "community") - assert results["postsCount"] == 0 - - count = Enum.random(1..20) - - Enum.reduce(1..count, [], fn _, acc -> - post_attrs = mock_attrs(:post, %{community_id: community.id}) - {:ok, post} = CMS.create_article(community, :post, post_attrs, user) - acc ++ [post] - end) - - results = guest_conn |> query_result(@query, variables, "community") - assert results["postsCount"] == count - end - - @query """ - query($id: ID) { - community(id: $id) { - id - title - jobsCount - } - } - """ - test "community have valid jobs_count", ~m(guest_conn community user)a do - variables = %{id: community.id} - results = guest_conn |> query_result(@query, variables, "community") - assert results["jobsCount"] == 0 - - count = Enum.random(1..20) - - Enum.reduce(1..count, [], fn _, acc -> - job_attrs = mock_attrs(:job, %{community_id: community.id}) - {:ok, job} = CMS.create_article(community, :job, job_attrs, user) - - acc ++ [job] - end) - - results = guest_conn |> query_result(@query, variables, "community") - assert results["jobsCount"] == count - end - - @query """ - query($id: ID) { - community(id: $id) { - id - title - reposCount - } - } - """ - test "community have valid repos_count", ~m(guest_conn community user)a do - variables = %{id: community.id} - results = guest_conn |> query_result(@query, variables, "community") - assert results["reposCount"] == 0 - - count = Enum.random(1..20) - - Enum.reduce(1..count, [], fn _, acc -> - repo_attrs = mock_attrs(:repo, %{community_id: community.id}) - {:ok, repo} = CMS.create_article(community, :repo, repo_attrs, user) - - acc ++ [repo] - end) - - results = guest_conn |> query_result(@query, variables, "community") - assert results["reposCount"] == count - end - end -end diff --git a/test/groupher_server_web/query/cms/community_meta_test.exs b/test/groupher_server_web/query/cms/community_meta_test.exs new file mode 100644 index 000000000..c91905211 --- /dev/null +++ b/test/groupher_server_web/query/cms/community_meta_test.exs @@ -0,0 +1,52 @@ +defmodule GroupherServer.Test.Query.CMS.CommunityMeta do + @moduledoc false + use GroupherServer.TestTools + + # alias GroupherServer.Accounts.User + alias GroupherServer.CMS + + setup do + guest_conn = simu_conn(:guest) + {:ok, user} = db_insert(:user) + + community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) + + {:ok, ~m(guest_conn community_attrs user)a} + end + + describe "[community meta]" do + @query """ + query($id: ID) { + community(id: $id) { + id + title + meta { + articlesCount + postsCount + jobsCount + reposCount + } + } + } + """ + + @tag :wip2 + test "community have valid [thread]s_count in meta", ~m(guest_conn community_attrs user)a do + {:ok, community} = CMS.create_community(community_attrs) + + {:ok, _post} = CMS.create_article(community, :post, mock_attrs(:post), user) + {:ok, _post} = CMS.create_article(community, :post, mock_attrs(:post), user) + {:ok, _post} = CMS.create_article(community, :job, mock_attrs(:job), user) + {:ok, _post} = CMS.create_article(community, :repo, mock_attrs(:repo), user) + + variables = %{id: community.id} + results = guest_conn |> query_result(@query, variables, "community") + + meta = results["meta"] + assert meta["articlesCount"] == 4 + assert meta["postsCount"] == 2 + assert meta["jobsCount"] == 1 + assert meta["reposCount"] == 1 + end + end +end From 74a6d85fa0c932542fecb137d0f2d88e498dbfde Mon Sep 17 00:00:00 2001 From: mydearxym Date: Thu, 20 May 2021 16:42:43 +0800 Subject: [PATCH 07/23] refactor(community): wip --- .../cms/delegates/article_curd.ex | 45 ++++++++++++++----- .../cms/delegates/community_curd.ex | 13 +++++- .../cms/community/community_meta_test.exs | 8 ++-- .../mutation/cms/flags/job_flag_test.exs | 44 ++++++++++++++++-- .../mutation/cms/flags/post_flag_test.exs | 42 ++++++++++++++++- .../mutation/cms/flags/repo_flag_test.exs | 44 ++++++++++++++++-- .../query/cms/community_meta_test.exs | 3 +- 7 files changed, 174 insertions(+), 25 deletions(-) diff --git a/lib/groupher_server/cms/delegates/article_curd.ex b/lib/groupher_server/cms/delegates/article_curd.ex index 6415875da..65bb5a324 100644 --- a/lib/groupher_server/cms/delegates/article_curd.ex +++ b/lib/groupher_server/cms/delegates/article_curd.ex @@ -48,7 +48,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do update_viewed_user_list(article, user_id) end) |> Repo.transaction() - |> read_result() + |> result() end end @@ -186,18 +186,42 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do ArticleCommunity.update_edit_status(update_article) end) |> Repo.transaction() - |> update_article_result() + |> result() end + @doc """ + mark delete falst for an anticle + """ def mark_delete_article(thread, id) do - with {:ok, info} <- match(thread) do - ORM.find_update(info.model, %{id: id, mark_delete: true}) + with {:ok, info} <- match(thread), + {:ok, article} <- ORM.find(info.model, id, preload: :communities) do + Multi.new() + |> Multi.run(:update_article, fn _, _ -> + ORM.update(article, %{mark_delete: true}) + end) + |> Multi.run(:update_community_article_count, fn _, _ -> + CommunityCURD.update_community_meta(article.communities, thread, :count) + end) + |> Repo.transaction() + |> result() end end + @doc """ + undo mark delete falst for an anticle + """ def undo_mark_delete_article(thread, id) do - with {:ok, info} <- match(thread) do - ORM.find_update(info.model, %{id: id, mark_delete: false}) + with {:ok, info} <- match(thread), + {:ok, article} <- ORM.find(info.model, id, preload: :communities) do + Multi.new() + |> Multi.run(:update_article, fn _, _ -> + ORM.update(article, %{mark_delete: false}) + end) + |> Multi.run(:update_community_article_count, fn _, _ -> + CommunityCURD.update_community_meta(article.communities, thread, :count) + end) + |> Repo.transaction() + |> result() end end @@ -409,12 +433,11 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do end end - defp update_article_result({:ok, %{update_edit_status: result}}), do: {:ok, result} - defp update_article_result({:error, :update_article, result, _steps}), do: {:error, result} - - defp read_result({:ok, %{inc_views: result}}), do: result |> done() + defp result({:ok, %{update_edit_status: result}}), do: {:ok, result} + defp result({:ok, %{update_article: result}}), do: {:ok, result} + defp result({:ok, %{inc_views: result}}), do: result |> done() - defp read_result({:error, _, result, _steps}) do + defp result({:error, _, result, _steps}) do {:error, result} end end diff --git a/lib/groupher_server/cms/delegates/community_curd.ex b/lib/groupher_server/cms/delegates/community_curd.ex index c9ab59f9e..37e7b5876 100644 --- a/lib/groupher_server/cms/delegates/community_curd.ex +++ b/lib/groupher_server/cms/delegates/community_curd.ex @@ -49,13 +49,24 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do end end + def update_community_meta(communities, thread, :count) when is_list(communities) do + case Enum.all?(communities, &({:ok, _} = update_community_meta(&1, thread, :count))) do + true -> {:ok, :pass} + false -> {:error, "update_community_meta"} + end + end + @doc """ update thread / article count in community meta """ def update_community_meta(%Community{} = community, thread, :count) do with {:ok, info} <- match(thread) do count_query = - from(a in info.model, join: c in assoc(a, :communities), where: ^community.id == c.id) + from(a in info.model, + join: c in assoc(a, :communities), + where: a.mark_delete == false, + where: c.id == ^community.id + ) thread_article_count = Repo.aggregate(count_query, :count) community_meta = if is_nil(community.meta), do: @default_meta, else: community.meta diff --git a/test/groupher_server/cms/community/community_meta_test.exs b/test/groupher_server/cms/community/community_meta_test.exs index a5ce48647..ed4742733 100644 --- a/test/groupher_server/cms/community/community_meta_test.exs +++ b/test/groupher_server/cms/community/community_meta_test.exs @@ -38,7 +38,7 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do assert community.meta |> strip_struct == @default_meta end - @tag :wip2 + @tag :wip3 test "create a post should inc posts_count in meta", ~m(user community community2 community3)a do post_attrs = mock_attrs(:post) @@ -59,7 +59,7 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do assert community2.meta.posts_count == 1 end - @tag :wip2 + @tag :wip3 test "create a job should inc jobs_count in meta", ~m(user community community2 community3)a do job_attrs = mock_attrs(:job) @@ -80,7 +80,7 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do assert community2.meta.jobs_count == 1 end - @tag :wip2 + @tag :wip3 test "create a repo should inc repos_count in meta", ~m(user community community2 community3)a do repo_attrs = mock_attrs(:repo) @@ -101,7 +101,7 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do assert community2.meta.repos_count == 1 end - @tag :wip2 + @tag :wip3 test "create a multi article should inc repos_count in meta", ~m(user community community2)a do post_attrs = mock_attrs(:post) diff --git a/test/groupher_server_web/mutation/cms/flags/job_flag_test.exs b/test/groupher_server_web/mutation/cms/flags/job_flag_test.exs index f1639a414..266ef03ce 100644 --- a/test/groupher_server_web/mutation/cms/flags/job_flag_test.exs +++ b/test/groupher_server_web/mutation/cms/flags/job_flag_test.exs @@ -2,6 +2,7 @@ defmodule GroupherServer.Test.Mutation.Flags.JobFlag do use GroupherServer.TestTools alias GroupherServer.CMS + alias Helper.ORM setup do {:ok, user} = db_insert(:user) @@ -13,7 +14,7 @@ defmodule GroupherServer.Test.Mutation.Flags.JobFlag do user_conn = simu_conn(:user) owner_conn = simu_conn(:user, user) - {:ok, ~m(user_conn guest_conn owner_conn community job)a} + {:ok, ~m(user_conn guest_conn owner_conn community user job)a} end describe "[mutation job flag curd]" do @@ -25,7 +26,6 @@ defmodule GroupherServer.Test.Mutation.Flags.JobFlag do } } """ - test "auth user can markDelete job", ~m(job)a do variables = %{id: job.id} @@ -38,6 +38,25 @@ defmodule GroupherServer.Test.Mutation.Flags.JobFlag do assert updated["markDelete"] == true end + @tag :wip2 + test "mark delete job should update job's communities meta count", ~m(user)a do + community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) + {:ok, community} = CMS.create_community(community_attrs) + {:ok, job} = CMS.create_article(community, :job, mock_attrs(:job), user) + + {:ok, community} = ORM.find(CMS.Community, community.id) + assert community.meta.jobs_count == 1 + + variables = %{id: job.id} + passport_rules = %{"job.mark_delete" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + + rule_conn |> mutation_result(@query, variables, "markDeleteJob") + + {:ok, community} = ORM.find(CMS.Community, community.id) + assert community.meta.jobs_count == 0 + end + test "unauth user markDelete job fails", ~m(user_conn guest_conn job)a do variables = %{id: job.id} rule_conn = simu_conn(:user, cms: %{"what.ever" => true}) @@ -55,7 +74,6 @@ defmodule GroupherServer.Test.Mutation.Flags.JobFlag do } } """ - test "auth user can undo markDelete job", ~m(job)a do variables = %{id: job.id} @@ -70,6 +88,26 @@ defmodule GroupherServer.Test.Mutation.Flags.JobFlag do assert updated["markDelete"] == false end + @tag :wip2 + test "undo mark delete job should update job's communities meta count", ~m(user)a do + community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) + {:ok, community} = CMS.create_community(community_attrs) + {:ok, job} = CMS.create_article(community, :job, mock_attrs(:job), user) + + {:ok, _} = CMS.mark_delete_article(:job, job.id) + + {:ok, community} = ORM.find(CMS.Community, community.id) + assert community.meta.jobs_count == 0 + + variables = %{id: job.id} + passport_rules = %{"job.undo_mark_delete" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + rule_conn |> mutation_result(@query, variables, "undoMarkDeleteJob") + + {:ok, community} = ORM.find(CMS.Community, community.id) + assert community.meta.jobs_count == 1 + end + test "unauth user undo markDelete job fails", ~m(user_conn guest_conn job)a do variables = %{id: job.id} rule_conn = simu_conn(:user, cms: %{"what.ever" => true}) diff --git a/test/groupher_server_web/mutation/cms/flags/post_flag_test.exs b/test/groupher_server_web/mutation/cms/flags/post_flag_test.exs index ec84802db..4f95ee0d6 100644 --- a/test/groupher_server_web/mutation/cms/flags/post_flag_test.exs +++ b/test/groupher_server_web/mutation/cms/flags/post_flag_test.exs @@ -2,6 +2,7 @@ defmodule GroupherServer.Test.Mutation.Flags.PostFlag do use GroupherServer.TestTools alias GroupherServer.CMS + alias Helper.ORM setup do {:ok, user} = db_insert(:user) @@ -13,7 +14,7 @@ defmodule GroupherServer.Test.Mutation.Flags.PostFlag do user_conn = simu_conn(:user) owner_conn = simu_conn(:user, user) - {:ok, ~m(user_conn guest_conn owner_conn community post)a} + {:ok, ~m(user_conn guest_conn owner_conn community user post)a} end describe "[mutation post flag curd]" do @@ -37,6 +38,25 @@ defmodule GroupherServer.Test.Mutation.Flags.PostFlag do assert updated["markDelete"] == true end + @tag :wip2 + test "mark delete post should update post's communities meta count", ~m(user)a do + community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) + {:ok, community} = CMS.create_community(community_attrs) + {:ok, post} = CMS.create_article(community, :post, mock_attrs(:post), user) + + {:ok, community} = ORM.find(CMS.Community, community.id) + assert community.meta.posts_count == 1 + + variables = %{id: post.id} + passport_rules = %{"post.mark_delete" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + + rule_conn |> mutation_result(@query, variables, "markDeletePost") + + {:ok, community} = ORM.find(CMS.Community, community.id) + assert community.meta.posts_count == 0 + end + test "unauth user markDelete post fails", ~m(user_conn guest_conn post)a do variables = %{id: post.id} rule_conn = simu_conn(:user, cms: %{"what.ever" => true}) @@ -68,6 +88,26 @@ defmodule GroupherServer.Test.Mutation.Flags.PostFlag do assert updated["markDelete"] == false end + @tag :wip2 + test "undo mark delete post should update post's communities meta count", ~m(user)a do + community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) + {:ok, community} = CMS.create_community(community_attrs) + {:ok, post} = CMS.create_article(community, :post, mock_attrs(:post), user) + + {:ok, _} = CMS.mark_delete_article(:post, post.id) + + {:ok, community} = ORM.find(CMS.Community, community.id) + assert community.meta.posts_count == 0 + + variables = %{id: post.id} + passport_rules = %{"post.undo_mark_delete" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + rule_conn |> mutation_result(@query, variables, "undoMarkDeletePost") + + {:ok, community} = ORM.find(CMS.Community, community.id) + assert community.meta.posts_count == 1 + end + test "unauth user undo markDelete post fails", ~m(user_conn guest_conn post)a do variables = %{id: post.id} rule_conn = simu_conn(:user, cms: %{"what.ever" => true}) diff --git a/test/groupher_server_web/mutation/cms/flags/repo_flag_test.exs b/test/groupher_server_web/mutation/cms/flags/repo_flag_test.exs index 57aeeaf02..c84a57a12 100644 --- a/test/groupher_server_web/mutation/cms/flags/repo_flag_test.exs +++ b/test/groupher_server_web/mutation/cms/flags/repo_flag_test.exs @@ -2,6 +2,7 @@ defmodule GroupherServer.Test.Mutation.Flags.RepoFlag do use GroupherServer.TestTools alias GroupherServer.CMS + alias Helper.ORM setup do {:ok, user} = db_insert(:user) @@ -13,7 +14,7 @@ defmodule GroupherServer.Test.Mutation.Flags.RepoFlag do user_conn = simu_conn(:user) owner_conn = simu_conn(:user, user) - {:ok, ~m(user_conn guest_conn owner_conn community repo)a} + {:ok, ~m(user_conn guest_conn owner_conn community user repo)a} end describe "[mutation repo flag curd]" do @@ -25,7 +26,6 @@ defmodule GroupherServer.Test.Mutation.Flags.RepoFlag do } } """ - test "auth user can markDelete repo", ~m(repo)a do variables = %{id: repo.id} @@ -38,6 +38,25 @@ defmodule GroupherServer.Test.Mutation.Flags.RepoFlag do assert updated["markDelete"] == true end + @tag :wip2 + test "mark delete repo should update repo's communities meta count", ~m(user)a do + community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) + {:ok, community} = CMS.create_community(community_attrs) + {:ok, repo} = CMS.create_article(community, :repo, mock_attrs(:repo), user) + + {:ok, community} = ORM.find(CMS.Community, community.id) + assert community.meta.repos_count == 1 + + variables = %{id: repo.id} + passport_rules = %{"repo.mark_delete" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + + rule_conn |> mutation_result(@query, variables, "markDeleteRepo") + + {:ok, community} = ORM.find(CMS.Community, community.id) + assert community.meta.repos_count == 0 + end + test "unauth user markDelete repo fails", ~m(user_conn guest_conn repo)a do variables = %{id: repo.id} rule_conn = simu_conn(:user, cms: %{"what.ever" => true}) @@ -55,7 +74,6 @@ defmodule GroupherServer.Test.Mutation.Flags.RepoFlag do } } """ - test "auth user can undo markDelete repo", ~m(repo)a do variables = %{id: repo.id} @@ -70,6 +88,26 @@ defmodule GroupherServer.Test.Mutation.Flags.RepoFlag do assert updated["markDelete"] == false end + @tag :wip2 + test "undo mark delete repo should update repo's communities meta count", ~m(user)a do + community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) + {:ok, community} = CMS.create_community(community_attrs) + {:ok, repo} = CMS.create_article(community, :repo, mock_attrs(:repo), user) + + {:ok, _} = CMS.mark_delete_article(:repo, repo.id) + + {:ok, community} = ORM.find(CMS.Community, community.id) + assert community.meta.repos_count == 0 + + variables = %{id: repo.id} + passport_rules = %{"repo.undo_mark_delete" => true} + rule_conn = simu_conn(:user, cms: passport_rules) + rule_conn |> mutation_result(@query, variables, "undoMarkDeleteRepo") + + {:ok, community} = ORM.find(CMS.Community, community.id) + assert community.meta.repos_count == 1 + end + test "unauth user undo markDelete repo fails", ~m(user_conn guest_conn repo)a do variables = %{id: repo.id} rule_conn = simu_conn(:user, cms: %{"what.ever" => true}) diff --git a/test/groupher_server_web/query/cms/community_meta_test.exs b/test/groupher_server_web/query/cms/community_meta_test.exs index c91905211..d04650e3b 100644 --- a/test/groupher_server_web/query/cms/community_meta_test.exs +++ b/test/groupher_server_web/query/cms/community_meta_test.exs @@ -29,8 +29,7 @@ defmodule GroupherServer.Test.Query.CMS.CommunityMeta do } } """ - - @tag :wip2 + @tag :wip3 test "community have valid [thread]s_count in meta", ~m(guest_conn community_attrs user)a do {:ok, community} = CMS.create_community(community_attrs) From d628c483c95db3cab03d572b53f8dec3e6d576e1 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Thu, 20 May 2021 17:06:15 +0800 Subject: [PATCH 08/23] refactor(community): add views to community --- lib/groupher_server/cms/cms.ex | 1 + lib/groupher_server/cms/community.ex | 1 + .../cms/delegates/community_curd.ex | 16 ++++++++++++++ .../resolvers/cms_resolver.ex | 20 ++++-------------- .../schema/cms/cms_types.ex | 1 + .../20210520084350_add_views_to_community.exs | 9 ++++++++ .../cms/community/community_meta_test.exs | 4 ++-- .../query/cms/cms_test.exs | 21 ++++++++++++------- 8 files changed, 47 insertions(+), 26 deletions(-) create mode 100644 priv/repo/migrations/20210520084350_add_views_to_community.exs diff --git a/lib/groupher_server/cms/cms.ex b/lib/groupher_server/cms/cms.ex index fabe0203b..c1e6b701a 100644 --- a/lib/groupher_server/cms/cms.ex +++ b/lib/groupher_server/cms/cms.ex @@ -31,6 +31,7 @@ defmodule GroupherServer.CMS do # see https://github.com/elixir-lang/elixir/issues/5306 # Community CURD: editors, thread, tag + defdelegate read_community(args), to: CommunityCURD defdelegate create_community(args), to: CommunityCURD defdelegate update_community(id, args), to: CommunityCURD # >> editor .. diff --git a/lib/groupher_server/cms/community.ex b/lib/groupher_server/cms/community.ex index c6cffecfc..88277edeb 100644 --- a/lib/groupher_server/cms/community.ex +++ b/lib/groupher_server/cms/community.ex @@ -36,6 +36,7 @@ defmodule GroupherServer.CMS.Community do field(:raw, :string) field(:index, :integer) field(:geo_info, :map) + field(:views, :integer) embeds_one(:meta, Embeds.CommunityMeta, on_replace: :delete) belongs_to(:author, Accounts.User, foreign_key: :user_id) diff --git a/lib/groupher_server/cms/delegates/community_curd.ex b/lib/groupher_server/cms/delegates/community_curd.ex index 37e7b5876..ee3034b37 100644 --- a/lib/groupher_server/cms/delegates/community_curd.ex +++ b/lib/groupher_server/cms/delegates/community_curd.ex @@ -27,6 +27,22 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do @default_meta Embeds.CommunityMeta.default_meta() @article_threads get_config(:article, :article_threads) + def read_community(%{id: id}), do: ORM.read(Community, id, inc: :views) + + def read_community(%{raw: raw} = clauses) do + case ORM.read_by(Community, clauses, inc: :views) do + {:ok, community} -> {:ok, community} + {:error, _} -> ORM.find_by(Community, aka: raw) + end + end + + def read_community(%{title: title} = clauses) do + case ORM.read_by(Community, clauses, inc: :views) do + {:ok, community} -> {:ok, community} + {:error, _} -> ORM.find_by(Community, aka: title) + end + end + @doc """ create a community """ diff --git a/lib/groupher_server_web/resolvers/cms_resolver.ex b/lib/groupher_server_web/resolvers/cms_resolver.ex index 07f170c97..ed6f42571 100644 --- a/lib/groupher_server_web/resolvers/cms_resolver.ex +++ b/lib/groupher_server_web/resolvers/cms_resolver.ex @@ -14,25 +14,13 @@ defmodule GroupherServerWeb.Resolvers.CMS do # ####################### # community .. # ####################### - def community(_root, %{id: id}, _info) do - Community |> ORM.find(id) - end - - def community(_root, %{title: title}, _info) do - case Community |> ORM.find_by(title: title) do - {:ok, community} -> {:ok, community} - {:error, _} -> Community |> ORM.find_by(aka: title) - end - end - - def community(_root, %{raw: raw}, _info) do - case Community |> ORM.find_by(raw: raw) do - {:ok, community} -> {:ok, community} - {:error, _} -> Community |> ORM.find_by(aka: raw) + def community(_root, args, _info) do + case Enum.empty?(args) do + false -> CMS.read_community(args) + true -> {:error, "please provide community id or title or raw"} end end - def community(_root, _args, _info), do: {:error, "please provide community id or title or raw"} def paged_communities(_root, ~m(filter)a, _info), do: Community |> ORM.find_all(filter) def create_community(_root, args, %{context: %{cur_user: user}}) do diff --git a/lib/groupher_server_web/schema/cms/cms_types.ex b/lib/groupher_server_web/schema/cms/cms_types.ex index 5bdadc8db..ab7be719d 100644 --- a/lib/groupher_server_web/schema/cms/cms_types.ex +++ b/lib/groupher_server_web/schema/cms/cms_types.ex @@ -261,6 +261,7 @@ defmodule GroupherServerWeb.Schema.CMS.Types do field(:threads, list_of(:thread_item), resolve: dataloader(CMS, :threads)) field(:categories, list_of(:category), resolve: dataloader(CMS, :categories)) field(:meta, :community_meta) + field(:views, :integer) field :subscribers, list_of(:user) do arg(:filter, :members_filter) diff --git a/priv/repo/migrations/20210520084350_add_views_to_community.exs b/priv/repo/migrations/20210520084350_add_views_to_community.exs new file mode 100644 index 000000000..c6e580c2c --- /dev/null +++ b/priv/repo/migrations/20210520084350_add_views_to_community.exs @@ -0,0 +1,9 @@ +defmodule GroupherServer.Repo.Migrations.AddViewsToCommunity do + use Ecto.Migration + + def change do + alter table(:communities) do + add(:views, :integer, default: 0) + end + end +end diff --git a/test/groupher_server/cms/community/community_meta_test.exs b/test/groupher_server/cms/community/community_meta_test.exs index ed4742733..3652688d5 100644 --- a/test/groupher_server/cms/community/community_meta_test.exs +++ b/test/groupher_server/cms/community/community_meta_test.exs @@ -25,13 +25,13 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do describe "[article count meta]" do @tag :wip - test "created community should have default meta ", ~m(user community_attrs)a do + test "created community should have default meta ", ~m(community_attrs)a do {:ok, community} = CMS.create_community(community_attrs) assert community.meta |> strip_struct == @default_meta end @tag :wip - test "update legacy community should add default meta", ~m(user community)a do + test "update legacy community should add default meta", ~m(community)a do assert is_nil(community.meta) {:ok, community} = CMS.update_community(community.id, %{title: "new title"}) diff --git a/test/groupher_server_web/query/cms/cms_test.exs b/test/groupher_server_web/query/cms/cms_test.exs index 56fadab75..aa2088ff5 100644 --- a/test/groupher_server_web/query/cms/cms_test.exs +++ b/test/groupher_server_web/query/cms/cms_test.exs @@ -4,6 +4,7 @@ defmodule GroupherServer.Test.Query.CMS.Basic do alias GroupherServer.Accounts.User alias GroupherServer.CMS alias CMS.{Community, Category} + alias Helper.ORM setup do guest_conn = simu_conn(:guest) @@ -21,6 +22,7 @@ defmodule GroupherServer.Test.Query.CMS.Basic do title threadsCount articleTagsCount + views threads { id raw @@ -29,17 +31,20 @@ defmodule GroupherServer.Test.Query.CMS.Basic do } } """ + @tag :wip2 + test "views should work", ~m(guest_conn)a do + {:ok, community} = db_insert(:community) - # @tag :cache - # test "make sure apollo cache works", ~m(guest_conn)a do - # {:ok, _community} = db_insert(:community, %{raw: "cacheme"}) + variables = %{id: community.id} + guest_conn |> query_result(@query, variables, "community") - # variables = %{raw: "cacheme"} - # guest_conn |> query_result(@query, variables, "community") + {:ok, community} = ORM.find(Community, community.id) + assert community.views == 1 + guest_conn |> query_result(@query, variables, "community") - # variables = %{raw: "cacheme"} - # guest_conn |> query_result(@query, variables, "community") - # end + {:ok, community} = ORM.find(Community, community.id) + assert community.views == 2 + end test "can get from alias community name", ~m(guest_conn)a do {:ok, _community} = db_insert(:community, %{raw: "kubernetes", aka: "k8s"}) From 176c5a2d2a899e0cefbb5869169b64c8c43cf4d0 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Thu, 20 May 2021 17:23:45 +0800 Subject: [PATCH 09/23] refactor(community): move articles_count to outside field --- lib/groupher_server/cms/community.ex | 4 ++++ .../cms/delegates/community_curd.ex | 8 ++++--- .../cms/embeds/community_meta.ex | 2 -- .../schema/cms/cms_types.ex | 10 ++++---- ...10520090847_add_xxx_count_to_community.exs | 11 +++++++++ .../cms/community/community_meta_test.exs | 24 +++++++++---------- .../query/cms/community_meta_test.exs | 6 ++--- 7 files changed, 40 insertions(+), 25 deletions(-) create mode 100644 priv/repo/migrations/20210520090847_add_xxx_count_to_community.exs diff --git a/lib/groupher_server/cms/community.ex b/lib/groupher_server/cms/community.ex index 88277edeb..73789cc78 100644 --- a/lib/groupher_server/cms/community.ex +++ b/lib/groupher_server/cms/community.ex @@ -45,6 +45,10 @@ defmodule GroupherServer.CMS.Community do has_many(:subscribers, {"communities_subscribers", CommunitySubscriber}) has_many(:editors, {"communities_editors", CommunityEditor}) + field(:articles_count, :integer, default: 0) + field(:editors_count, :integer, default: 0) + field(:subscribers_count, :integer, default: 0) + has_one(:wiki, CommunityWiki) has_one(:cheatsheet, CommunityCheatsheet) diff --git a/lib/groupher_server/cms/delegates/community_curd.ex b/lib/groupher_server/cms/delegates/community_curd.ex index ee3034b37..366353b15 100644 --- a/lib/groupher_server/cms/delegates/community_curd.ex +++ b/lib/groupher_server/cms/delegates/community_curd.ex @@ -87,10 +87,12 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do thread_article_count = Repo.aggregate(count_query, :count) community_meta = if is_nil(community.meta), do: @default_meta, else: community.meta - meta = community_meta |> Map.put(:"#{thread}s_count", thread_article_count) - meta = meta |> Map.put(:articles_count, recount_articles_count(meta)) |> strip_struct + meta = community_meta |> Map.put(:"#{thread}s_count", thread_article_count) |> strip_struct - community |> ORM.update_meta(meta) + community + |> Ecto.Changeset.change(%{articles_count: recount_articles_count(meta)}) + |> Ecto.Changeset.put_embed(:meta, meta) + |> Repo.update() end end diff --git a/lib/groupher_server/cms/embeds/community_meta.ex b/lib/groupher_server/cms/embeds/community_meta.ex index 5e89f0fc9..53039f17e 100644 --- a/lib/groupher_server/cms/embeds/community_meta.ex +++ b/lib/groupher_server/cms/embeds/community_meta.ex @@ -29,7 +29,6 @@ defmodule GroupherServer.CMS.Embeds.CommunityMeta do @article_threads get_config(:article, :article_threads) @general_options %{ - articles_count: 0, subscribers_count: 0, editors_count: 0, subscribed_user_ids: [], @@ -49,7 +48,6 @@ defmodule GroupherServer.CMS.Embeds.CommunityMeta do embedded_schema do thread_count_fields() - field(:articles_count, :integer, default: 0) # 关注相关 field(:subscribers_count, :integer, default: 0) diff --git a/lib/groupher_server_web/schema/cms/cms_types.ex b/lib/groupher_server_web/schema/cms/cms_types.ex index ab7be719d..3ca1b7957 100644 --- a/lib/groupher_server_web/schema/cms/cms_types.ex +++ b/lib/groupher_server_web/schema/cms/cms_types.ex @@ -263,6 +263,10 @@ defmodule GroupherServerWeb.Schema.CMS.Types do field(:meta, :community_meta) field(:views, :integer) + field(:articles_count, :integer) + field(:subscribers_count, :integer) + field(:editors_count, :integer) + field :subscribers, list_of(:user) do arg(:filter, :members_filter) middleware(M.PageSizeProof) @@ -503,11 +507,7 @@ defmodule GroupherServerWeb.Schema.CMS.Types do end object :community_meta do - field(:articles_count, :integer) threads_count_fields() - - field(:subscribers_count, :integer) - field(:editors_count, :integer) - field(:contributes_digest, list_of(:integer)) + # field(:contributes_digest, list_of(:integer)) end end diff --git a/priv/repo/migrations/20210520090847_add_xxx_count_to_community.exs b/priv/repo/migrations/20210520090847_add_xxx_count_to_community.exs new file mode 100644 index 000000000..a2c820d27 --- /dev/null +++ b/priv/repo/migrations/20210520090847_add_xxx_count_to_community.exs @@ -0,0 +1,11 @@ +defmodule GroupherServer.Repo.Migrations.AddXxxCountToCommunity do + use Ecto.Migration + + def change do + alter table(:communities) do + add(:articles_count, :integer, default: 0) + add(:editors_count, :integer, default: 0) + add(:subscribers_count, :integer, default: 0) + end + end +end diff --git a/test/groupher_server/cms/community/community_meta_test.exs b/test/groupher_server/cms/community/community_meta_test.exs index 3652688d5..1630848d7 100644 --- a/test/groupher_server/cms/community/community_meta_test.exs +++ b/test/groupher_server/cms/community/community_meta_test.exs @@ -38,7 +38,7 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do assert community.meta |> strip_struct == @default_meta end - @tag :wip3 + @tag :wip2 test "create a post should inc posts_count in meta", ~m(user community community2 community3)a do post_attrs = mock_attrs(:post) @@ -51,15 +51,15 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do {:ok, _post} = CMS.create_article(community3, :post, post_attrs, user) {:ok, community} = ORM.find(Community, community.id) - assert community.meta.articles_count == 2 + assert community.articles_count == 2 assert community.meta.posts_count == 2 {:ok, community2} = ORM.find(Community, community2.id) - assert community2.meta.articles_count == 1 + assert community2.articles_count == 1 assert community2.meta.posts_count == 1 end - @tag :wip3 + @tag :wip2 test "create a job should inc jobs_count in meta", ~m(user community community2 community3)a do job_attrs = mock_attrs(:job) @@ -72,15 +72,15 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do {:ok, _job} = CMS.create_article(community3, :job, job_attrs, user) {:ok, community} = ORM.find(Community, community.id) - assert community.meta.articles_count == 2 + assert community.articles_count == 2 assert community.meta.jobs_count == 2 {:ok, community2} = ORM.find(Community, community2.id) - assert community2.meta.articles_count == 1 + assert community2.articles_count == 1 assert community2.meta.jobs_count == 1 end - @tag :wip3 + @tag :wip2 test "create a repo should inc repos_count in meta", ~m(user community community2 community3)a do repo_attrs = mock_attrs(:repo) @@ -93,15 +93,15 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do {:ok, _repo} = CMS.create_article(community3, :repo, repo_attrs, user) {:ok, community} = ORM.find(Community, community.id) - assert community.meta.articles_count == 2 + assert community.articles_count == 2 assert community.meta.repos_count == 2 {:ok, community2} = ORM.find(Community, community2.id) - assert community2.meta.articles_count == 1 + assert community2.articles_count == 1 assert community2.meta.repos_count == 1 end - @tag :wip3 + @tag :wip2 test "create a multi article should inc repos_count in meta", ~m(user community community2)a do post_attrs = mock_attrs(:post) @@ -118,13 +118,13 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do {:ok, _} = CMS.create_article(community2, :repo, repo_attrs, user) {:ok, community} = ORM.find(Community, community.id) - assert community.meta.articles_count == 3 + assert community.articles_count == 3 assert community.meta.posts_count == 2 assert community.meta.jobs_count == 1 assert community.meta.repos_count == 0 {:ok, community2} = ORM.find(Community, community2.id) - assert community2.meta.articles_count == 2 + assert community2.articles_count == 2 assert community2.meta.posts_count == 0 assert community2.meta.jobs_count == 1 assert community2.meta.repos_count == 1 diff --git a/test/groupher_server_web/query/cms/community_meta_test.exs b/test/groupher_server_web/query/cms/community_meta_test.exs index d04650e3b..af2c787a2 100644 --- a/test/groupher_server_web/query/cms/community_meta_test.exs +++ b/test/groupher_server_web/query/cms/community_meta_test.exs @@ -20,8 +20,8 @@ defmodule GroupherServer.Test.Query.CMS.CommunityMeta do community(id: $id) { id title + articlesCount meta { - articlesCount postsCount jobsCount reposCount @@ -29,7 +29,7 @@ defmodule GroupherServer.Test.Query.CMS.CommunityMeta do } } """ - @tag :wip3 + @tag :wip2 test "community have valid [thread]s_count in meta", ~m(guest_conn community_attrs user)a do {:ok, community} = CMS.create_community(community_attrs) @@ -42,7 +42,7 @@ defmodule GroupherServer.Test.Query.CMS.CommunityMeta do results = guest_conn |> query_result(@query, variables, "community") meta = results["meta"] - assert meta["articlesCount"] == 4 + assert results["articlesCount"] == 4 assert meta["postsCount"] == 2 assert meta["jobsCount"] == 1 assert meta["reposCount"] == 1 From a1bc01fdc4ed4c65b9058ecc889b93a8f313b21b Mon Sep 17 00:00:00 2001 From: mydearxym Date: Thu, 20 May 2021 22:18:27 +0800 Subject: [PATCH 10/23] refactor(test): skip old post comment test --- .../mutation/cms/articles/job_test.exs | 2 +- .../query/cms/old_post_comment_test.exs | 129 +++++++++--------- 2 files changed, 66 insertions(+), 65 deletions(-) diff --git a/test/groupher_server_web/mutation/cms/articles/job_test.exs b/test/groupher_server_web/mutation/cms/articles/job_test.exs index 274c9b360..0ba8bd16a 100644 --- a/test/groupher_server_web/mutation/cms/articles/job_test.exs +++ b/test/groupher_server_web/mutation/cms/articles/job_test.exs @@ -191,7 +191,7 @@ defmodule GroupherServer.Test.Mutation.Articles.Job do body: "updated body #{unique_num}" } - updated = rule_conn |> mutation_result(@query, variables, "updateJob", :debug) + updated = rule_conn |> mutation_result(@query, variables, "updateJob") assert updated["id"] == to_string(job.id) end diff --git a/test/groupher_server_web/query/cms/old_post_comment_test.exs b/test/groupher_server_web/query/cms/old_post_comment_test.exs index 2718ed48b..0d41ef65f 100644 --- a/test/groupher_server_web/query/cms/old_post_comment_test.exs +++ b/test/groupher_server_web/query/cms/old_post_comment_test.exs @@ -39,96 +39,97 @@ defmodule GroupherServer.Test.Query.OldPostComment do } } """ - test "can get comments participators of a post", ~m(user guest_conn)a do - {:ok, user2} = db_insert(:user) - {:ok, community} = db_insert(:community) - {:ok, post} = CMS.create_article(community, :post, mock_attrs(:post), user) + # test "can get comments participators of a post", ~m(user guest_conn)a do + # {:ok, user2} = db_insert(:user) - variables = %{filter: %{community: community.raw}} - results = guest_conn |> query_result(@query, variables, "pagedPosts") + # {:ok, community} = db_insert(:community) + # {:ok, post} = CMS.create_article(community, :post, mock_attrs(:post), user) - comments_participators_count = - results["entries"] |> List.first() |> Map.get("commentsParticipatorsCount") + # variables = %{filter: %{community: community.raw}} + # results = guest_conn |> query_result(@query, variables, "pagedPosts") - assert comments_participators_count == 0 + # comments_participators_count = + # results["entries"] |> List.first() |> Map.get("commentsParticipatorsCount") - body = "this is a test comment" + # assert comments_participators_count == 0 - assert {:ok, _comment} = - CMS.create_comment(:post, post.id, %{community: community.raw, body: body}, user) + # body = "this is a test comment" - assert {:ok, _comment} = - CMS.create_comment(:post, post.id, %{community: community.raw, body: body}, user) + # assert {:ok, _comment} = + # CMS.create_comment(:post, post.id, %{community: community.raw, body: body}, user) - assert {:ok, _comment} = - CMS.create_comment(:post, post.id, %{community: community.raw, body: body}, user2) + # assert {:ok, _comment} = + # CMS.create_comment(:post, post.id, %{community: community.raw, body: body}, user) - variables = %{filter: %{community: community.raw}} - results = guest_conn |> query_result(@query, variables, "pagedPosts") + # assert {:ok, _comment} = + # CMS.create_comment(:post, post.id, %{community: community.raw, body: body}, user2) - comments_participators_count = - results["entries"] |> List.first() |> Map.get("commentsParticipatorsCount") + # variables = %{filter: %{community: community.raw}} + # results = guest_conn |> query_result(@query, variables, "pagedPosts") - comments_count = results["entries"] |> List.first() |> Map.get("commentsCount") + # comments_participators_count = + # results["entries"] |> List.first() |> Map.get("commentsParticipatorsCount") - assert comments_participators_count == 2 - assert comments_count == 3 + # comments_count = results["entries"] |> List.first() |> Map.get("commentsCount") - comments_participators = - results["entries"] |> List.first() |> Map.get("commentsParticipators") + # assert comments_participators_count == 2 + # assert comments_count == 3 - assert comments_participators |> Enum.any?(&(&1["id"] == to_string(user.id))) - assert comments_participators |> Enum.any?(&(&1["id"] == to_string(user2.id))) - end + # comments_participators = + # results["entries"] |> List.first() |> Map.get("commentsParticipators") - test "can get comments participators of a post with multi user", ~m(user guest_conn)a do - body = "this is a test comment" - {:ok, community} = db_insert(:community) - {:ok, post1} = CMS.create_article(community, :post, mock_attrs(:post), user) - {:ok, post2} = CMS.create_article(community, :post, mock_attrs(:post), user) + # assert comments_participators |> Enum.any?(&(&1["id"] == to_string(user.id))) + # assert comments_participators |> Enum.any?(&(&1["id"] == to_string(user2.id))) + # end - {:ok, users_list} = db_insert_multi(:user, 10) - {:ok, users_list2} = db_insert_multi(:user, 10) + # test "can get comments participators of a post with multi user", ~m(user guest_conn)a do + # body = "this is a test comment" + # {:ok, community} = db_insert(:community) + # {:ok, post1} = CMS.create_article(community, :post, mock_attrs(:post), user) + # {:ok, post2} = CMS.create_article(community, :post, mock_attrs(:post), user) - Enum.each( - users_list, - &CMS.create_comment(:post, post1.id, %{community: community.raw, body: body}, &1) - ) + # {:ok, users_list} = db_insert_multi(:user, 10) + # {:ok, users_list2} = db_insert_multi(:user, 10) - Enum.each( - users_list2, - &CMS.create_comment(:post, post2.id, %{community: community.raw, body: body}, &1) - ) + # Enum.each( + # users_list, + # &CMS.create_comment(:post, post1.id, %{community: community.raw, body: body}, &1) + # ) - variables = %{filter: %{community: community.raw}} - results = guest_conn |> query_result(@query, variables, "pagedPosts") + # Enum.each( + # users_list2, + # &CMS.create_comment(:post, post2.id, %{community: community.raw, body: body}, &1) + # ) - assert results["entries"] |> List.first() |> Map.get("commentsParticipators") |> length == 5 - assert results["entries"] |> List.first() |> Map.get("commentsParticipatorsCount") == 10 + # variables = %{filter: %{community: community.raw}} + # results = guest_conn |> query_result(@query, variables, "pagedPosts") - assert results["entries"] |> List.last() |> Map.get("commentsParticipators") |> length == 5 - assert results["entries"] |> List.last() |> Map.get("commentsParticipatorsCount") == 10 - end + # assert results["entries"] |> List.first() |> Map.get("commentsParticipators") |> length == 5 + # assert results["entries"] |> List.first() |> Map.get("commentsParticipatorsCount") == 10 - test "can get paged commetns participators of a post", ~m(user guest_conn)a do - body = "this is a test comment" + # assert results["entries"] |> List.last() |> Map.get("commentsParticipators") |> length == 5 + # assert results["entries"] |> List.last() |> Map.get("commentsParticipatorsCount") == 10 + # end - {:ok, community} = db_insert(:community) - {:ok, post} = CMS.create_article(community, :post, mock_attrs(:post), user) - {:ok, users_list} = db_insert_multi(:user, 10) + # test "can get paged commetns participators of a post", ~m(user guest_conn)a do + # body = "this is a test comment" - Enum.each( - users_list, - &CMS.create_comment(:post, post.id, %{community: community.raw, body: body}, &1) - ) + # {:ok, community} = db_insert(:community) + # {:ok, post} = CMS.create_article(community, :post, mock_attrs(:post), user) + # {:ok, users_list} = db_insert_multi(:user, 10) - variables = %{filter: %{community: community.raw}} - results = guest_conn |> query_result(@query, variables, "pagedPosts") - participators = results["entries"] |> List.first() |> Map.get("pagedCommentsParticipators") + # Enum.each( + # users_list, + # &CMS.create_comment(:post, post.id, %{community: community.raw, body: body}, &1) + # ) - assert participators["totalCount"] == 10 - end + # variables = %{filter: %{community: community.raw}} + # results = guest_conn |> query_result(@query, variables, "pagedPosts") + # participators = results["entries"] |> List.first() |> Map.get("pagedCommentsParticipators") + + # assert participators["totalCount"] == 10 + # end end @query """ From 82528883693b8ef473ee22a347317e5295f82c6b Mon Sep 17 00:00:00 2001 From: mydearxym Date: Thu, 20 May 2021 22:31:34 +0800 Subject: [PATCH 11/23] chore: fmt --- .../cms/delegates/community_operation.ex | 40 ++++++------------- 1 file changed, 12 insertions(+), 28 deletions(-) diff --git a/lib/groupher_server/cms/delegates/community_operation.ex b/lib/groupher_server/cms/delegates/community_operation.ex index 93e371b8b..e8834d474 100644 --- a/lib/groupher_server/cms/delegates/community_operation.ex +++ b/lib/groupher_server/cms/delegates/community_operation.ex @@ -102,33 +102,23 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do @doc """ subscribe a community. (ONLY community, post etc use watch ) """ - def subscribe_community( - %Community{id: community_id}, - %User{id: user_id} - ) do - with {:ok, record} <- CommunitySubscriber |> ORM.create(~m(user_id community_id)a) do - Community |> ORM.find(record.community_id) + def subscribe_community(%Community{id: community_id}, %User{id: user_id}) do + with {:ok, record} <- ORM.create(CommunitySubscriber, ~m(user_id community_id)a) do + ORM.find(Community, record.community_id) end end - def subscribe_community( - %Community{id: community_id}, - %User{id: user_id}, - remote_ip - ) do - with {:ok, record} <- CommunitySubscriber |> ORM.create(~m(user_id community_id)a) do + def subscribe_community(%Community{id: community_id}, %User{id: user_id}, remote_ip) do + with {:ok, record} <- ORM.create(CommunitySubscriber, ~m(user_id community_id)a) do update_community_geo(community_id, user_id, remote_ip, :inc) - Community |> ORM.find(record.community_id) + ORM.find(Community, record.community_id) end end @doc """ unsubscribe a community """ - def unsubscribe_community( - %Community{id: community_id}, - %User{id: user_id} - ) do + def unsubscribe_community(%Community{id: community_id}, %User{id: user_id}) do with {:ok, community} <- ORM.find(Community, community_id), true <- community.raw !== "home", {:ok, record} <- @@ -175,11 +165,8 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do update_community_geo_map(community.id, city, :dec) Community |> ORM.find(record.community_id) else - false -> - {:error, "can't delete home community"} - - error -> - error + false -> {:error, "can't delete home community"} + error -> error end end @@ -217,12 +204,9 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do def subscribe_default_community_ifnot(%User{geo_city: city} = user, _remote_ip) do with {:ok, community} <- ORM.find_by(Community, raw: "home") do case ORM.find_by(CommunitySubscriber, %{community_id: community.id, user_id: user.id}) do - {:error, _} -> - update_community_geo_map(community.id, city, :inc) - - {:ok, _} -> - # 手续齐全且之前也订阅了 - {:ok, :pass} + {:error, _} -> update_community_geo_map(community.id, city, :inc) + # 手续齐全且之前也订阅了 + {:ok, _} -> {:ok, :pass} end end end From 0ec4880bd9b8d47a50d52a3e87441b1a28e4d225 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Fri, 21 May 2021 13:41:15 +0800 Subject: [PATCH 12/23] fix(article): add viewer_has states when read --- .../cms/delegates/article_curd.ex | 11 ++++++- .../cms/community/community_meta_test.exs | 8 ++--- test/groupher_server/cms/job_test.exs | 32 +++++++++++++++++++ test/groupher_server/cms/post_test.exs | 32 +++++++++++++++++++ test/groupher_server/cms/repo_test.exs | 32 +++++++++++++++++++ .../mutation/cms/flags/job_flag_test.exs | 4 +-- .../mutation/cms/flags/post_flag_test.exs | 4 +-- .../mutation/cms/flags/repo_flag_test.exs | 4 +-- .../query/cms/cms_test.exs | 2 +- .../query/cms/community_meta_test.exs | 2 +- 10 files changed, 118 insertions(+), 13 deletions(-) diff --git a/lib/groupher_server/cms/delegates/article_curd.ex b/lib/groupher_server/cms/delegates/article_curd.ex index 65bb5a324..8c0a1bbad 100644 --- a/lib/groupher_server/cms/delegates/article_curd.ex +++ b/lib/groupher_server/cms/delegates/article_curd.ex @@ -47,6 +47,15 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do |> Multi.run(:add_viewed_user, fn _, %{inc_views: article} -> update_viewed_user_list(article, user_id) end) + |> Multi.run(:set_viewer_has_states, fn _, %{inc_views: article} -> + viewer_has_states = %{ + viewer_has_collected: user_id in article.meta.collected_user_ids, + viewer_has_upvoted: user_id in article.meta.upvoted_user_ids, + viewer_has_reported: user_id in article.meta.reported_user_ids + } + + {:ok, Map.merge(article, viewer_has_states)} + end) |> Repo.transaction() |> result() end @@ -435,7 +444,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do defp result({:ok, %{update_edit_status: result}}), do: {:ok, result} defp result({:ok, %{update_article: result}}), do: {:ok, result} - defp result({:ok, %{inc_views: result}}), do: result |> done() + defp result({:ok, %{set_viewer_has_states: result}}), do: result |> done() defp result({:error, _, result, _steps}) do {:error, result} diff --git a/test/groupher_server/cms/community/community_meta_test.exs b/test/groupher_server/cms/community/community_meta_test.exs index 1630848d7..0c8bbd7f2 100644 --- a/test/groupher_server/cms/community/community_meta_test.exs +++ b/test/groupher_server/cms/community/community_meta_test.exs @@ -38,7 +38,7 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do assert community.meta |> strip_struct == @default_meta end - @tag :wip2 + @tag :wip3 test "create a post should inc posts_count in meta", ~m(user community community2 community3)a do post_attrs = mock_attrs(:post) @@ -59,7 +59,7 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do assert community2.meta.posts_count == 1 end - @tag :wip2 + @tag :wip3 test "create a job should inc jobs_count in meta", ~m(user community community2 community3)a do job_attrs = mock_attrs(:job) @@ -80,7 +80,7 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do assert community2.meta.jobs_count == 1 end - @tag :wip2 + @tag :wip3 test "create a repo should inc repos_count in meta", ~m(user community community2 community3)a do repo_attrs = mock_attrs(:repo) @@ -101,7 +101,7 @@ defmodule GroupherServer.Test.CMS.Community.CommunityMeta do assert community2.meta.repos_count == 1 end - @tag :wip2 + @tag :wip3 test "create a multi article should inc repos_count in meta", ~m(user community community2)a do post_attrs = mock_attrs(:post) diff --git a/test/groupher_server/cms/job_test.exs b/test/groupher_server/cms/job_test.exs index 7d5956996..39f4cd0ff 100644 --- a/test/groupher_server/cms/job_test.exs +++ b/test/groupher_server/cms/job_test.exs @@ -46,6 +46,38 @@ defmodule GroupherServer.Test.Job do assert user2.id in created.meta.viewed_user_ids end + @tag :wip2 + test "read job should contains viewer_has_xxx state", ~m(job_attrs community user user2)a do + {:ok, job} = CMS.create_article(community, :job, job_attrs, user) + {:ok, job} = CMS.read_article(:job, job.id, user) + + assert not job.viewer_has_collected + assert not job.viewer_has_upvoted + assert not job.viewer_has_reported + + {:ok, job} = CMS.read_article(:job, job.id) + + assert not job.viewer_has_collected + assert not job.viewer_has_upvoted + assert not job.viewer_has_reported + + {:ok, job} = CMS.read_article(:job, job.id, user2) + + assert not job.viewer_has_collected + assert not job.viewer_has_upvoted + assert not job.viewer_has_reported + + {:ok, _} = CMS.upvote_article(:job, job.id, user) + {:ok, _} = CMS.collect_article(:job, job.id, user) + {:ok, _} = CMS.report_article(:job, job.id, "reason", "attr_info", user) + + {:ok, job} = CMS.read_article(:job, job.id, user) + + assert job.viewer_has_collected + assert job.viewer_has_upvoted + assert job.viewer_has_reported + end + test "create job with an exsit community fails", ~m(user)a do invalid_attrs = mock_attrs(:job, %{community_id: non_exsit_id()}) ivalid_community = %Community{id: non_exsit_id()} diff --git a/test/groupher_server/cms/post_test.exs b/test/groupher_server/cms/post_test.exs index 187b0eee4..da5bd272d 100644 --- a/test/groupher_server/cms/post_test.exs +++ b/test/groupher_server/cms/post_test.exs @@ -47,6 +47,38 @@ defmodule GroupherServer.Test.CMS.Post do assert user2.id in created.meta.viewed_user_ids end + @tag :wip2 + test "read post should contains viewer_has_xxx state", ~m(post_attrs community user user2)a do + {:ok, post} = CMS.create_article(community, :post, post_attrs, user) + {:ok, post} = CMS.read_article(:post, post.id, user) + + assert not post.viewer_has_collected + assert not post.viewer_has_upvoted + assert not post.viewer_has_reported + + {:ok, post} = CMS.read_article(:post, post.id) + + assert not post.viewer_has_collected + assert not post.viewer_has_upvoted + assert not post.viewer_has_reported + + {:ok, post} = CMS.read_article(:post, post.id, user2) + + assert not post.viewer_has_collected + assert not post.viewer_has_upvoted + assert not post.viewer_has_reported + + {:ok, _} = CMS.upvote_article(:post, post.id, user) + {:ok, _} = CMS.collect_article(:post, post.id, user) + {:ok, _} = CMS.report_article(:post, post.id, "reason", "attr_info", user) + + {:ok, post} = CMS.read_article(:post, post.id, user) + + assert post.viewer_has_collected + assert post.viewer_has_upvoted + assert post.viewer_has_reported + end + test "add user to cms authors, if the user is not exsit in cms authors", ~m(user community post_attrs)a do assert {:error, _} = ORM.find_by(Author, user_id: user.id) diff --git a/test/groupher_server/cms/repo_test.exs b/test/groupher_server/cms/repo_test.exs index 4df407ddc..d96f8b4b2 100644 --- a/test/groupher_server/cms/repo_test.exs +++ b/test/groupher_server/cms/repo_test.exs @@ -48,6 +48,38 @@ defmodule GroupherServer.Test.Repo do assert user2.id in created.meta.viewed_user_ids end + @tag :wip2 + test "read repo should contains viewer_has_xxx state", ~m(repo_attrs community user user2)a do + {:ok, repo} = CMS.create_article(community, :repo, repo_attrs, user) + {:ok, repo} = CMS.read_article(:repo, repo.id, user) + + assert not repo.viewer_has_collected + assert not repo.viewer_has_upvoted + assert not repo.viewer_has_reported + + {:ok, repo} = CMS.read_article(:repo, repo.id) + + assert not repo.viewer_has_collected + assert not repo.viewer_has_upvoted + assert not repo.viewer_has_reported + + {:ok, repo} = CMS.read_article(:repo, repo.id, user2) + + assert not repo.viewer_has_collected + assert not repo.viewer_has_upvoted + assert not repo.viewer_has_reported + + {:ok, _} = CMS.upvote_article(:repo, repo.id, user) + {:ok, _} = CMS.collect_article(:repo, repo.id, user) + {:ok, _} = CMS.report_article(:repo, repo.id, "reason", "attr_info", user) + + {:ok, repo} = CMS.read_article(:repo, repo.id, user) + + assert repo.viewer_has_collected + assert repo.viewer_has_upvoted + assert repo.viewer_has_reported + end + test "add user to cms authors, if the user is not exsit in cms authors", ~m(user community repo_attrs)a do assert {:error, _} = ORM.find_by(Author, user_id: user.id) diff --git a/test/groupher_server_web/mutation/cms/flags/job_flag_test.exs b/test/groupher_server_web/mutation/cms/flags/job_flag_test.exs index 266ef03ce..e4bfed07e 100644 --- a/test/groupher_server_web/mutation/cms/flags/job_flag_test.exs +++ b/test/groupher_server_web/mutation/cms/flags/job_flag_test.exs @@ -38,7 +38,7 @@ defmodule GroupherServer.Test.Mutation.Flags.JobFlag do assert updated["markDelete"] == true end - @tag :wip2 + @tag :wip3 test "mark delete job should update job's communities meta count", ~m(user)a do community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) {:ok, community} = CMS.create_community(community_attrs) @@ -88,7 +88,7 @@ defmodule GroupherServer.Test.Mutation.Flags.JobFlag do assert updated["markDelete"] == false end - @tag :wip2 + @tag :wip3 test "undo mark delete job should update job's communities meta count", ~m(user)a do community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) {:ok, community} = CMS.create_community(community_attrs) diff --git a/test/groupher_server_web/mutation/cms/flags/post_flag_test.exs b/test/groupher_server_web/mutation/cms/flags/post_flag_test.exs index 4f95ee0d6..f80bcd666 100644 --- a/test/groupher_server_web/mutation/cms/flags/post_flag_test.exs +++ b/test/groupher_server_web/mutation/cms/flags/post_flag_test.exs @@ -38,7 +38,7 @@ defmodule GroupherServer.Test.Mutation.Flags.PostFlag do assert updated["markDelete"] == true end - @tag :wip2 + @tag :wip3 test "mark delete post should update post's communities meta count", ~m(user)a do community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) {:ok, community} = CMS.create_community(community_attrs) @@ -88,7 +88,7 @@ defmodule GroupherServer.Test.Mutation.Flags.PostFlag do assert updated["markDelete"] == false end - @tag :wip2 + @tag :wip3 test "undo mark delete post should update post's communities meta count", ~m(user)a do community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) {:ok, community} = CMS.create_community(community_attrs) diff --git a/test/groupher_server_web/mutation/cms/flags/repo_flag_test.exs b/test/groupher_server_web/mutation/cms/flags/repo_flag_test.exs index c84a57a12..995b94cc3 100644 --- a/test/groupher_server_web/mutation/cms/flags/repo_flag_test.exs +++ b/test/groupher_server_web/mutation/cms/flags/repo_flag_test.exs @@ -38,7 +38,7 @@ defmodule GroupherServer.Test.Mutation.Flags.RepoFlag do assert updated["markDelete"] == true end - @tag :wip2 + @tag :wip3 test "mark delete repo should update repo's communities meta count", ~m(user)a do community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) {:ok, community} = CMS.create_community(community_attrs) @@ -88,7 +88,7 @@ defmodule GroupherServer.Test.Mutation.Flags.RepoFlag do assert updated["markDelete"] == false end - @tag :wip2 + @tag :wip3 test "undo mark delete repo should update repo's communities meta count", ~m(user)a do community_attrs = mock_attrs(:community) |> Map.merge(%{user_id: user.id}) {:ok, community} = CMS.create_community(community_attrs) diff --git a/test/groupher_server_web/query/cms/cms_test.exs b/test/groupher_server_web/query/cms/cms_test.exs index aa2088ff5..e14744690 100644 --- a/test/groupher_server_web/query/cms/cms_test.exs +++ b/test/groupher_server_web/query/cms/cms_test.exs @@ -31,7 +31,7 @@ defmodule GroupherServer.Test.Query.CMS.Basic do } } """ - @tag :wip2 + @tag :wip3 test "views should work", ~m(guest_conn)a do {:ok, community} = db_insert(:community) diff --git a/test/groupher_server_web/query/cms/community_meta_test.exs b/test/groupher_server_web/query/cms/community_meta_test.exs index af2c787a2..0e50d9728 100644 --- a/test/groupher_server_web/query/cms/community_meta_test.exs +++ b/test/groupher_server_web/query/cms/community_meta_test.exs @@ -29,7 +29,7 @@ defmodule GroupherServer.Test.Query.CMS.CommunityMeta do } } """ - @tag :wip2 + @tag :wip3 test "community have valid [thread]s_count in meta", ~m(guest_conn community_attrs user)a do {:ok, community} = CMS.create_community(community_attrs) From 07660855e2e1e08b4201bdd4ef32cdb232a99c6a Mon Sep 17 00:00:00 2001 From: mydearxym Date: Fri, 21 May 2021 18:53:09 +0800 Subject: [PATCH 13/23] refactor: sync subscribe count to community --- .../cms/delegates/article_curd.ex | 6 +- .../cms/delegates/community_curd.ex | 30 ++++++- .../cms/delegates/community_operation.ex | 79 ++++++++++++++++--- .../cms/embeds/community_meta.ex | 9 +-- test/groupher_server/cms/cms_test.exs | 36 +++++++-- test/groupher_server/cms/job_test.exs | 1 - test/groupher_server/cms/post_test.exs | 1 - test/groupher_server/cms/repo_test.exs | 1 - 8 files changed, 131 insertions(+), 32 deletions(-) diff --git a/lib/groupher_server/cms/delegates/article_curd.ex b/lib/groupher_server/cms/delegates/article_curd.ex index 8c0a1bbad..516e43c87 100644 --- a/lib/groupher_server/cms/delegates/article_curd.ex +++ b/lib/groupher_server/cms/delegates/article_curd.ex @@ -145,7 +145,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do ArticleTag.set_article_tags(community, thread, article, attrs) end) |> Multi.run(:update_community_article_count, fn _, _ -> - CommunityCURD.update_community_meta(community, thread, :count) + CommunityCURD.update_community_count_field(community, thread) end) |> Multi.run(:mention_users, fn _, %{create_article: article} -> Delivery.mention_from_content(community.raw, thread, article, attrs, %User{id: uid}) @@ -209,7 +209,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do ORM.update(article, %{mark_delete: true}) end) |> Multi.run(:update_community_article_count, fn _, _ -> - CommunityCURD.update_community_meta(article.communities, thread, :count) + CommunityCURD.update_community_count_field(article.communities, thread) end) |> Repo.transaction() |> result() @@ -227,7 +227,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do ORM.update(article, %{mark_delete: false}) end) |> Multi.run(:update_community_article_count, fn _, _ -> - CommunityCURD.update_community_meta(article.communities, thread, :count) + CommunityCURD.update_community_count_field(article.communities, thread) end) |> Repo.transaction() |> result() diff --git a/lib/groupher_server/cms/delegates/community_curd.ex b/lib/groupher_server/cms/delegates/community_curd.ex index 366353b15..d9fef3f54 100644 --- a/lib/groupher_server/cms/delegates/community_curd.ex +++ b/lib/groupher_server/cms/delegates/community_curd.ex @@ -65,17 +65,39 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do end end - def update_community_meta(communities, thread, :count) when is_list(communities) do - case Enum.all?(communities, &({:ok, _} = update_community_meta(&1, thread, :count))) do + @doc """ + update subscribers_count of a community + """ + def update_community_count_field(%Community{} = community, user_id, :subscribers_count, opt) do + count_query = from(s in CommunitySubscriber, where: s.community_id == ^community.id) + subscribers_count = Repo.aggregate(count_query, :count) + community_meta = if is_nil(community.meta), do: @default_meta, else: community.meta + + subscribed_user_ids = + case opt do + :inc -> (community_meta.subscribed_user_ids ++ [user_id]) |> Enum.uniq() + :dec -> (community_meta.subscribed_user_ids -- [user_id]) |> Enum.uniq() + end + + meta = community_meta |> Map.put(:subscribed_user_ids, subscribed_user_ids) |> strip_struct + + community + |> Ecto.Changeset.change(%{subscribers_count: subscribers_count}) + |> Ecto.Changeset.put_embed(:meta, meta) + |> Repo.update() + end + + def update_community_count_field(communities, thread) when is_list(communities) do + case Enum.all?(communities, &({:ok, _} = update_community_count_field(&1, thread))) do true -> {:ok, :pass} - false -> {:error, "update_community_meta"} + false -> {:error, "update_community_count_field"} end end @doc """ update thread / article count in community meta """ - def update_community_meta(%Community{} = community, thread, :count) do + def update_community_count_field(%Community{} = community, thread) do with {:ok, info} <- match(thread) do count_query = from(a in info.model, diff --git a/lib/groupher_server/cms/delegates/community_operation.ex b/lib/groupher_server/cms/delegates/community_operation.ex index e8834d474..14631c094 100644 --- a/lib/groupher_server/cms/delegates/community_operation.ex +++ b/lib/groupher_server/cms/delegates/community_operation.ex @@ -4,13 +4,13 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do """ import ShortMaps - alias Ecto.Multi alias Helper.{Certification, RadarSearch, ORM} alias GroupherServer.Accounts.User alias GroupherServer.CMS.Delegate.PassportCURD alias GroupherServer.Repo alias GroupherServer.CMS.{ + Delegate, Category, Community, CommunityCategory, @@ -20,6 +20,9 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do Thread } + alias Delegate.CommunityCURD + alias Ecto.Multi + @doc """ set a category to community """ @@ -104,14 +107,32 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do """ def subscribe_community(%Community{id: community_id}, %User{id: user_id}) do with {:ok, record} <- ORM.create(CommunitySubscriber, ~m(user_id community_id)a) do - ORM.find(Community, record.community_id) + Multi.new() + |> Multi.run(:subscribed_community, fn _, _ -> + ORM.find(Community, record.community_id) + end) + |> Multi.run(:update_community_count, fn _, %{subscribed_community: community} -> + CommunityCURD.update_community_count_field(community, user_id, :subscribers_count, :inc) + end) + |> Repo.transaction() + |> result() end end def subscribe_community(%Community{id: community_id}, %User{id: user_id}, remote_ip) do with {:ok, record} <- ORM.create(CommunitySubscriber, ~m(user_id community_id)a) do - update_community_geo(community_id, user_id, remote_ip, :inc) - ORM.find(Community, record.community_id) + Multi.new() + |> Multi.run(:subscribed_community, fn _, _ -> + ORM.find(Community, record.community_id) + end) + |> Multi.run(:update_community_geo, fn _, _ -> + update_community_geo(community_id, user_id, remote_ip, :inc) + end) + |> Multi.run(:update_community_count, fn _, %{subscribed_community: community} -> + CommunityCURD.update_community_count_field(community, user_id, :subscribers_count, :inc) + end) + |> Repo.transaction() + |> result() end end @@ -123,7 +144,15 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do true <- community.raw !== "home", {:ok, record} <- ORM.findby_delete!(CommunitySubscriber, community_id: community.id, user_id: user_id) do - Community |> ORM.find(record.community_id) + Multi.new() + |> Multi.run(:unsubscribed_community, fn _, _ -> + ORM.find(Community, record.community_id) + end) + |> Multi.run(:update_community_count, fn _, %{unsubscribed_community: community} -> + CommunityCURD.update_community_count_field(community, user_id, :subscribers_count, :dec) + end) + |> Repo.transaction() + |> result() else false -> {:error, "can not unsubscribe home community"} @@ -142,8 +171,18 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do true <- community.raw !== "home", {:ok, record} <- CommunitySubscriber |> ORM.findby_delete!(community_id: community.id, user_id: user_id) do - update_community_geo(community_id, user_id, remote_ip, :dec) - Community |> ORM.find(record.community_id) + Multi.new() + |> Multi.run(:unsubscribed_community, fn _, _ -> + ORM.find(Community, record.community_id) + end) + |> Multi.run(:update_community_geo, fn _, _ -> + update_community_geo(community_id, user_id, remote_ip, :dec) + end) + |> Multi.run(:update_community_count, fn _, %{unsubscribed_community: community} -> + CommunityCURD.update_community_count_field(community, user_id, :subscribers_count, :dec) + end) + |> Repo.transaction() + |> result() else false -> {:error, "can't delete home community"} @@ -162,8 +201,18 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do true <- community.raw !== "home", {:ok, record} <- CommunitySubscriber |> ORM.findby_delete!(community_id: community.id, user_id: user_id) do - update_community_geo_map(community.id, city, :dec) - Community |> ORM.find(record.community_id) + Multi.new() + |> Multi.run(:unsubscribed_community, fn _, _ -> + ORM.find(Community, record.community_id) + end) + |> Multi.run(:update_community_geo_city, fn _, _ -> + update_community_geo_map(community.id, city, :dec) + end) + |> Multi.run(:update_community_count, fn _, %{unsubscribed_community: community} -> + CommunityCURD.update_community_count_field(community, user_id, :subscribers_count, :dec) + end) + |> Repo.transaction() + |> result() else false -> {:error, "can't delete home community"} error -> error @@ -255,4 +304,16 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do defp update_geo_value(geo_info, :dec) do Map.merge(geo_info, %{"value" => max(geo_info["value"] - 1, 0)}) end + + defp result({:ok, %{subscribed_community: result}}) do + {:ok, result} + end + + defp result({:ok, %{unsubscribed_community: result}}) do + {:ok, result} + end + + defp result({:error, _, result, _steps}) do + {:error, result} + end end diff --git a/lib/groupher_server/cms/embeds/community_meta.ex b/lib/groupher_server/cms/embeds/community_meta.ex index 53039f17e..05dc8e5cd 100644 --- a/lib/groupher_server/cms/embeds/community_meta.ex +++ b/lib/groupher_server/cms/embeds/community_meta.ex @@ -29,8 +29,6 @@ defmodule GroupherServer.CMS.Embeds.CommunityMeta do @article_threads get_config(:article, :article_threads) @general_options %{ - subscribers_count: 0, - editors_count: 0, subscribed_user_ids: [], contributes_digest: [] } @@ -50,14 +48,13 @@ defmodule GroupherServer.CMS.Embeds.CommunityMeta do thread_count_fields() # 关注相关 - field(:subscribers_count, :integer, default: 0) field(:subscribed_user_ids, {:array, :integer}, default: []) - - field(:editors_count, :integer, default: 0) field(:contributes_digest, {:array, :integer}, default: []) end def changeset(struct, params) do - struct |> cast(params, @optional_fields) + struct + |> cast(params, @optional_fields) + |> IO.inspect(label: "community meta casting?") end end diff --git a/test/groupher_server/cms/cms_test.exs b/test/groupher_server/cms/cms_test.exs index 2473938d2..e2fc1e3bf 100644 --- a/test/groupher_server/cms/cms_test.exs +++ b/test/groupher_server/cms/cms_test.exs @@ -3,7 +3,7 @@ defmodule GroupherServer.Test.CMS do alias GroupherServer.Accounts.User alias GroupherServer.CMS - alias CMS.Community + alias CMS.{Community, CommunityEditor} alias Helper.{Certification, ORM} @@ -88,8 +88,6 @@ defmodule GroupherServer.Test.CMS do end describe "[cms community thread]" do - alias CMS.Community - test "can create thread to a community" do title = "post" raw = "POST" @@ -115,8 +113,6 @@ defmodule GroupherServer.Test.CMS do end describe "[cms community editors]" do - alias CMS.{Community, CommunityEditor} - test "can add editor to a community, editor has default passport", ~m(user community)a do title = "chief editor" @@ -150,13 +146,39 @@ defmodule GroupherServer.Test.CMS do end describe "[cms community subscribe]" do - alias CMS.Community - + # @tag :wip2 test "user can subscribe a community", ~m(user community)a do {:ok, record} = CMS.subscribe_community(community, user) assert community.id == record.id end + @tag :wip2 + test "user subscribe a community will update the community's subscribted info", + ~m(user community)a do + assert community.subscribers_count == 0 + {:ok, record} = CMS.subscribe_community(community, user) + + {:ok, community} = ORM.find(Community, community.id) + assert community.subscribers_count == 1 + + assert user.id in community.meta.subscribed_user_ids + end + + @tag :wip2 + test "user unsubscribe a community will update the community's subscribted info", + ~m(user community)a do + {:ok, _} = CMS.subscribe_community(community, user) + {:ok, community} = ORM.find(Community, community.id) + assert community.subscribers_count == 1 + assert user.id in community.meta.subscribed_user_ids + + {:ok, _} = CMS.unsubscribe_community(community, user) + + {:ok, community} = ORM.find(Community, community.id) + assert community.subscribers_count == 0 + assert user.id not in community.meta.subscribed_user_ids + end + test "user can get paged-subscribers of a community", ~m(community)a do {:ok, users} = db_insert_multi(:user, 25) diff --git a/test/groupher_server/cms/job_test.exs b/test/groupher_server/cms/job_test.exs index 39f4cd0ff..f08b76da5 100644 --- a/test/groupher_server/cms/job_test.exs +++ b/test/groupher_server/cms/job_test.exs @@ -46,7 +46,6 @@ defmodule GroupherServer.Test.Job do assert user2.id in created.meta.viewed_user_ids end - @tag :wip2 test "read job should contains viewer_has_xxx state", ~m(job_attrs community user user2)a do {:ok, job} = CMS.create_article(community, :job, job_attrs, user) {:ok, job} = CMS.read_article(:job, job.id, user) diff --git a/test/groupher_server/cms/post_test.exs b/test/groupher_server/cms/post_test.exs index da5bd272d..3b00afbc5 100644 --- a/test/groupher_server/cms/post_test.exs +++ b/test/groupher_server/cms/post_test.exs @@ -47,7 +47,6 @@ defmodule GroupherServer.Test.CMS.Post do assert user2.id in created.meta.viewed_user_ids end - @tag :wip2 test "read post should contains viewer_has_xxx state", ~m(post_attrs community user user2)a do {:ok, post} = CMS.create_article(community, :post, post_attrs, user) {:ok, post} = CMS.read_article(:post, post.id, user) diff --git a/test/groupher_server/cms/repo_test.exs b/test/groupher_server/cms/repo_test.exs index d96f8b4b2..24c981b46 100644 --- a/test/groupher_server/cms/repo_test.exs +++ b/test/groupher_server/cms/repo_test.exs @@ -48,7 +48,6 @@ defmodule GroupherServer.Test.Repo do assert user2.id in created.meta.viewed_user_ids end - @tag :wip2 test "read repo should contains viewer_has_xxx state", ~m(repo_attrs community user user2)a do {:ok, repo} = CMS.create_article(community, :repo, repo_attrs, user) {:ok, repo} = CMS.read_article(:repo, repo.id, user) From 8cd6ee8ee03ff208933a12acf5c18c6b2f074662 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 22 May 2021 00:19:26 +0800 Subject: [PATCH 14/23] refactor: move subscribe count & viewer_has logic --- lib/groupher_server/cms/cms.ex | 1 + lib/groupher_server/cms/community.ex | 2 ++ .../cms/delegates/community_curd.ex | 20 ++++++++++++------- .../cms/embeds/community_meta.ex | 1 - .../resolvers/cms_resolver.ex | 7 +++++++ .../schema/cms/cms_types.ex | 18 ----------------- 6 files changed, 23 insertions(+), 26 deletions(-) diff --git a/lib/groupher_server/cms/cms.ex b/lib/groupher_server/cms/cms.ex index c1e6b701a..629db816b 100644 --- a/lib/groupher_server/cms/cms.ex +++ b/lib/groupher_server/cms/cms.ex @@ -32,6 +32,7 @@ defmodule GroupherServer.CMS do # Community CURD: editors, thread, tag defdelegate read_community(args), to: CommunityCURD + defdelegate read_community(args, user), to: CommunityCURD defdelegate create_community(args), to: CommunityCURD defdelegate update_community(id, args), to: CommunityCURD # >> editor .. diff --git a/lib/groupher_server/cms/community.ex b/lib/groupher_server/cms/community.ex index 73789cc78..9bfd79991 100644 --- a/lib/groupher_server/cms/community.ex +++ b/lib/groupher_server/cms/community.ex @@ -49,6 +49,8 @@ defmodule GroupherServer.CMS.Community do field(:editors_count, :integer, default: 0) field(:subscribers_count, :integer, default: 0) + field(:viewer_has_subscribed, :boolean, default: false, virtual: true) + has_one(:wiki, CommunityWiki) has_one(:cheatsheet, CommunityCheatsheet) diff --git a/lib/groupher_server/cms/delegates/community_curd.ex b/lib/groupher_server/cms/delegates/community_curd.ex index d9fef3f54..c1abf64ba 100644 --- a/lib/groupher_server/cms/delegates/community_curd.ex +++ b/lib/groupher_server/cms/delegates/community_curd.ex @@ -27,22 +27,28 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do @default_meta Embeds.CommunityMeta.default_meta() @article_threads get_config(:article, :article_threads) + def read_community(clauses, user), do: read_community(clauses) |> viewer_has_states(user) def read_community(%{id: id}), do: ORM.read(Community, id, inc: :views) + def read_community(%{raw: raw} = clauses), do: do_read_community(clauses, raw) + def read_community(%{title: title} = clauses), do: do_read_community(clauses, title) - def read_community(%{raw: raw} = clauses) do + defp do_read_community(clauses, aka) do case ORM.read_by(Community, clauses, inc: :views) do {:ok, community} -> {:ok, community} - {:error, _} -> ORM.find_by(Community, aka: raw) + {:error, _} -> ORM.find_by(Community, aka: aka) end end - def read_community(%{title: title} = clauses) do - case ORM.read_by(Community, clauses, inc: :views) do - {:ok, community} -> {:ok, community} - {:error, _} -> ORM.find_by(Community, aka: title) - end + defp viewer_has_states({:ok, community}, %User{id: user_id}) do + viewer_has_states = %{ + viewer_has_subscribed: user_id in community.meta.subscribed_user_ids + } + + {:ok, Map.merge(community, viewer_has_states)} end + defp viewer_has_states({:error, reason}, _user), do: {:error, reason} + @doc """ create a community """ diff --git a/lib/groupher_server/cms/embeds/community_meta.ex b/lib/groupher_server/cms/embeds/community_meta.ex index 05dc8e5cd..cbc4d181f 100644 --- a/lib/groupher_server/cms/embeds/community_meta.ex +++ b/lib/groupher_server/cms/embeds/community_meta.ex @@ -55,6 +55,5 @@ defmodule GroupherServer.CMS.Embeds.CommunityMeta do def changeset(struct, params) do struct |> cast(params, @optional_fields) - |> IO.inspect(label: "community meta casting?") end end diff --git a/lib/groupher_server_web/resolvers/cms_resolver.ex b/lib/groupher_server_web/resolvers/cms_resolver.ex index ed6f42571..10ed2e841 100644 --- a/lib/groupher_server_web/resolvers/cms_resolver.ex +++ b/lib/groupher_server_web/resolvers/cms_resolver.ex @@ -14,6 +14,13 @@ defmodule GroupherServerWeb.Resolvers.CMS do # ####################### # community .. # ####################### + def community(_root, args, %{context: %{cur_user: user}}) do + case Enum.empty?(args) do + false -> CMS.read_community(args, user) + true -> {:error, "please provide community id or title or raw"} + end + end + def community(_root, args, _info) do case Enum.empty?(args) do false -> CMS.read_community(args) diff --git a/lib/groupher_server_web/schema/cms/cms_types.ex b/lib/groupher_server_web/schema/cms/cms_types.ex index 3ca1b7957..9ce6b0830 100644 --- a/lib/groupher_server_web/schema/cms/cms_types.ex +++ b/lib/groupher_server_web/schema/cms/cms_types.ex @@ -273,24 +273,6 @@ defmodule GroupherServerWeb.Schema.CMS.Types do resolve(dataloader(CMS, :subscribers)) end - # TODO: remove - field :subscribers_count, :integer do - arg(:count, :count_type, default_value: :count) - arg(:type, :community_type, default_value: :community) - resolve(dataloader(CMS, :subscribers)) - middleware(M.ConvertToInt) - end - - # TODO: remove - field :viewer_has_subscribed, :boolean do - arg(:viewer_did, :viewer_did_type, default_value: :viewer_did) - - middleware(M.Authorize, :login) - middleware(M.PutCurrentUser) - resolve(dataloader(CMS, :subscribers)) - middleware(M.ViewerDidConvert) - end - field :editors, list_of(:user) do arg(:filter, :members_filter) middleware(M.PageSizeProof) From a49dc9347cff360b1c4d7d8560708c3f2f6722ba Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 22 May 2021 11:08:18 +0800 Subject: [PATCH 15/23] refactor(community): enhance read community & test --- .../cms/delegates/community_curd.ex | 4 +- test/groupher_server/cms/cms_test.exs | 58 +----------- .../cms/community/community_test.exs | 91 +++++++++++++++++++ 3 files changed, 94 insertions(+), 59 deletions(-) create mode 100644 test/groupher_server/cms/community/community_test.exs diff --git a/lib/groupher_server/cms/delegates/community_curd.ex b/lib/groupher_server/cms/delegates/community_curd.ex index c1abf64ba..68ee45b91 100644 --- a/lib/groupher_server/cms/delegates/community_curd.ex +++ b/lib/groupher_server/cms/delegates/community_curd.ex @@ -40,9 +40,7 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do end defp viewer_has_states({:ok, community}, %User{id: user_id}) do - viewer_has_states = %{ - viewer_has_subscribed: user_id in community.meta.subscribed_user_ids - } + viewer_has_states = %{viewer_has_subscribed: user_id in community.meta.subscribed_user_ids} {:ok, Map.merge(community, viewer_has_states)} end diff --git a/test/groupher_server/cms/cms_test.exs b/test/groupher_server/cms/cms_test.exs index e2fc1e3bf..211063eea 100644 --- a/test/groupher_server/cms/cms_test.exs +++ b/test/groupher_server/cms/cms_test.exs @@ -3,7 +3,7 @@ defmodule GroupherServer.Test.CMS do alias GroupherServer.Accounts.User alias GroupherServer.CMS - alias CMS.{Community, CommunityEditor} + alias CMS.{Category, Community, CommunityEditor} alias Helper.{Certification, ORM} @@ -16,8 +16,6 @@ defmodule GroupherServer.Test.CMS do end describe "[cms category]" do - alias CMS.{Community, Category} - test "create category with valid attrs", ~m(user)a do valid_attrs = mock_attrs(:category, %{user_id: user.id}) ~m(title raw)a = valid_attrs @@ -132,10 +130,7 @@ defmodule GroupherServer.Test.CMS do {:ok, users} = db_insert_multi(:user, 25) title = "chief editor" - Enum.each( - users, - &CMS.set_editor(community, title, %User{id: &1.id}) - ) + Enum.each(users, &CMS.set_editor(community, title, %User{id: &1.id})) filter = %{page: 1, size: 10} {:ok, results} = CMS.community_members(:editors, %Community{id: community.id}, filter) @@ -144,53 +139,4 @@ defmodule GroupherServer.Test.CMS do assert results.total_count == 25 end end - - describe "[cms community subscribe]" do - # @tag :wip2 - test "user can subscribe a community", ~m(user community)a do - {:ok, record} = CMS.subscribe_community(community, user) - assert community.id == record.id - end - - @tag :wip2 - test "user subscribe a community will update the community's subscribted info", - ~m(user community)a do - assert community.subscribers_count == 0 - {:ok, record} = CMS.subscribe_community(community, user) - - {:ok, community} = ORM.find(Community, community.id) - assert community.subscribers_count == 1 - - assert user.id in community.meta.subscribed_user_ids - end - - @tag :wip2 - test "user unsubscribe a community will update the community's subscribted info", - ~m(user community)a do - {:ok, _} = CMS.subscribe_community(community, user) - {:ok, community} = ORM.find(Community, community.id) - assert community.subscribers_count == 1 - assert user.id in community.meta.subscribed_user_ids - - {:ok, _} = CMS.unsubscribe_community(community, user) - - {:ok, community} = ORM.find(Community, community.id) - assert community.subscribers_count == 0 - assert user.id not in community.meta.subscribed_user_ids - end - - test "user can get paged-subscribers of a community", ~m(community)a do - {:ok, users} = db_insert_multi(:user, 25) - - Enum.each( - users, - &CMS.subscribe_community(community, %User{id: &1.id}) - ) - - {:ok, results} = - CMS.community_members(:subscribers, %Community{id: community.id}, %{page: 1, size: 10}) - - assert results |> is_valid_pagination?(:raw) - end - end end diff --git a/test/groupher_server/cms/community/community_test.exs b/test/groupher_server/cms/community/community_test.exs new file mode 100644 index 000000000..81d1132d5 --- /dev/null +++ b/test/groupher_server/cms/community/community_test.exs @@ -0,0 +1,91 @@ +defmodule GroupherServer.Test.CMS.Community do + @moduledoc false + use GroupherServer.TestTools + + alias GroupherServer.Accounts.User + alias GroupherServer.CMS + alias CMS.Community + + alias Helper.ORM + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, community} = db_insert(:community) + + {:ok, ~m(user community user2)a} + end + + describe "[cms community read]" do + @tag :wip2 + test "read community should inc views", ~m(community)a do + {:ok, community} = CMS.read_community(%{id: community.id}) + + assert community.views == 1 + {:ok, community} = CMS.read_community(%{title: community.title}) + assert community.views == 2 + {:ok, community} = CMS.read_community(%{raw: community.raw}) + assert community.views == 3 + end + + @tag :wip2 + test "read subscribed community should have a flag", ~m(community user user2)a do + {:ok, _} = CMS.subscribe_community(community, user) + + {:ok, community} = CMS.read_community(%{id: community.id}, user) + + assert community.viewer_has_subscribed + assert user.id in community.meta.subscribed_user_ids + + {:ok, community} = CMS.read_community(%{id: community.id}, user2) + assert not community.viewer_has_subscribed + assert user2.id not in community.meta.subscribed_user_ids + end + end + + describe "[cms community subscribe]" do + # @tag :wip2 + test "user can subscribe a community", ~m(user community)a do + {:ok, record} = CMS.subscribe_community(community, user) + assert community.id == record.id + end + + @tag :wip2 + test "user subscribe a community will update the community's subscribted info", + ~m(user community)a do + assert community.subscribers_count == 0 + {:ok, record} = CMS.subscribe_community(community, user) + + {:ok, community} = ORM.find(Community, community.id) + assert community.subscribers_count == 1 + + assert user.id in community.meta.subscribed_user_ids + end + + @tag :wip2 + test "user unsubscribe a community will update the community's subscribted info", + ~m(user community)a do + {:ok, _} = CMS.subscribe_community(community, user) + {:ok, community} = ORM.find(Community, community.id) + assert community.subscribers_count == 1 + assert user.id in community.meta.subscribed_user_ids + + {:ok, _} = CMS.unsubscribe_community(community, user) + + {:ok, community} = ORM.find(Community, community.id) + assert community.subscribers_count == 0 + assert user.id not in community.meta.subscribed_user_ids + end + + test "user can get paged-subscribers of a community", ~m(community)a do + {:ok, users} = db_insert_multi(:user, 25) + + Enum.each(users, &CMS.subscribe_community(community, %User{id: &1.id})) + + {:ok, results} = + CMS.community_members(:subscribers, %Community{id: community.id}, %{page: 1, size: 10}) + + assert results |> is_valid_pagination?(:raw) + end + end +end From af3bfc2cb34591c482e042995a8deebb5be360c1 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 22 May 2021 11:09:33 +0800 Subject: [PATCH 16/23] refactor(community): fmt --- .../cms/delegates/community_curd.ex | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/lib/groupher_server/cms/delegates/community_curd.ex b/lib/groupher_server/cms/delegates/community_curd.ex index 68ee45b91..6b0a72492 100644 --- a/lib/groupher_server/cms/delegates/community_curd.ex +++ b/lib/groupher_server/cms/delegates/community_curd.ex @@ -32,21 +32,6 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do def read_community(%{raw: raw} = clauses), do: do_read_community(clauses, raw) def read_community(%{title: title} = clauses), do: do_read_community(clauses, title) - defp do_read_community(clauses, aka) do - case ORM.read_by(Community, clauses, inc: :views) do - {:ok, community} -> {:ok, community} - {:error, _} -> ORM.find_by(Community, aka: aka) - end - end - - defp viewer_has_states({:ok, community}, %User{id: user_id}) do - viewer_has_states = %{viewer_has_subscribed: user_id in community.meta.subscribed_user_ids} - - {:ok, Map.merge(community, viewer_has_states)} - end - - defp viewer_has_states({:error, reason}, _user), do: {:error, reason} - @doc """ create a community """ @@ -220,6 +205,21 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do end end + defp do_read_community(clauses, aka) do + case ORM.read_by(Community, clauses, inc: :views) do + {:ok, community} -> {:ok, community} + {:error, _} -> ORM.find_by(Community, aka: aka) + end + end + + defp viewer_has_states({:ok, community}, %User{id: user_id}) do + viewer_has_states = %{viewer_has_subscribed: user_id in community.meta.subscribed_user_ids} + + {:ok, Map.merge(community, viewer_has_states)} + end + + defp viewer_has_states({:error, reason}, _user), do: {:error, reason} + defp load_community_members(%Community{id: id}, queryable, %{page: page, size: size} = filters) when not is_nil(id) do queryable From bea44aba81bb55a9589c2b6f967da6155c2d59b3 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 22 May 2021 11:45:20 +0800 Subject: [PATCH 17/23] fix(community): meta nil edge-case --- lib/groupher_server/cms/delegates/article_curd.ex | 8 +++++--- test/groupher_server/cms/community/community_test.exs | 6 +----- test/groupher_server_web/query/cms/articles/job_test.exs | 2 +- test/groupher_server_web/query/cms/articles/repo_test.exs | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/groupher_server/cms/delegates/article_curd.ex b/lib/groupher_server/cms/delegates/article_curd.ex index 516e43c87..d66486263 100644 --- a/lib/groupher_server/cms/delegates/article_curd.ex +++ b/lib/groupher_server/cms/delegates/article_curd.ex @@ -48,10 +48,12 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do update_viewed_user_list(article, user_id) end) |> Multi.run(:set_viewer_has_states, fn _, %{inc_views: article} -> + article_meta = if is_nil(article.meta), do: @default_article_meta, else: article.meta + viewer_has_states = %{ - viewer_has_collected: user_id in article.meta.collected_user_ids, - viewer_has_upvoted: user_id in article.meta.upvoted_user_ids, - viewer_has_reported: user_id in article.meta.reported_user_ids + viewer_has_collected: user_id in article_meta.collected_user_ids, + viewer_has_upvoted: user_id in article_meta.upvoted_user_ids, + viewer_has_reported: user_id in article_meta.reported_user_ids } {:ok, Map.merge(article, viewer_has_states)} diff --git a/test/groupher_server/cms/community/community_test.exs b/test/groupher_server/cms/community/community_test.exs index 81d1132d5..44e0fa7ee 100644 --- a/test/groupher_server/cms/community/community_test.exs +++ b/test/groupher_server/cms/community/community_test.exs @@ -17,7 +17,6 @@ defmodule GroupherServer.Test.CMS.Community do end describe "[cms community read]" do - @tag :wip2 test "read community should inc views", ~m(community)a do {:ok, community} = CMS.read_community(%{id: community.id}) @@ -28,7 +27,6 @@ defmodule GroupherServer.Test.CMS.Community do assert community.views == 3 end - @tag :wip2 test "read subscribed community should have a flag", ~m(community user user2)a do {:ok, _} = CMS.subscribe_community(community, user) @@ -50,11 +48,10 @@ defmodule GroupherServer.Test.CMS.Community do assert community.id == record.id end - @tag :wip2 test "user subscribe a community will update the community's subscribted info", ~m(user community)a do assert community.subscribers_count == 0 - {:ok, record} = CMS.subscribe_community(community, user) + {:ok, _record} = CMS.subscribe_community(community, user) {:ok, community} = ORM.find(Community, community.id) assert community.subscribers_count == 1 @@ -62,7 +59,6 @@ defmodule GroupherServer.Test.CMS.Community do assert user.id in community.meta.subscribed_user_ids end - @tag :wip2 test "user unsubscribe a community will update the community's subscribted info", ~m(user community)a do {:ok, _} = CMS.subscribe_community(community, user) diff --git a/test/groupher_server_web/query/cms/articles/job_test.exs b/test/groupher_server_web/query/cms/articles/job_test.exs index 3c50f89fa..6d66c221d 100644 --- a/test/groupher_server_web/query/cms/articles/job_test.exs +++ b/test/groupher_server_web/query/cms/articles/job_test.exs @@ -19,7 +19,7 @@ defmodule GroupherServer.Test.Query.Articles.Job do } } """ - + @tag :wip3 test "basic graphql query on job with logined user", ~m(user_conn job)a do variables = %{id: job.id} results = user_conn |> query_result(@query, variables, "job") diff --git a/test/groupher_server_web/query/cms/articles/repo_test.exs b/test/groupher_server_web/query/cms/articles/repo_test.exs index ed6b4cb07..8aaee608d 100644 --- a/test/groupher_server_web/query/cms/articles/repo_test.exs +++ b/test/groupher_server_web/query/cms/articles/repo_test.exs @@ -19,7 +19,7 @@ defmodule GroupherServer.Test.Query.Articles.Repo do } } """ - + @tag :wip2 test "basic graphql query on repo with logined user", ~m(user_conn repo)a do variables = %{id: repo.id} results = user_conn |> query_result(@query, variables, "repo") From 9f50d4552330d2cd77466d9c981e86e98b76a8c9 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 22 May 2021 13:22:48 +0800 Subject: [PATCH 18/23] fix(community): editor count & tests --- lib/groupher_server/cms/community.ex | 1 + .../cms/delegates/community_curd.ex | 27 ++++++++++- .../cms/delegates/community_operation.ex | 48 ++++++++++--------- .../cms/embeds/community_meta.ex | 2 + .../schema/cms/cms_types.ex | 7 --- .../cms/community/community_test.exs | 39 ++++++++++++++- .../query/cms/cms_test.exs | 12 ++--- 7 files changed, 97 insertions(+), 39 deletions(-) diff --git a/lib/groupher_server/cms/community.ex b/lib/groupher_server/cms/community.ex index 9bfd79991..f3d959fed 100644 --- a/lib/groupher_server/cms/community.ex +++ b/lib/groupher_server/cms/community.ex @@ -50,6 +50,7 @@ defmodule GroupherServer.CMS.Community do field(:subscribers_count, :integer, default: 0) field(:viewer_has_subscribed, :boolean, default: false, virtual: true) + field(:viewer_is_editor, :boolean, default: false, virtual: true) has_one(:wiki, CommunityWiki) has_one(:cheatsheet, CommunityCheatsheet) diff --git a/lib/groupher_server/cms/delegates/community_curd.ex b/lib/groupher_server/cms/delegates/community_curd.ex index 6b0a72492..32ec3ac4e 100644 --- a/lib/groupher_server/cms/delegates/community_curd.ex +++ b/lib/groupher_server/cms/delegates/community_curd.ex @@ -54,6 +54,28 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do end end + @doc """ + update editors_count of a community + """ + def update_community_count_field(%Community{} = community, user_id, :editors_count, opt) do + count_query = from(s in CommunityEditor, where: s.community_id == ^community.id) + editors_count = Repo.aggregate(count_query, :count) + community_meta = if is_nil(community.meta), do: @default_meta, else: community.meta + + editors_ids = + case opt do + :inc -> (community_meta.editors_ids ++ [user_id]) |> Enum.uniq() + :dec -> (community_meta.editors_ids -- [user_id]) |> Enum.uniq() + end + + meta = community_meta |> Map.put(:editors_ids, editors_ids) |> strip_struct + + community + |> Ecto.Changeset.change(%{editors_count: editors_count}) + |> Ecto.Changeset.put_embed(:meta, meta) + |> Repo.update() + end + @doc """ update subscribers_count of a community """ @@ -213,7 +235,10 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do end defp viewer_has_states({:ok, community}, %User{id: user_id}) do - viewer_has_states = %{viewer_has_subscribed: user_id in community.meta.subscribed_user_ids} + viewer_has_states = %{ + viewer_has_subscribed: user_id in community.meta.subscribed_user_ids, + viewer_is_editor: user_id in community.meta.editors_ids + } {:ok, Map.merge(community, viewer_has_states)} end diff --git a/lib/groupher_server/cms/delegates/community_operation.ex b/lib/groupher_server/cms/delegates/community_operation.ex index 14631c094..3c51c5797 100644 --- a/lib/groupher_server/cms/delegates/community_operation.ex +++ b/lib/groupher_server/cms/delegates/community_operation.ex @@ -71,37 +71,39 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do :insert_editor, CommunityEditor.changeset(%CommunityEditor{}, ~m(user_id community_id title)a) ) + |> Multi.run(:update_editors_count, fn _, _ -> + with {:ok, community} <- ORM.find(Community, community_id) do + CommunityCURD.update_community_count_field(community, user_id, :editors_count, :inc) + end + end) |> Multi.run(:stamp_passport, fn _, _ -> rules = Certification.passport_rules(cms: title) PassportCURD.stamp_passport(rules, %User{id: user_id}) end) |> Repo.transaction() - |> set_editor_result() + |> result() end @doc """ unset a community editor """ def unset_editor(%Community{id: community_id}, %User{id: user_id}) do - with {:ok, _} <- ORM.findby_delete!(CommunityEditor, ~m(user_id community_id)a), - {:ok, _} <- PassportCURD.delete_passport(%User{id: user_id}) do - User |> ORM.find(user_id) - end - end - - defp set_editor_result({:ok, %{insert_editor: editor}}) do - User |> ORM.find(editor.user_id) + Multi.new() + |> Multi.run(:delete_editor, fn _, _ -> + ORM.findby_delete!(CommunityEditor, ~m(user_id community_id)a) + end) + |> Multi.run(:update_editors_count, fn _, _ -> + with {:ok, community} <- ORM.find(Community, community_id) do + CommunityCURD.update_community_count_field(community, user_id, :editors_count, :dec) + end + end) + |> Multi.run(:stamp_passport, fn _, _ -> + PassportCURD.delete_passport(%User{id: user_id}) + end) + |> Repo.transaction() + |> result() end - defp set_editor_result({:error, :stamp_passport, %Ecto.Changeset{} = result, _steps}), - do: {:error, result} - - defp set_editor_result({:error, :stamp_passport, _result, _steps}), - do: {:error, "stamp passport error"} - - defp set_editor_result({:error, :insert_editor, _result, _steps}), - do: {:error, "insert editor error"} - @doc """ subscribe a community. (ONLY community, post etc use watch ) """ @@ -305,13 +307,15 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do Map.merge(geo_info, %{"value" => max(geo_info["value"] - 1, 0)}) end - defp result({:ok, %{subscribed_community: result}}) do + defp result({:ok, %{update_editors_count: result}}) do {:ok, result} end - defp result({:ok, %{unsubscribed_community: result}}) do - {:ok, result} - end + defp result({:error, :stamp_passport, %Ecto.Changeset{} = result, _steps}), + do: {:error, result} + + defp result({:error, :stamp_passport, _result, _steps}), + do: {:error, "stamp passport error"} defp result({:error, _, result, _steps}) do {:error, result} diff --git a/lib/groupher_server/cms/embeds/community_meta.ex b/lib/groupher_server/cms/embeds/community_meta.ex index cbc4d181f..5b95a93c1 100644 --- a/lib/groupher_server/cms/embeds/community_meta.ex +++ b/lib/groupher_server/cms/embeds/community_meta.ex @@ -29,6 +29,7 @@ defmodule GroupherServer.CMS.Embeds.CommunityMeta do @article_threads get_config(:article, :article_threads) @general_options %{ + editors_ids: [], subscribed_user_ids: [], contributes_digest: [] } @@ -47,6 +48,7 @@ defmodule GroupherServer.CMS.Embeds.CommunityMeta do embedded_schema do thread_count_fields() + field(:editors_ids, {:array, :integer}, default: []) # 关注相关 field(:subscribed_user_ids, {:array, :integer}, default: []) field(:contributes_digest, {:array, :integer}, default: []) diff --git a/lib/groupher_server_web/schema/cms/cms_types.ex b/lib/groupher_server_web/schema/cms/cms_types.ex index 9ce6b0830..2834e368f 100644 --- a/lib/groupher_server_web/schema/cms/cms_types.ex +++ b/lib/groupher_server_web/schema/cms/cms_types.ex @@ -279,13 +279,6 @@ defmodule GroupherServerWeb.Schema.CMS.Types do resolve(dataloader(CMS, :editors)) end - field :editors_count, :integer do - arg(:count, :count_type, default_value: :count) - arg(:type, :community_type, default_value: :community) - resolve(dataloader(CMS, :editors)) - middleware(M.ConvertToInt) - end - # TODO: remove field :threads_count, :integer do resolve(&R.CMS.threads_count/3) diff --git a/test/groupher_server/cms/community/community_test.exs b/test/groupher_server/cms/community/community_test.exs index 44e0fa7ee..a691a9218 100644 --- a/test/groupher_server/cms/community/community_test.exs +++ b/test/groupher_server/cms/community/community_test.exs @@ -39,10 +39,47 @@ defmodule GroupherServer.Test.CMS.Community do assert not community.viewer_has_subscribed assert user2.id not in community.meta.subscribed_user_ids end + + @tag :wip2 + test "read editored community should have a flag", ~m(community user user2)a do + title = "chief editor" + {:ok, community} = CMS.set_editor(community, title, user) + + {:ok, community} = CMS.read_community(%{id: community.id}, user) + assert community.viewer_is_editor + + {:ok, community} = CMS.read_community(%{id: community.id}, user2) + assert not community.viewer_is_editor + + {:ok, community} = CMS.unset_editor(community, user) + {:ok, community} = CMS.read_community(%{id: community.id}, user) + assert not community.viewer_is_editor + end + end + + describe "[cms community editor]" do + @tag :wip2 + test "can set editor to a community", ~m(user community)a do + title = "chief editor" + {:ok, community} = CMS.set_editor(community, title, user) + + assert community.editors_count == 1 + assert user.id in community.meta.editors_ids + end + + @tag :wip2 + test "can unset editor to a community", ~m(user community)a do + title = "chief editor" + {:ok, community} = CMS.set_editor(community, title, user) + assert community.editors_count == 1 + + {:ok, community} = CMS.unset_editor(community, user) + assert community.editors_count == 0 + assert user.id not in community.meta.editors_ids + end end describe "[cms community subscribe]" do - # @tag :wip2 test "user can subscribe a community", ~m(user community)a do {:ok, record} = CMS.subscribe_community(community, user) assert community.id == record.id diff --git a/test/groupher_server_web/query/cms/cms_test.exs b/test/groupher_server_web/query/cms/cms_test.exs index e14744690..168f10f4c 100644 --- a/test/groupher_server_web/query/cms/cms_test.exs +++ b/test/groupher_server_web/query/cms/cms_test.exs @@ -326,14 +326,12 @@ defmodule GroupherServer.Test.Query.CMS.Basic do } } """ + @tag :wip2 test "guest can get editors list and count of a community", ~m(guest_conn community)a do title = "chief editor" {:ok, users} = db_insert_multi(:user, assert_v(:inner_page_size)) - Enum.each( - users, - &CMS.set_editor(community, title, %User{id: &1.id}) - ) + Enum.each(users, &CMS.set_editor(community, title, %User{id: &1.id})) variables = %{id: community.id} results = guest_conn |> query_result(@query, variables, "community") @@ -363,14 +361,12 @@ defmodule GroupherServer.Test.Query.CMS.Basic do } } """ + @tag :wip2 test "guest user can get paged editors", ~m(guest_conn community)a do title = "chief editor" {:ok, users} = db_insert_multi(:user, 25) - Enum.each( - users, - &CMS.set_editor(community, title, %User{id: &1.id}) - ) + Enum.each(users, &CMS.set_editor(community, title, %User{id: &1.id})) variables = %{id: community.id, filter: %{page: 1, size: 10}} results = guest_conn |> query_result(@query, variables, "communityEditors") From ca57175bd4328e0eb58fc42ef88ee7d89983c315 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 22 May 2021 13:33:55 +0800 Subject: [PATCH 19/23] fix(community): remove editors subscribers in community fields --- .../cms/delegates/community_operation.ex | 4 ++++ lib/groupher_server_web/schema/cms/cms_types.ex | 6 ------ test/groupher_server_web/query/cms/cms_test.exs | 15 ++------------- 3 files changed, 6 insertions(+), 19 deletions(-) diff --git a/lib/groupher_server/cms/delegates/community_operation.ex b/lib/groupher_server/cms/delegates/community_operation.ex index 3c51c5797..c3e13796f 100644 --- a/lib/groupher_server/cms/delegates/community_operation.ex +++ b/lib/groupher_server/cms/delegates/community_operation.ex @@ -307,6 +307,10 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do Map.merge(geo_info, %{"value" => max(geo_info["value"] - 1, 0)}) end + defp result({:ok, %{subscribed_community: result}}) do + {:ok, result} + end + defp result({:ok, %{update_editors_count: result}}) do {:ok, result} end diff --git a/lib/groupher_server_web/schema/cms/cms_types.ex b/lib/groupher_server_web/schema/cms/cms_types.ex index 2834e368f..3d64c9609 100644 --- a/lib/groupher_server_web/schema/cms/cms_types.ex +++ b/lib/groupher_server_web/schema/cms/cms_types.ex @@ -267,12 +267,6 @@ defmodule GroupherServerWeb.Schema.CMS.Types do field(:subscribers_count, :integer) field(:editors_count, :integer) - field :subscribers, list_of(:user) do - arg(:filter, :members_filter) - middleware(M.PageSizeProof) - resolve(dataloader(CMS, :subscribers)) - end - field :editors, list_of(:user) do arg(:filter, :members_filter) middleware(M.PageSizeProof) diff --git a/test/groupher_server_web/query/cms/cms_test.exs b/test/groupher_server_web/query/cms/cms_test.exs index 168f10f4c..a4841da8f 100644 --- a/test/groupher_server_web/query/cms/cms_test.exs +++ b/test/groupher_server_web/query/cms/cms_test.exs @@ -381,14 +381,11 @@ defmodule GroupherServer.Test.Query.CMS.Basic do community(id: $id) { id subscribersCount - subscribers { - id - nickname - } } } """ - test "guest can get subscribers list and count of a community", ~m(guest_conn community)a do + @tag :wip2 + test "guest can get subscribers count of a community", ~m(guest_conn community)a do {:ok, users} = db_insert_multi(:user, assert_v(:inner_page_size)) Enum.each( @@ -398,16 +395,8 @@ defmodule GroupherServer.Test.Query.CMS.Basic do variables = %{id: community.id} results = guest_conn |> query_result(@query, variables, "community") - subscribers = results["subscribers"] subscribers_count = results["subscribersCount"] - [user_1, user_2, user_3, user_x] = users |> firstn_and_last(3) - - assert results["id"] == to_string(community.id) - assert subscribers |> Enum.any?(&(&1["id"] == to_string(user_1.id))) - assert subscribers |> Enum.any?(&(&1["id"] == to_string(user_2.id))) - assert subscribers |> Enum.any?(&(&1["id"] == to_string(user_3.id))) - assert subscribers |> Enum.any?(&(&1["id"] == to_string(user_x.id))) assert subscribers_count == assert_v(:inner_page_size) end From 2ea73d3acd3f47092c3307a936fdc9d76b0aaddb Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 22 May 2021 13:35:03 +0800 Subject: [PATCH 20/23] fix(community): remove editors subscribers in community fields --- lib/groupher_server_web/schema/cms/cms_types.ex | 6 ------ test/groupher_server_web/query/cms/cms_test.exs | 13 +------------ 2 files changed, 1 insertion(+), 18 deletions(-) diff --git a/lib/groupher_server_web/schema/cms/cms_types.ex b/lib/groupher_server_web/schema/cms/cms_types.ex index 3d64c9609..1de6e6bc5 100644 --- a/lib/groupher_server_web/schema/cms/cms_types.ex +++ b/lib/groupher_server_web/schema/cms/cms_types.ex @@ -267,12 +267,6 @@ defmodule GroupherServerWeb.Schema.CMS.Types do field(:subscribers_count, :integer) field(:editors_count, :integer) - field :editors, list_of(:user) do - arg(:filter, :members_filter) - middleware(M.PageSizeProof) - resolve(dataloader(CMS, :editors)) - end - # TODO: remove field :threads_count, :integer do resolve(&R.CMS.threads_count/3) diff --git a/test/groupher_server_web/query/cms/cms_test.exs b/test/groupher_server_web/query/cms/cms_test.exs index a4841da8f..a326279a2 100644 --- a/test/groupher_server_web/query/cms/cms_test.exs +++ b/test/groupher_server_web/query/cms/cms_test.exs @@ -319,15 +319,11 @@ defmodule GroupherServer.Test.Query.CMS.Basic do community(id: $id) { id editorsCount - editors { - id - nickname - } } } """ @tag :wip2 - test "guest can get editors list and count of a community", ~m(guest_conn community)a do + test "guest can get editors count of a community", ~m(guest_conn community)a do title = "chief editor" {:ok, users} = db_insert_multi(:user, assert_v(:inner_page_size)) @@ -335,16 +331,9 @@ defmodule GroupherServer.Test.Query.CMS.Basic do variables = %{id: community.id} results = guest_conn |> query_result(@query, variables, "community") - editors = results["editors"] editors_count = results["editorsCount"] - [user_1, user_2, user_3, user_x] = users |> firstn_and_last(3) - assert results["id"] == to_string(community.id) - assert editors |> Enum.any?(&(&1["id"] == to_string(user_1.id))) - assert editors |> Enum.any?(&(&1["id"] == to_string(user_2.id))) - assert editors |> Enum.any?(&(&1["id"] == to_string(user_3.id))) - assert editors |> Enum.any?(&(&1["id"] == to_string(user_x.id))) assert editors_count == assert_v(:inner_page_size) end From 2f53345410b1684e28cf5abb05c88e9e83d7e493 Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 22 May 2021 15:57:14 +0800 Subject: [PATCH 21/23] fix(community): error test --- .../cms/delegates/community_operation.ex | 42 ++++++++----------- .../cms/community/community_test.exs | 3 -- .../mutation/cms/cms_test.exs | 5 +-- .../query/cms/articles/repo_test.exs | 2 +- .../query/cms/cms_test.exs | 6 +-- 5 files changed, 23 insertions(+), 35 deletions(-) diff --git a/lib/groupher_server/cms/delegates/community_operation.ex b/lib/groupher_server/cms/delegates/community_operation.ex index c3e13796f..8e3d07833 100644 --- a/lib/groupher_server/cms/delegates/community_operation.ex +++ b/lib/groupher_server/cms/delegates/community_operation.ex @@ -71,7 +71,7 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do :insert_editor, CommunityEditor.changeset(%CommunityEditor{}, ~m(user_id community_id title)a) ) - |> Multi.run(:update_editors_count, fn _, _ -> + |> Multi.run(:update_community_count, fn _, _ -> with {:ok, community} <- ORM.find(Community, community_id) do CommunityCURD.update_community_count_field(community, user_id, :editors_count, :inc) end @@ -92,7 +92,7 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do |> Multi.run(:delete_editor, fn _, _ -> ORM.findby_delete!(CommunityEditor, ~m(user_id community_id)a) end) - |> Multi.run(:update_editors_count, fn _, _ -> + |> Multi.run(:update_community_count, fn _, _ -> with {:ok, community} <- ORM.find(Community, community_id) do CommunityCURD.update_community_count_field(community, user_id, :editors_count, :dec) end @@ -143,16 +143,14 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do """ def unsubscribe_community(%Community{id: community_id}, %User{id: user_id}) do with {:ok, community} <- ORM.find(Community, community_id), - true <- community.raw !== "home", - {:ok, record} <- - ORM.findby_delete!(CommunitySubscriber, community_id: community.id, user_id: user_id) do + true <- community.raw !== "home" do Multi.new() - |> Multi.run(:unsubscribed_community, fn _, _ -> - ORM.find(Community, record.community_id) - end) - |> Multi.run(:update_community_count, fn _, %{unsubscribed_community: community} -> + |> Multi.run(:update_community_count, fn _, _ -> CommunityCURD.update_community_count_field(community, user_id, :subscribers_count, :dec) end) + |> Multi.run(:unsubscribed_community, fn _, _ -> + ORM.findby_delete!(CommunitySubscriber, %{community_id: community.id, user_id: user_id}) + end) |> Repo.transaction() |> result() else @@ -170,19 +168,17 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do remote_ip ) do with {:ok, community} <- ORM.find(Community, community_id), - true <- community.raw !== "home", - {:ok, record} <- - CommunitySubscriber |> ORM.findby_delete!(community_id: community.id, user_id: user_id) do + true <- community.raw !== "home" do Multi.new() + |> Multi.run(:update_community_count, fn _, _ -> + CommunityCURD.update_community_count_field(community, user_id, :subscribers_count, :dec) + end) |> Multi.run(:unsubscribed_community, fn _, _ -> - ORM.find(Community, record.community_id) + ORM.findby_delete!(CommunitySubscriber, %{community_id: community.id, user_id: user_id}) end) |> Multi.run(:update_community_geo, fn _, _ -> update_community_geo(community_id, user_id, remote_ip, :dec) end) - |> Multi.run(:update_community_count, fn _, %{unsubscribed_community: community} -> - CommunityCURD.update_community_count_field(community, user_id, :subscribers_count, :dec) - end) |> Repo.transaction() |> result() else @@ -200,19 +196,17 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do _remote_ip ) do with {:ok, community} <- ORM.find(Community, community_id), - true <- community.raw !== "home", - {:ok, record} <- - CommunitySubscriber |> ORM.findby_delete!(community_id: community.id, user_id: user_id) do + true <- community.raw !== "home" do Multi.new() + |> Multi.run(:update_community_count, fn _, _ -> + CommunityCURD.update_community_count_field(community, user_id, :subscribers_count, :dec) + end) |> Multi.run(:unsubscribed_community, fn _, _ -> - ORM.find(Community, record.community_id) + ORM.findby_delete!(CommunitySubscriber, %{community_id: community.id, user_id: user_id}) end) |> Multi.run(:update_community_geo_city, fn _, _ -> update_community_geo_map(community.id, city, :dec) end) - |> Multi.run(:update_community_count, fn _, %{unsubscribed_community: community} -> - CommunityCURD.update_community_count_field(community, user_id, :subscribers_count, :dec) - end) |> Repo.transaction() |> result() else @@ -311,7 +305,7 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do {:ok, result} end - defp result({:ok, %{update_editors_count: result}}) do + defp result({:ok, %{update_community_count: result}}) do {:ok, result} end diff --git a/test/groupher_server/cms/community/community_test.exs b/test/groupher_server/cms/community/community_test.exs index a691a9218..c921a5328 100644 --- a/test/groupher_server/cms/community/community_test.exs +++ b/test/groupher_server/cms/community/community_test.exs @@ -40,7 +40,6 @@ defmodule GroupherServer.Test.CMS.Community do assert user2.id not in community.meta.subscribed_user_ids end - @tag :wip2 test "read editored community should have a flag", ~m(community user user2)a do title = "chief editor" {:ok, community} = CMS.set_editor(community, title, user) @@ -58,7 +57,6 @@ defmodule GroupherServer.Test.CMS.Community do end describe "[cms community editor]" do - @tag :wip2 test "can set editor to a community", ~m(user community)a do title = "chief editor" {:ok, community} = CMS.set_editor(community, title, user) @@ -67,7 +65,6 @@ defmodule GroupherServer.Test.CMS.Community do assert user.id in community.meta.editors_ids end - @tag :wip2 test "can unset editor to a community", ~m(user community)a do title = "chief editor" {:ok, community} = CMS.set_editor(community, title, user) diff --git a/test/groupher_server_web/mutation/cms/cms_test.exs b/test/groupher_server_web/mutation/cms/cms_test.exs index 65ddccd90..721da59f8 100644 --- a/test/groupher_server_web/mutation/cms/cms_test.exs +++ b/test/groupher_server_web/mutation/cms/cms_test.exs @@ -529,12 +529,10 @@ defmodule GroupherServer.Test.Mutation.CMS.Basic do mutation($communityId: ID!){ unsubscribeCommunity(communityId: $communityId) { id - subscribers { - id - } } } """ + @tag :wip2 test "login user can unsubscribe community", ~m(user community)a do {:ok, cur_subscribers} = CMS.community_members(:subscribers, %Community{id: community.id}, %{page: 1, size: 10}) @@ -547,7 +545,6 @@ defmodule GroupherServer.Test.Mutation.CMS.Basic do CMS.community_members(:subscribers, %Community{id: community.id}, %{page: 1, size: 10}) assert true == cur_subscribers.entries |> Enum.any?(&(&1.id == user.id)) - login_conn = simu_conn(:user, user) variables = %{communityId: community.id} diff --git a/test/groupher_server_web/query/cms/articles/repo_test.exs b/test/groupher_server_web/query/cms/articles/repo_test.exs index 8aaee608d..ed6b4cb07 100644 --- a/test/groupher_server_web/query/cms/articles/repo_test.exs +++ b/test/groupher_server_web/query/cms/articles/repo_test.exs @@ -19,7 +19,7 @@ defmodule GroupherServer.Test.Query.Articles.Repo do } } """ - @tag :wip2 + test "basic graphql query on repo with logined user", ~m(user_conn repo)a do variables = %{id: repo.id} results = user_conn |> query_result(@query, variables, "repo") diff --git a/test/groupher_server_web/query/cms/cms_test.exs b/test/groupher_server_web/query/cms/cms_test.exs index a326279a2..23d13d8ba 100644 --- a/test/groupher_server_web/query/cms/cms_test.exs +++ b/test/groupher_server_web/query/cms/cms_test.exs @@ -322,7 +322,7 @@ defmodule GroupherServer.Test.Query.CMS.Basic do } } """ - @tag :wip2 + test "guest can get editors count of a community", ~m(guest_conn community)a do title = "chief editor" {:ok, users} = db_insert_multi(:user, assert_v(:inner_page_size)) @@ -350,7 +350,7 @@ defmodule GroupherServer.Test.Query.CMS.Basic do } } """ - @tag :wip2 + test "guest user can get paged editors", ~m(guest_conn community)a do title = "chief editor" {:ok, users} = db_insert_multi(:user, 25) @@ -373,7 +373,7 @@ defmodule GroupherServer.Test.Query.CMS.Basic do } } """ - @tag :wip2 + test "guest can get subscribers count of a community", ~m(guest_conn community)a do {:ok, users} = db_insert_multi(:user, assert_v(:inner_page_size)) From e78a3155a8c176aee32348a1faa8dc7c441abbbb Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 22 May 2021 16:12:13 +0800 Subject: [PATCH 22/23] fix(community): error test --- .../mutation/cms/cms_test.exs | 5 +---- .../query/cms/cms_test.exs | 21 +------------------ 2 files changed, 2 insertions(+), 24 deletions(-) diff --git a/test/groupher_server_web/mutation/cms/cms_test.exs b/test/groupher_server_web/mutation/cms/cms_test.exs index 721da59f8..473a11aea 100644 --- a/test/groupher_server_web/mutation/cms/cms_test.exs +++ b/test/groupher_server_web/mutation/cms/cms_test.exs @@ -483,9 +483,6 @@ defmodule GroupherServer.Test.Mutation.CMS.Basic do mutation($communityId: ID!){ subscribeCommunity(communityId: $communityId) { id - subscribers { - id - } } } """ @@ -496,7 +493,6 @@ defmodule GroupherServer.Test.Mutation.CMS.Basic do created = login_conn |> mutation_result(@subscribe_query, variables, "subscribeCommunity") assert created["id"] == to_string(community.id) - assert created["subscribers"] |> Enum.any?(&(&1["id"] == to_string(user.id))) end test "login user subscribe non-exsit community fails", ~m(user)a do @@ -512,6 +508,7 @@ defmodule GroupherServer.Test.Mutation.CMS.Basic do assert guest_conn |> mutation_get_error?(@subscribe_query, variables, ecode(:account_login)) end + @tag :wip2 test "subscribed community should inc it's own geo info", ~m(user community)a do login_conn = simu_conn(:user, user) diff --git a/test/groupher_server_web/query/cms/cms_test.exs b/test/groupher_server_web/query/cms/cms_test.exs index 23d13d8ba..1a8109ff2 100644 --- a/test/groupher_server_web/query/cms/cms_test.exs +++ b/test/groupher_server_web/query/cms/cms_test.exs @@ -373,14 +373,10 @@ defmodule GroupherServer.Test.Query.CMS.Basic do } } """ - test "guest can get subscribers count of a community", ~m(guest_conn community)a do {:ok, users} = db_insert_multi(:user, assert_v(:inner_page_size)) - Enum.each( - users, - &CMS.subscribe_community(community, %User{id: &1.id}) - ) + Enum.each(users, &CMS.subscribe_community(community, %User{id: &1.id})) variables = %{id: community.id} results = guest_conn |> query_result(@query, variables, "community") @@ -389,21 +385,6 @@ defmodule GroupherServer.Test.Query.CMS.Basic do assert subscribers_count == assert_v(:inner_page_size) end - test "guest user can get subscribers count of 20 at most", ~m(guest_conn community)a do - {:ok, users} = db_insert_multi(:user, assert_v(:inner_page_size) + 1) - - Enum.each( - users, - &CMS.subscribe_community(community, %User{id: &1.id}) - ) - - variables = %{id: community.id} - results = guest_conn |> query_result(@query, variables, "community") - subscribers = results["subscribers"] - - assert length(subscribers) == assert_v(:inner_page_size) - end - @query """ query($id: ID, $community: String, $filter: PagedFilter!) { communitySubscribers(id: $id, community: $community, filter: $filter) { From e56b586d55b051868d18049dbffd9d483ae0d72c Mon Sep 17 00:00:00 2001 From: mydearxym Date: Sat, 22 May 2021 16:22:56 +0800 Subject: [PATCH 23/23] fix(community): error test --- .../cms/delegates/community_operation.ex | 18 +++++++++--------- .../cms/community/community_test.exs | 1 + .../mutation/cms/cms_test.exs | 5 ++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/groupher_server/cms/delegates/community_operation.ex b/lib/groupher_server/cms/delegates/community_operation.ex index 8e3d07833..2b6d02b8a 100644 --- a/lib/groupher_server/cms/delegates/community_operation.ex +++ b/lib/groupher_server/cms/delegates/community_operation.ex @@ -145,12 +145,12 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do with {:ok, community} <- ORM.find(Community, community_id), true <- community.raw !== "home" do Multi.new() - |> Multi.run(:update_community_count, fn _, _ -> - CommunityCURD.update_community_count_field(community, user_id, :subscribers_count, :dec) - end) |> Multi.run(:unsubscribed_community, fn _, _ -> ORM.findby_delete!(CommunitySubscriber, %{community_id: community.id, user_id: user_id}) end) + |> Multi.run(:update_community_count, fn _, _ -> + CommunityCURD.update_community_count_field(community, user_id, :subscribers_count, :dec) + end) |> Repo.transaction() |> result() else @@ -170,12 +170,12 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do with {:ok, community} <- ORM.find(Community, community_id), true <- community.raw !== "home" do Multi.new() - |> Multi.run(:update_community_count, fn _, _ -> - CommunityCURD.update_community_count_field(community, user_id, :subscribers_count, :dec) - end) |> Multi.run(:unsubscribed_community, fn _, _ -> ORM.findby_delete!(CommunitySubscriber, %{community_id: community.id, user_id: user_id}) end) + |> Multi.run(:update_community_count, fn _, _ -> + CommunityCURD.update_community_count_field(community, user_id, :subscribers_count, :dec) + end) |> Multi.run(:update_community_geo, fn _, _ -> update_community_geo(community_id, user_id, remote_ip, :dec) end) @@ -198,12 +198,12 @@ defmodule GroupherServer.CMS.Delegate.CommunityOperation do with {:ok, community} <- ORM.find(Community, community_id), true <- community.raw !== "home" do Multi.new() - |> Multi.run(:update_community_count, fn _, _ -> - CommunityCURD.update_community_count_field(community, user_id, :subscribers_count, :dec) - end) |> Multi.run(:unsubscribed_community, fn _, _ -> ORM.findby_delete!(CommunitySubscriber, %{community_id: community.id, user_id: user_id}) end) + |> Multi.run(:update_community_count, fn _, _ -> + CommunityCURD.update_community_count_field(community, user_id, :subscribers_count, :dec) + end) |> Multi.run(:update_community_geo_city, fn _, _ -> update_community_geo_map(community.id, city, :dec) end) diff --git a/test/groupher_server/cms/community/community_test.exs b/test/groupher_server/cms/community/community_test.exs index c921a5328..f7c4db1e7 100644 --- a/test/groupher_server/cms/community/community_test.exs +++ b/test/groupher_server/cms/community/community_test.exs @@ -93,6 +93,7 @@ defmodule GroupherServer.Test.CMS.Community do assert user.id in community.meta.subscribed_user_ids end + @tag :wip2 test "user unsubscribe a community will update the community's subscribted info", ~m(user community)a do {:ok, _} = CMS.subscribe_community(community, user) diff --git a/test/groupher_server_web/mutation/cms/cms_test.exs b/test/groupher_server_web/mutation/cms/cms_test.exs index 473a11aea..ec08ca434 100644 --- a/test/groupher_server_web/mutation/cms/cms_test.exs +++ b/test/groupher_server_web/mutation/cms/cms_test.exs @@ -401,7 +401,7 @@ defmodule GroupherServer.Test.Mutation.CMS.Basic do result = rule_conn |> mutation_result(@set_editor_query, variables, "setEditor") - assert result["id"] == to_string(user.id) + assert result["id"] == to_string(community.id) end @unset_editor_query """ @@ -508,7 +508,6 @@ defmodule GroupherServer.Test.Mutation.CMS.Basic do assert guest_conn |> mutation_get_error?(@subscribe_query, variables, ecode(:account_login)) end - @tag :wip2 test "subscribed community should inc it's own geo info", ~m(user community)a do login_conn = simu_conn(:user, user) @@ -529,7 +528,7 @@ defmodule GroupherServer.Test.Mutation.CMS.Basic do } } """ - @tag :wip2 + test "login user can unsubscribe community", ~m(user community)a do {:ok, cur_subscribers} = CMS.community_members(:subscribers, %Community{id: community.id}, %{page: 1, size: 10})