diff --git a/cover/excoveralls.json b/cover/excoveralls.json index 2025c5307..24b4cd02f 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 ArticleOperation,\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_content(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 # ArticleOperation\n # >> set flag on article, like: pin / unpin article\n defdelegate set_flag(queryable, id, attrs, user), to: ArticleOperation\n # >> tag: set / unset\n defdelegate set_tag(community, thread, tag, content_id), to: ArticleOperation\n defdelegate unset_tag(thread, tag, content_id), to: ArticleOperation\n # >> community: set / unset\n defdelegate set_community(community, thread, content_id), to: ArticleOperation\n defdelegate unset_community(community, thread, content_id), to: ArticleOperation\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 list_comments(thread, content_id, filters), to: CommentCURD\n defdelegate list_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 list_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 trash 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 trash 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.Utils.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.ArticleOperation\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_content(%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 ArticleOperation.set_community(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.Utils.Helper\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(: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.Utils.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 list_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 list_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 trash)\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(:trash, :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.Utils.Helper\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 :cms_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(: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(: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(: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.ArticleOperation do\n @moduledoc \"\"\"\n set / unset operations for Article-like resource\n \"\"\"\n import GroupherServer.CMS.Utils.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, trash / 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, %{trash: _} = attrs, %User{} = _user) do\n queryable |> ORM.find_update(id, attrs)\n end\n\n def set_community(%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 unset_community(%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, :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, :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.Utils.Helper\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.Utils.Helper 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.Utils.Helper\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.Utils.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.Utils.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_content(_root, ~m(community_id thread)a = args, %{context: %{cur_user: user}}) do\n CMS.create_content(%Community{id: community_id}, thread, args, user)\n end\n\n def update_content(_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, %{trash: true}, user)\n end\n\n def undo_trash_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{trash: 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 set_community(_root, ~m(thread id community_id)a, _info) do\n CMS.set_community(%Community{id: community_id}, thread, id)\n end\n\n def unset_community(_root, ~m(thread id community_id)a, _info) do\n CMS.unset_community(%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.list_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.Utils.Helper\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(:trash, :boolean)\n field(:tags, list_of(: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(:trash, :boolean)\n\n # field(:tags, list_of(: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(:trash, :boolean)\n\n # field(:tags, list_of(: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(: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 :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(: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, :cms_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(:tag) do\n arg(:community_id, :id)\n arg(:community, :string)\n arg(:thread, :cms_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, :cms_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, :cms_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, :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, :cms_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, :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, :cms_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 :set_community, :community do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :cms_thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->t?.community.set\")\n resolve(&R.CMS.set_community/3)\n end\n\n # TODO: can't not unset the oldest community\n field :unset_community, :community do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :cms_thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->t?.community.unset\")\n resolve(&R.CMS.unset_community/3)\n end\n\n field :reaction, :article do\n arg(:id, non_null(:id))\n arg(:thread, non_null(:cms_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(:cms_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(: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(:tag, attrs), do: mock_meta(: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(:tag), do: CMS.Tag |> struct(mock_meta(: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 list_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.Utils.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, :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, :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, :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.Utils.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, :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 trash)\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(:trash, :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 trash 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(:trash, :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.Utils.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.Utils.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(:cms_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, :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, :cms_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, :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, :cms_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, :tag do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :cms_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.Utils.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.trash\" => 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.set\",\n \"post.community.unset\",\n \"job.community.set\",\n \"job.community.unset\",\n \"post.pin\",\n \"post.undo_pin\",\n \"post.trash\",\n \"post.undo_trash\"\n ],\n community: [\n # thread\n \"thread.set\",\n \"thread.unset\",\n \"post.edit\",\n \"post.trash\",\n \"post.delete\",\n \"job.edit\",\n \"job.trash\",\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.trash\")\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, :cms_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, :cms_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(:cms_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, trash: 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 {: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 {:trash, bool}, queryable ->\n queryable\n |> where([p], p.trash == ^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.Utils.Loader.data())\n |> Dataloader.add_source(CMS, CMS.Utils.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, :cms_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_content/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 \"trash 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.trash\")\n\n resolve(&R.CMS.trash_post/3)\n end\n\n @desc \"trash 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_content/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, :cms_thread, default_value: :job)\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.create_content/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_content/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.Utils.CommonTypes 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.Utils.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 ArticleOperation,\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_content(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 # ArticleOperation\n # >> set flag on article, like: pin / unpin article\n defdelegate set_flag(queryable, id, attrs, user), to: ArticleOperation\n # >> tag: set / unset\n defdelegate set_tag(community, thread, tag, content_id), to: ArticleOperation\n defdelegate unset_tag(thread, tag, content_id), to: ArticleOperation\n # >> community: set / unset\n defdelegate set_community(community, thread, content_id), to: ArticleOperation\n defdelegate unset_community(community, thread, content_id), to: ArticleOperation\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 list_comments(thread, content_id, filters), to: CommentCURD\n defdelegate list_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 list_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 trash 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 trash 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.ArticleOperation\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_content(%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 ArticleOperation.set_community(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(: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 list_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 list_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 trash)\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(:trash, :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 :cms_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(: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(: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(: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.ArticleOperation 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, trash / 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, %{trash: _} = attrs, %User{} = _user) do\n queryable |> ORM.find_update(id, attrs)\n end\n\n def set_community(%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 unset_community(%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, :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, :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_content(_root, ~m(community_id thread)a = args, %{context: %{cur_user: user}}) do\n CMS.create_content(%Community{id: community_id}, thread, args, user)\n end\n\n def update_content(_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, %{trash: true}, user)\n end\n\n def undo_trash_post(_root, %{id: id}, %{context: %{cur_user: user}}) do\n CMS.set_flag(Post, id, %{trash: 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 set_community(_root, ~m(thread id community_id)a, _info) do\n CMS.set_community(%Community{id: community_id}, thread, id)\n end\n\n def unset_community(_root, ~m(thread id community_id)a, _info) do\n CMS.unset_community(%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.list_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(:trash, :boolean)\n field(:tags, list_of(: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(:trash, :boolean)\n\n # field(:tags, list_of(: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(:trash, :boolean)\n\n # field(:tags, list_of(: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(: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 :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(: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, :cms_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(:tag) do\n arg(:community_id, :id)\n arg(:community, :string)\n arg(:thread, :cms_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, :cms_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, :cms_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, :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, :cms_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, :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, :cms_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 :set_community, :community do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :cms_thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->t?.community.set\")\n resolve(&R.CMS.set_community/3)\n end\n\n # TODO: can't not unset the oldest community\n field :unset_community, :community do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :cms_thread, default_value: :post)\n\n middleware(M.Authorize, :login)\n middleware(M.Passport, claim: \"cms->t?.community.unset\")\n resolve(&R.CMS.unset_community/3)\n end\n\n field :reaction, :article do\n arg(:id, non_null(:id))\n arg(:thread, non_null(:cms_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(:cms_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(: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(:tag, attrs), do: mock_meta(: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(:tag), do: CMS.Tag |> struct(mock_meta(: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 list_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, :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, :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, :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, :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 trash)\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(:trash, :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 trash 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(:trash, :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(:cms_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, :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, :cms_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, :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, :cms_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, :tag do\n arg(:id, non_null(:id))\n arg(:community_id, non_null(:id))\n arg(:thread, :cms_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.trash\" => 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.set\",\n \"post.community.unset\",\n \"job.community.set\",\n \"job.community.unset\",\n \"post.pin\",\n \"post.undo_pin\",\n \"post.trash\",\n \"post.undo_trash\"\n ],\n community: [\n # thread\n \"thread.set\",\n \"thread.unset\",\n \"post.edit\",\n \"post.trash\",\n \"post.delete\",\n \"job.edit\",\n \"job.trash\",\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.trash\")\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, :cms_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, :cms_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(:cms_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, trash: 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 {: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 {:trash, bool}, queryable ->\n queryable\n |> where([p], p.trash == ^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, :cms_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_content/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 \"trash 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.trash\")\n\n resolve(&R.CMS.trash_post/3)\n end\n\n @desc \"trash 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_content/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, :cms_thread, default_value: :job)\n\n middleware(M.Authorize, :login)\n resolve(&R.CMS.create_content/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_content/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 diff --git a/lib/groupher_server/accounts/delegates/collect_folder.ex b/lib/groupher_server/accounts/delegates/collect_folder.ex index 173227632..2bd1c0eda 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.Utils.Matcher2 + import GroupherServer.CMS.Helper.Matcher2 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 1c5387612..f98d6c3cc 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.Utils.Matcher + import GroupherServer.CMS.Helper.Matcher alias Helper.{ORM, QueryBuilder} # alias GroupherServer.{Accounts, Repo} diff --git a/lib/groupher_server/accounts/delegates/upvoted_articles.ex b/lib/groupher_server/accounts/delegates/upvoted_articles.ex index bca1a728e..e7ba8312d 100644 --- a/lib/groupher_server/accounts/delegates/upvoted_articles.ex +++ b/lib/groupher_server/accounts/delegates/upvoted_articles.ex @@ -2,7 +2,7 @@ defmodule GroupherServer.Accounts.Delegate.UpvotedArticles do @moduledoc """ get contents(posts, jobs ...) that user upvotes """ - # import GroupherServer.CMS.Utils.Matcher + # import GroupherServer.CMS.Helper.Matcher import Ecto.Query, warn: false import Helper.Utils, only: [done: 1] import ShortMaps diff --git a/lib/groupher_server/accounts/utils/loader.ex b/lib/groupher_server/accounts/helper/loader.ex similarity index 96% rename from lib/groupher_server/accounts/utils/loader.ex rename to lib/groupher_server/accounts/helper/loader.ex index 01f2c9346..15fed0c6f 100644 --- a/lib/groupher_server/accounts/utils/loader.ex +++ b/lib/groupher_server/accounts/helper/loader.ex @@ -1,4 +1,4 @@ -defmodule GroupherServer.Accounts.Utils.Loader do +defmodule GroupherServer.Accounts.Helper.Loader do @moduledoc """ dataloader for accounts """ diff --git a/lib/groupher_server/accounts/user.ex b/lib/groupher_server/accounts/user.ex index 7d62a0713..f665f9642 100644 --- a/lib/groupher_server/accounts/user.ex +++ b/lib/groupher_server/accounts/user.ex @@ -4,7 +4,7 @@ defmodule GroupherServer.Accounts.User do use Ecto.Schema - # import GroupherServerWeb.Schema.Utils.Helper + # import GroupherServerWeb.Schema.Helper.Fields import Ecto.Changeset alias GroupherServer.Accounts.{ diff --git a/lib/groupher_server/cms/delegates/abuse_report.ex b/lib/groupher_server/cms/delegates/abuse_report.ex index dc5d6c7ba..2efe1142f 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] - import GroupherServer.CMS.Utils.Matcher2 + import GroupherServer.CMS.Helper.Matcher2 # import ShortMaps alias Helper.{ORM} diff --git a/lib/groupher_server/cms/delegates/article_comment.ex b/lib/groupher_server/cms/delegates/article_comment.ex index 9bee1f4d3..d4b110e67 100644 --- a/lib/groupher_server/cms/delegates/article_comment.ex +++ b/lib/groupher_server/cms/delegates/article_comment.ex @@ -6,7 +6,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleComment do import Helper.Utils, only: [done: 1] import Helper.ErrorCode - import GroupherServer.CMS.Utils.Matcher2 + import GroupherServer.CMS.Helper.Matcher2 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 fb32a5d07..a0f23e393 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.Utils.Matcher2 + import GroupherServer.CMS.Helper.Matcher2 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 b8a05feb5..fcf0aa81a 100644 --- a/lib/groupher_server/cms/delegates/article_curd.ex +++ b/lib/groupher_server/cms/delegates/article_curd.ex @@ -4,8 +4,8 @@ defmodule GroupherServer.CMS.Delegate.ArticleCURD do """ import Ecto.Query, warn: false - import GroupherServer.CMS.Utils.Matcher2 - import GroupherServer.CMS.Utils.Matcher, only: [match_action: 2] + import GroupherServer.CMS.Helper.Matcher2 + import GroupherServer.CMS.Helper.Matcher, only: [match_action: 2] import Helper.Utils, only: [done: 1, pick_by: 2, integerfy: 1, strip_struct: 1] import Helper.ErrorCode diff --git a/lib/groupher_server/cms/delegates/article_operation.ex b/lib/groupher_server/cms/delegates/article_operation.ex index 25b59c367..6dac2d98b 100644 --- a/lib/groupher_server/cms/delegates/article_operation.ex +++ b/lib/groupher_server/cms/delegates/article_operation.ex @@ -2,13 +2,13 @@ defmodule GroupherServer.CMS.Delegate.ArticleOperation do @moduledoc """ set / unset operations for Article-like resource """ - import GroupherServer.CMS.Utils.Matcher + import GroupherServer.CMS.Helper.Matcher import Ecto.Query, warn: false import Helper.ErrorCode import ShortMaps import Helper.Utils, only: [strip_struct: 1] - import GroupherServer.CMS.Utils.Matcher2 + import GroupherServer.CMS.Helper.Matcher2 alias Helper.Types, as: T alias Helper.ORM diff --git a/lib/groupher_server/cms/delegates/article_reaction.ex b/lib/groupher_server/cms/delegates/article_reaction.ex index fb65ce41d..a2cae6b03 100644 --- a/lib/groupher_server/cms/delegates/article_reaction.ex +++ b/lib/groupher_server/cms/delegates/article_reaction.ex @@ -2,7 +2,7 @@ defmodule GroupherServer.CMS.Delegate.ArticleReaction do @moduledoc """ reaction[upvote, collect, watch ...] on article [post, job...] """ - import GroupherServer.CMS.Utils.Matcher2 + import GroupherServer.CMS.Helper.Matcher2 import Ecto.Query, warn: false import Helper.Utils, only: [done: 1, strip_struct: 1] # import Helper.ErrorCode diff --git a/lib/groupher_server/cms/delegates/comment_curd.ex b/lib/groupher_server/cms/delegates/comment_curd.ex index e8b3f0afe..8b730f1b3 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.Utils.Matcher + import GroupherServer.CMS.Helper.Matcher import ShortMaps alias Helper.{ORM, QueryBuilder} diff --git a/lib/groupher_server/cms/delegates/community_curd.ex b/lib/groupher_server/cms/delegates/community_curd.ex index d5d97fbad..ab550d9d9 100644 --- a/lib/groupher_server/cms/delegates/community_curd.ex +++ b/lib/groupher_server/cms/delegates/community_curd.ex @@ -3,7 +3,7 @@ defmodule GroupherServer.CMS.Delegate.CommunityCURD do community curd """ import Ecto.Query, warn: false - import GroupherServer.CMS.Utils.Matcher + 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/delegates/seeds.ex b/lib/groupher_server/cms/delegates/seeds.ex index 206a2785e..b20e648e3 100644 --- a/lib/groupher_server/cms/delegates/seeds.ex +++ b/lib/groupher_server/cms/delegates/seeds.ex @@ -7,7 +7,7 @@ defmodule GroupherServer.CMS.Delegate.Seeds do import Ecto.Query, warn: false @oss_endpoint "https://cps-oss.oss-cn-shanghai.aliyuncs.com" - # import GroupherServer.CMS.Utils.Matcher + # 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/utils/loader.ex b/lib/groupher_server/cms/helper/loader.ex similarity index 99% rename from lib/groupher_server/cms/utils/loader.ex rename to lib/groupher_server/cms/helper/loader.ex index 699cfde07..d166b36ab 100644 --- a/lib/groupher_server/cms/utils/loader.ex +++ b/lib/groupher_server/cms/helper/loader.ex @@ -1,4 +1,4 @@ -defmodule GroupherServer.CMS.Utils.Loader do +defmodule GroupherServer.CMS.Helper.Loader do @moduledoc """ dataloader for cms context """ diff --git a/lib/groupher_server/cms/utils/matcher.ex b/lib/groupher_server/cms/helper/matcher.ex similarity index 98% rename from lib/groupher_server/cms/utils/matcher.ex rename to lib/groupher_server/cms/helper/matcher.ex index 732b2ebbd..a2f8ac71b 100644 --- a/lib/groupher_server/cms/utils/matcher.ex +++ b/lib/groupher_server/cms/helper/matcher.ex @@ -1,4 +1,4 @@ -defmodule GroupherServer.CMS.Utils.Matcher do +defmodule GroupherServer.CMS.Helper.Matcher do @moduledoc """ this module defined the matches and handy guard ... """ diff --git a/lib/groupher_server/cms/utils/matcher2.ex b/lib/groupher_server/cms/helper/matcher2.ex similarity index 96% rename from lib/groupher_server/cms/utils/matcher2.ex rename to lib/groupher_server/cms/helper/matcher2.ex index de6975ddf..f583dbdd1 100644 --- a/lib/groupher_server/cms/utils/matcher2.ex +++ b/lib/groupher_server/cms/helper/matcher2.ex @@ -1,4 +1,4 @@ -defmodule GroupherServer.CMS.Utils.Matcher2 do +defmodule GroupherServer.CMS.Helper.Matcher2 do @moduledoc """ this module defined the matches and handy guard ... """ diff --git a/lib/groupher_server_web/middleware/passport_loader.ex b/lib/groupher_server_web/middleware/passport_loader.ex index b783c6857..7d5b5aad1 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.Utils.Matcher + import GroupherServer.CMS.Helper.Matcher import Helper.Utils import Helper.ErrorCode import ShortMaps diff --git a/lib/groupher_server_web/resolvers/cms_resolver.ex b/lib/groupher_server_web/resolvers/cms_resolver.ex index 4dd8c86ef..712c48805 100644 --- a/lib/groupher_server_web/resolvers/cms_resolver.ex +++ b/lib/groupher_server_web/resolvers/cms_resolver.ex @@ -1,7 +1,7 @@ defmodule GroupherServerWeb.Resolvers.CMS do @moduledoc false - import GroupherServer.CMS.Utils.Matcher + import GroupherServer.CMS.Helper.Matcher import ShortMaps import Ecto.Query, warn: false diff --git a/lib/groupher_server_web/schema.ex b/lib/groupher_server_web/schema.ex index 2393305e2..cb97f8e13 100644 --- a/lib/groupher_server_web/schema.ex +++ b/lib/groupher_server_web/schema.ex @@ -6,12 +6,12 @@ defmodule GroupherServerWeb.Schema do # use ApolloTracing alias GroupherServerWeb.Middleware, as: M - alias GroupherServerWeb.Schema.{Account, Billing, CMS, Delivery, Statistics, Utils} + alias GroupherServerWeb.Schema.{Account, Billing, CMS, Delivery, Statistics, Helper} import_types(Absinthe.Type.Custom) # utils - import_types(Utils.CommonTypes) + import_types(Helper.Metrics) # account import_types(Account.Types) @@ -91,8 +91,8 @@ defmodule GroupherServerWeb.Schema do alias GroupherServer.{Accounts, CMS} Dataloader.new() - |> Dataloader.add_source(Accounts, Accounts.Utils.Loader.data()) - |> Dataloader.add_source(CMS, CMS.Utils.Loader.data()) + |> Dataloader.add_source(Accounts, Accounts.Helper.Loader.data()) + |> Dataloader.add_source(CMS, CMS.Helper.Loader.data()) end def context(ctx) do diff --git a/lib/groupher_server_web/schema/utils/helper.ex b/lib/groupher_server_web/schema/Helper/fields.ex similarity index 83% rename from lib/groupher_server_web/schema/utils/helper.ex rename to lib/groupher_server_web/schema/Helper/fields.ex index afeb95bfa..4fbae7e5c 100644 --- a/lib/groupher_server_web/schema/utils/helper.ex +++ b/lib/groupher_server_web/schema/Helper/fields.ex @@ -1,4 +1,4 @@ -defmodule GroupherServerWeb.Schema.Utils.Helper do +defmodule GroupherServerWeb.Schema.Helper.Fields do @moduledoc """ common fields """ @@ -97,46 +97,6 @@ defmodule GroupherServerWeb.Schema.Utils.Helper do end end - @doc """ - query generator for threads, like: - - post, page_posts ... - """ - defmacro article_queries(thread) do - quote do - @desc unquote("get #{thread} by id") - field unquote(thread), non_null(unquote(thread)) do - arg(:id, non_null(:id)) - arg(:thread, unquote(:"#{thread}_thread"), default_value: unquote(thread)) - - resolve(&R.CMS.read_article/3) - end - - @desc unquote("get paged #{thread}s") - field unquote(:"paged_#{thread}s"), unquote(:"paged_#{thread}s") do - arg(:thread, unquote(:"#{thread}_thread"), default_value: unquote(thread)) - arg(:filter, non_null(unquote(:"paged_#{thread}s_filter"))) - - middleware(M.PageSizeProof) - resolve(&R.CMS.paged_articles/3) - end - end - end - - defmacro article_reacted_users_query(action, resolver) do - quote do - @desc unquote("get paged #{action}ed users of an article") - field unquote(:"#{action}ed_users"), :paged_users do - arg(:id, non_null(:id)) - arg(:thread, :cms_thread, default_value: :post) - arg(:filter, non_null(:paged_filter)) - - middleware(M.PageSizeProof) - resolve(unquote(resolver)) - end - end - end - defmacro comments_fields do quote do field(:id, :id) diff --git a/lib/groupher_server_web/schema/utils/common_types.ex b/lib/groupher_server_web/schema/Helper/metrics.ex similarity index 86% rename from lib/groupher_server_web/schema/utils/common_types.ex rename to lib/groupher_server_web/schema/Helper/metrics.ex index e4d0c2838..d97fa5eb2 100644 --- a/lib/groupher_server_web/schema/utils/common_types.ex +++ b/lib/groupher_server_web/schema/Helper/metrics.ex @@ -1,8 +1,8 @@ -defmodule GroupherServerWeb.Schema.Utils.CommonTypes do +defmodule GroupherServerWeb.Schema.Helper.Metrics do @moduledoc """ common types might be used in all context """ - import GroupherServerWeb.Schema.Utils.Helper + import GroupherServerWeb.Schema.Helper.Fields use Absinthe.Schema.Notation diff --git a/lib/groupher_server_web/schema/Helper/mutations.ex b/lib/groupher_server_web/schema/Helper/mutations.ex new file mode 100644 index 000000000..0e5a945d6 --- /dev/null +++ b/lib/groupher_server_web/schema/Helper/mutations.ex @@ -0,0 +1,29 @@ +defmodule GroupherServerWeb.Schema.Helper.Mutations do + @moduledoc """ + common fields + """ + alias GroupherServerWeb.Middleware, as: M + alias GroupherServerWeb.Resolvers, as: R + + defmacro article_upvote_mutation(thread) do + quote do + @desc unquote("upvote to #{thread}") + field unquote(:"upvote_#{thread}"), :article do + arg(:id, non_null(:id)) + arg(:thread, unquote(:"#{thread}_thread"), default_value: unquote(thread)) + + middleware(M.Authorize, :login) + resolve(&R.CMS.upvote_article/3) + end + + @desc unquote("undo upvote to #{thread}") + field unquote(:"undo_upvote_#{thread}"), :article do + arg(:id, non_null(:id)) + arg(:thread, unquote(:"#{thread}_thread"), default_value: unquote(thread)) + + middleware(M.Authorize, :login) + resolve(&R.CMS.undo_upvote_article/3) + end + end + end +end diff --git a/lib/groupher_server_web/schema/Helper/queries.ex b/lib/groupher_server_web/schema/Helper/queries.ex new file mode 100644 index 000000000..57db390c2 --- /dev/null +++ b/lib/groupher_server_web/schema/Helper/queries.ex @@ -0,0 +1,47 @@ +defmodule GroupherServerWeb.Schema.Helper.Queries do + @moduledoc """ + common fields + """ + alias GroupherServerWeb.Middleware, as: M + alias GroupherServerWeb.Resolvers, as: R + + @doc """ + query generator for threads, like: + + post, page_posts ... + """ + defmacro article_queries(thread) do + quote do + @desc unquote("get #{thread} by id") + field unquote(thread), non_null(unquote(thread)) do + arg(:id, non_null(:id)) + arg(:thread, unquote(:"#{thread}_thread"), default_value: unquote(thread)) + + resolve(&R.CMS.read_article/3) + end + + @desc unquote("get paged #{thread}s") + field unquote(:"paged_#{thread}s"), unquote(:"paged_#{thread}s") do + arg(:thread, unquote(:"#{thread}_thread"), default_value: unquote(thread)) + arg(:filter, non_null(unquote(:"paged_#{thread}s_filter"))) + + middleware(M.PageSizeProof) + resolve(&R.CMS.paged_articles/3) + end + end + end + + defmacro article_reacted_users_query(action, resolver) do + quote do + @desc unquote("get paged #{action}ed users of an article") + field unquote(:"#{action}ed_users"), :paged_users do + arg(:id, non_null(:id)) + arg(:thread, :cms_thread, default_value: :post) + arg(:filter, non_null(:paged_filter)) + + middleware(M.PageSizeProof) + resolve(unquote(resolver)) + end + end + end +end diff --git a/lib/groupher_server_web/schema/account/account_misc.ex b/lib/groupher_server_web/schema/account/account_misc.ex index 35bb6aa63..bceb17636 100644 --- a/lib/groupher_server_web/schema/account/account_misc.ex +++ b/lib/groupher_server_web/schema/account/account_misc.ex @@ -2,7 +2,7 @@ defmodule GroupherServerWeb.Schema.Account.Misc do @moduledoc false use Absinthe.Schema.Notation - import GroupherServerWeb.Schema.Utils.Helper + import GroupherServerWeb.Schema.Helper.Fields @desc "article_filter doc" input_object :paged_users_filter do diff --git a/lib/groupher_server_web/schema/account/account_types.ex b/lib/groupher_server_web/schema/account/account_types.ex index d03ef22bb..b8f450e67 100644 --- a/lib/groupher_server_web/schema/account/account_types.ex +++ b/lib/groupher_server_web/schema/account/account_types.ex @@ -4,7 +4,7 @@ defmodule GroupherServerWeb.Schema.Account.Types do """ use Helper.GqlSchemaSuite - import GroupherServerWeb.Schema.Utils.Helper + import GroupherServerWeb.Schema.Helper.Fields import Absinthe.Resolution.Helpers alias GroupherServer.Accounts diff --git a/lib/groupher_server_web/schema/billing/billing_types.ex b/lib/groupher_server_web/schema/billing/billing_types.ex index 311b7fd92..a629b15b6 100644 --- a/lib/groupher_server_web/schema/billing/billing_types.ex +++ b/lib/groupher_server_web/schema/billing/billing_types.ex @@ -4,7 +4,7 @@ defmodule GroupherServerWeb.Schema.Billing.Types do """ use Helper.GqlSchemaSuite - import GroupherServerWeb.Schema.Utils.Helper + import GroupherServerWeb.Schema.Helper.Fields enum :bill_state_enum do value(:pending) diff --git a/lib/groupher_server_web/schema/cms/cms_misc.ex b/lib/groupher_server_web/schema/cms/cms_misc.ex index b69323fd9..6cc624e1b 100644 --- a/lib/groupher_server_web/schema/cms/cms_misc.ex +++ b/lib/groupher_server_web/schema/cms/cms_misc.ex @@ -1,7 +1,10 @@ defmodule GroupherServerWeb.Schema.CMS.Misc do + @moduledoc """ + common metrics in queries + """ use Absinthe.Schema.Notation - import GroupherServerWeb.Schema.Utils.Helper + import GroupherServerWeb.Schema.Helper.Fields alias GroupherServer.CMS diff --git a/lib/groupher_server_web/schema/cms/cms_queries.ex b/lib/groupher_server_web/schema/cms/cms_queries.ex index 2ae1c61e0..e5b71a932 100644 --- a/lib/groupher_server_web/schema/cms/cms_queries.ex +++ b/lib/groupher_server_web/schema/cms/cms_queries.ex @@ -2,7 +2,7 @@ defmodule GroupherServerWeb.Schema.CMS.Queries do @moduledoc """ CMS queries """ - import GroupherServerWeb.Schema.Utils.Helper + import GroupherServerWeb.Schema.Helper.Queries use Helper.GqlSchemaSuite diff --git a/lib/groupher_server_web/schema/cms/cms_types.ex b/lib/groupher_server_web/schema/cms/cms_types.ex index f39da83ca..a60a3c751 100644 --- a/lib/groupher_server_web/schema/cms/cms_types.ex +++ b/lib/groupher_server_web/schema/cms/cms_types.ex @@ -4,7 +4,7 @@ defmodule GroupherServerWeb.Schema.CMS.Types do """ use Helper.GqlSchemaSuite - import GroupherServerWeb.Schema.Utils.Helper + import GroupherServerWeb.Schema.Helper.Fields import Ecto.Query, warn: false import Absinthe.Resolution.Helpers, only: [dataloader: 2, on_load: 2] diff --git a/lib/groupher_server_web/schema/cms/mutations/job.ex b/lib/groupher_server_web/schema/cms/mutations/job.ex index 930d7f721..0d05b06dd 100644 --- a/lib/groupher_server_web/schema/cms/mutations/job.ex +++ b/lib/groupher_server_web/schema/cms/mutations/job.ex @@ -3,6 +3,7 @@ defmodule GroupherServerWeb.Schema.CMS.Mutations.Job do CMS mutations for job """ use Helper.GqlSchemaSuite + import GroupherServerWeb.Schema.Helper.Mutations object :cms_job_mutations do @desc "create a job" @@ -37,6 +38,42 @@ defmodule GroupherServerWeb.Schema.CMS.Mutations.Job do middleware(M.Statistics.MakeContribute, for: [:user, :community]) end + @desc "update a cms/job" + field :update_job, :job do + arg(:id, non_null(:id)) + arg(:title, :string) + arg(:body, :string) + arg(:digest, :string) + arg(:length, :integer) + arg(:salary, :string) + arg(:copy_right, :string) + arg(:desc, :string) + arg(:link_addr, :string) + + arg(:company, :string) + arg(:company_logo, :string) + arg(:company_link, :string) + + arg(:exp, :string) + arg(:education, :string) + arg(:field, :string) + arg(:finance, :string) + arg(:scale, :string) + arg(:tags, list_of(:ids)) + + # ... + + middleware(M.Authorize, :login) + middleware(M.PassportLoader, source: :job) + middleware(M.Passport, claim: "owner;cms->c?->job.edit") + + resolve(&R.CMS.update_content/3) + end + + ############# + article_upvote_mutation(:job) + ############# + @desc "pin a job" field :pin_job, :job do arg(:id, non_null(:id)) @@ -97,37 +134,5 @@ defmodule GroupherServerWeb.Schema.CMS.Mutations.Job do resolve(&R.CMS.delete_content/3) end - - @desc "update a cms/job" - field :update_job, :job do - arg(:id, non_null(:id)) - arg(:title, :string) - arg(:body, :string) - arg(:digest, :string) - arg(:length, :integer) - arg(:salary, :string) - arg(:copy_right, :string) - arg(:desc, :string) - arg(:link_addr, :string) - - arg(:company, :string) - arg(:company_logo, :string) - arg(:company_link, :string) - - arg(:exp, :string) - arg(:education, :string) - arg(:field, :string) - arg(:finance, :string) - arg(:scale, :string) - arg(:tags, list_of(:ids)) - - # ... - - middleware(M.Authorize, :login) - middleware(M.PassportLoader, source: :job) - middleware(M.Passport, claim: "owner;cms->c?->job.edit") - - resolve(&R.CMS.update_content/3) - end end end diff --git a/lib/groupher_server_web/schema/cms/mutations/operation.ex b/lib/groupher_server_web/schema/cms/mutations/operation.ex index c8367c805..348062fe2 100644 --- a/lib/groupher_server_web/schema/cms/mutations/operation.ex +++ b/lib/groupher_server_web/schema/cms/mutations/operation.ex @@ -129,23 +129,5 @@ defmodule GroupherServerWeb.Schema.CMS.Mutations.Operation do middleware(M.Passport, claim: "cms->t?.community.unset") resolve(&R.CMS.unset_community/3) end - - @desc "upvote an article" - field :upvote_article, :article do - arg(:id, non_null(:id)) - arg(:thread, :cms_thread, default_value: :post) - - middleware(M.Authorize, :login) - resolve(&R.CMS.upvote_article/3) - end - - @desc "undo upvote an article" - field :undo_upvote_article, :article do - arg(:id, non_null(:id)) - arg(:thread, :cms_thread, default_value: :post) - - middleware(M.Authorize, :login) - resolve(&R.CMS.undo_upvote_article/3) - end end end diff --git a/lib/groupher_server_web/schema/cms/mutations/post.ex b/lib/groupher_server_web/schema/cms/mutations/post.ex index 860c4d84d..cdd72c6b2 100644 --- a/lib/groupher_server_web/schema/cms/mutations/post.ex +++ b/lib/groupher_server_web/schema/cms/mutations/post.ex @@ -4,6 +4,8 @@ defmodule GroupherServerWeb.Schema.CMS.Mutations.Post do """ use Helper.GqlSchemaSuite + import GroupherServerWeb.Schema.Helper.Mutations + object :cms_post_mutations do @desc "create a post" field :create_post, :post do @@ -25,6 +27,27 @@ defmodule GroupherServerWeb.Schema.CMS.Mutations.Post do middleware(M.Statistics.MakeContribute, for: [:user, :community]) end + @desc "update a cms/post" + field :update_post, :post do + arg(:id, non_null(:id)) + arg(:title, :string) + arg(:body, :string) + arg(:digest, :string) + arg(:copy_right, :string) + arg(:link_addr, :string) + arg(:tags, list_of(:ids)) + + middleware(M.Authorize, :login) + middleware(M.PassportLoader, source: :post) + middleware(M.Passport, claim: "owner;cms->c?->post.edit") + + resolve(&R.CMS.update_content/3) + end + + ############# + article_upvote_mutation(:post) + ############# + @desc "pin a post" field :pin_post, :post do arg(:id, non_null(:id)) @@ -86,22 +109,5 @@ defmodule GroupherServerWeb.Schema.CMS.Mutations.Post do resolve(&R.CMS.delete_content/3) end - - @desc "update a cms/post" - field :update_post, :post do - arg(:id, non_null(:id)) - arg(:title, :string) - arg(:body, :string) - arg(:digest, :string) - arg(:copy_right, :string) - arg(:link_addr, :string) - arg(:tags, list_of(:ids)) - - middleware(M.Authorize, :login) - middleware(M.PassportLoader, source: :post) - middleware(M.Passport, claim: "owner;cms->c?->post.edit") - - resolve(&R.CMS.update_content/3) - end end end diff --git a/lib/groupher_server_web/schema/delivery/delivery_types.ex b/lib/groupher_server_web/schema/delivery/delivery_types.ex index 5ca84efac..138e55dec 100644 --- a/lib/groupher_server_web/schema/delivery/delivery_types.ex +++ b/lib/groupher_server_web/schema/delivery/delivery_types.ex @@ -1,7 +1,7 @@ defmodule GroupherServerWeb.Schema.Delivery.Types do use Absinthe.Schema.Notation - import GroupherServerWeb.Schema.Utils.Helper + import GroupherServerWeb.Schema.Helper.Fields import Helper.Utils, only: [get_config: 2] @page_size get_config(:general, :page_size) diff --git a/lib/groupher_server_web/schema/statistics/statistics_types.ex b/lib/groupher_server_web/schema/statistics/statistics_types.ex index 421f526e4..662f992f7 100644 --- a/lib/groupher_server_web/schema/statistics/statistics_types.ex +++ b/lib/groupher_server_web/schema/statistics/statistics_types.ex @@ -1,7 +1,7 @@ defmodule GroupherServerWeb.Schema.Statistics.Types do use Absinthe.Schema.Notation - # import GroupherServerWeb.Schema.Utils.Helper + # import GroupherServerWeb.Schema.Helper.Fields # alias GroupherServer.Accounts diff --git a/test/groupher_server/cms/comments/job_comment_emotions_test.exs b/test/groupher_server/cms/comments/job_comment_emotions_test.exs index 7c3cc5369..3cc538cc6 100644 --- a/test/groupher_server/cms/comments/job_comment_emotions_test.exs +++ b/test/groupher_server/cms/comments/job_comment_emotions_test.exs @@ -21,7 +21,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentEmotions do end describe "[emotion in paged article comment]" do - @tag :wip3 + @tag :wip2 test "login user should got viewer has emotioned status", ~m(job user)a do total_count = 0 page_number = 10 @@ -76,7 +76,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentEmotions do assert @default_emotions == emotions end - @tag :wip3 + @tag :wip2 test "can make emotion to comment", ~m(job user user2)a do parent_content = "parent comment" {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, parent_content, user) @@ -91,7 +91,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentEmotions do assert user_exist_in?(user2, emotions.latest_downvote_users) end - @tag :wip3 + @tag :wip2 test "can undo emotion to comment", ~m(job user user2)a do parent_content = "parent comment" {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, parent_content, user) @@ -114,7 +114,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentEmotions do assert not user_exist_in?(user2, emotions.latest_downvote_users) end - @tag :wip3 + @tag :wip2 test "same user make same emotion to same comment.", ~m(job user)a do parent_content = "parent comment" {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, parent_content, user) @@ -128,7 +128,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentEmotions do assert user_exist_in?(user, parent_comment.emotions.latest_downvote_users) end - @tag :wip3 + @tag :wip2 test "same user same emotion to same comment only have one user_emotion record", ~m(job user)a do parent_content = "parent comment" @@ -152,7 +152,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentEmotions do assert record.heart end - @tag :wip3 + @tag :wip2 test "different user can make same emotions on same comment", ~m(job user user2 user3)a do {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, "parent comment", user) diff --git a/test/groupher_server/cms/comments/job_comment_replies_test.exs b/test/groupher_server/cms/comments/job_comment_replies_test.exs index 6a127ca62..67081286f 100644 --- a/test/groupher_server/cms/comments/job_comment_replies_test.exs +++ b/test/groupher_server/cms/comments/job_comment_replies_test.exs @@ -33,7 +33,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentReplies do assert exist_in?(replyed_comment, parent_comment.replies) end - @tag :wip3 + @tag :wip2 test "deleted comment can not be reply", ~m(job user user2)a do parent_content = "parent comment" reply_content = "reply comment" @@ -64,7 +64,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentReplies do assert exist_in?(replyed_comment_2, parent_comment.replies) end - @tag :wip3 + @tag :wip2 test "reply to reply inside a comment should belong same parent comment", ~m(job user user2)a do {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, "parent comment", user) @@ -90,7 +90,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentReplies do assert replyed_comment_3.reply_to_id == replyed_comment_2.id end - @tag :wip3 + @tag :wip2 test "reply to reply inside a comment should have is_reply_to_others flag in meta", ~m(job user user2)a do {:ok, parent_comment} = CMS.create_article_comment(:job, job.id, "parent comment", user) @@ -110,7 +110,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobCommentReplies do assert replyed_comment_3.meta.is_reply_to_others end - @tag :wip3 + @tag :wip2 test "comment replies only contains @max_parent_replies_count replies", ~m(job user)a do total_reply_count = @max_parent_replies_count + 1 diff --git a/test/groupher_server/cms/comments/job_comment_test.exs b/test/groupher_server/cms/comments/job_comment_test.exs index 01a44dae0..222b321e0 100644 --- a/test/groupher_server/cms/comments/job_comment_test.exs +++ b/test/groupher_server/cms/comments/job_comment_test.exs @@ -39,7 +39,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobComment do assert comment.meta |> Map.from_struct() |> Map.delete(:id) == @default_comment_meta end - @tag :wip3 + @tag :wip2 test "comment can be updated", ~m(job user)a do {:ok, comment} = CMS.create_article_comment(:job, job.id, "job comment", user) @@ -130,7 +130,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobComment do assert comment.meta.is_article_author_upvoted end - @tag :wip3 + @tag :wip2 test "user upvote job comment will add id to upvoted_user_ids", ~m(job user)a do comment = "job_comment" {:ok, comment} = CMS.create_article_comment(:job, job.id, comment, user) @@ -139,7 +139,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobComment do assert user.id in comment.meta.upvoted_user_ids end - @tag :wip3 + @tag :wip2 test "user undo upvote job comment will remove id from upvoted_user_ids", ~m(job user user2)a do comment = "job_comment" @@ -543,7 +543,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobComment do end describe "[article comment delete]" do - @tag :wip3 + @tag :wip2 test "delete comment still exsit in paged list and content is gone", ~m(user job)a do total_count = 10 page_number = 1 @@ -568,7 +568,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobComment do assert deleted_comment.body_html == @delete_hint end - @tag :wip3 + @tag :wip2 test "delete comment still update article's comments_count field", ~m(user job)a do {:ok, _comment} = CMS.create_article_comment(:job, job.id, "commment", user) {:ok, _comment} = CMS.create_article_comment(:job, job.id, "commment", user) @@ -586,7 +586,7 @@ defmodule GroupherServer.Test.CMS.Comments.JobComment do assert job.article_comments_count == 4 end - @tag :wip3 + @tag :wip2 test "delete comment still delete pined record if needed", ~m(user job)a do total_count = 10 diff --git a/test/groupher_server/cms/comments/post_comment_emotions_test.exs b/test/groupher_server/cms/comments/post_comment_emotions_test.exs index 0e55ecfd3..02665c31c 100644 --- a/test/groupher_server/cms/comments/post_comment_emotions_test.exs +++ b/test/groupher_server/cms/comments/post_comment_emotions_test.exs @@ -21,7 +21,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentEmotions do end describe "[emotion in paged article comment]" do - @tag :wip3 + @tag :wip2 test "login user should got viewer has emotioned status", ~m(post user)a do total_count = 0 page_number = 10 @@ -76,7 +76,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentEmotions do assert @default_emotions == emotions end - @tag :wip3 + @tag :wip2 test "can make emotion to comment", ~m(post user user2)a do parent_content = "parent comment" {:ok, parent_comment} = CMS.create_article_comment(:post, post.id, parent_content, user) @@ -91,7 +91,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentEmotions do assert user_exist_in?(user2, emotions.latest_downvote_users) end - @tag :wip3 + @tag :wip2 test "can undo emotion to comment", ~m(post user user2)a do parent_content = "parent comment" {:ok, parent_comment} = CMS.create_article_comment(:post, post.id, parent_content, user) @@ -114,7 +114,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentEmotions do assert not user_exist_in?(user2, emotions.latest_downvote_users) end - @tag :wip3 + @tag :wip2 test "same user make same emotion to same comment.", ~m(post user)a do parent_content = "parent comment" {:ok, parent_comment} = CMS.create_article_comment(:post, post.id, parent_content, user) @@ -128,7 +128,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentEmotions do assert user_exist_in?(user, parent_comment.emotions.latest_downvote_users) end - @tag :wip3 + @tag :wip2 test "same user same emotion to same comment only have one user_emotion record", ~m(post user)a do parent_content = "parent comment" @@ -152,7 +152,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentEmotions do assert record.heart end - @tag :wip3 + @tag :wip2 test "different user can make same emotions on same comment", ~m(post user user2 user3)a do {:ok, parent_comment} = CMS.create_article_comment(:post, post.id, "parent comment", user) diff --git a/test/groupher_server/cms/comments/post_comment_replies_test.exs b/test/groupher_server/cms/comments/post_comment_replies_test.exs index ab96e2ddc..93335859e 100644 --- a/test/groupher_server/cms/comments/post_comment_replies_test.exs +++ b/test/groupher_server/cms/comments/post_comment_replies_test.exs @@ -33,7 +33,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentReplies do assert exist_in?(replyed_comment, parent_comment.replies) end - @tag :wip3 + @tag :wip2 test "deleted comment can not be reply", ~m(post user user2)a do parent_content = "parent comment" reply_content = "reply comment" @@ -64,7 +64,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentReplies do assert exist_in?(replyed_comment_2, parent_comment.replies) end - @tag :wip3 + @tag :wip2 test "reply to reply inside a comment should belong same parent comment", ~m(post user user2)a do {:ok, parent_comment} = CMS.create_article_comment(:post, post.id, "parent comment", user) @@ -90,7 +90,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentReplies do assert replyed_comment_3.reply_to_id == replyed_comment_2.id end - @tag :wip3 + @tag :wip2 test "reply to reply inside a comment should have is_reply_to_others flag in meta", ~m(post user user2)a do {:ok, parent_comment} = CMS.create_article_comment(:post, post.id, "parent comment", user) @@ -110,7 +110,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostCommentReplies do assert replyed_comment_3.meta.is_reply_to_others end - @tag :wip3 + @tag :wip2 test "comment replies only contains @max_parent_replies_count replies", ~m(post user)a do total_reply_count = @max_parent_replies_count + 1 diff --git a/test/groupher_server/cms/comments/post_comment_test.exs b/test/groupher_server/cms/comments/post_comment_test.exs index a635353da..361fde20d 100644 --- a/test/groupher_server/cms/comments/post_comment_test.exs +++ b/test/groupher_server/cms/comments/post_comment_test.exs @@ -39,7 +39,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostComment do assert comment.meta |> Map.from_struct() |> Map.delete(:id) == @default_comment_meta end - @tag :wip3 + @tag :wip2 test "comment can be updated", ~m(post user)a do {:ok, comment} = CMS.create_article_comment(:post, post.id, "post comment", user) @@ -130,7 +130,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostComment do assert comment.meta.is_article_author_upvoted end - @tag :wip3 + @tag :wip2 test "user upvote post comment will add id to upvoted_user_ids", ~m(post user)a do comment = "post_comment" {:ok, comment} = CMS.create_article_comment(:post, post.id, comment, user) @@ -139,7 +139,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostComment do assert user.id in comment.meta.upvoted_user_ids end - @tag :wip3 + @tag :wip2 test "user undo upvote post comment will remove id from upvoted_user_ids", ~m(post user user2)a do comment = "post_comment" @@ -543,7 +543,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostComment do end describe "[article comment delete]" do - @tag :wip3 + @tag :wip2 test "delete comment still exsit in paged list and content is gone", ~m(user post)a do total_count = 10 page_number = 1 @@ -568,7 +568,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostComment do assert deleted_comment.body_html == @delete_hint end - @tag :wip3 + @tag :wip2 test "delete comment still update article's comments_count field", ~m(user post)a do {:ok, _comment} = CMS.create_article_comment(:post, post.id, "commment", user) {:ok, _comment} = CMS.create_article_comment(:post, post.id, "commment", user) @@ -586,7 +586,7 @@ defmodule GroupherServer.Test.CMS.Comments.PostComment do assert post.article_comments_count == 4 end - @tag :wip3 + @tag :wip2 test "delete comment still delete pined record if needed", ~m(user post)a do total_count = 10 diff --git a/test/groupher_server/cms/job_test.exs b/test/groupher_server/cms/job_test.exs index 6d924a25f..297468cd6 100644 --- a/test/groupher_server/cms/job_test.exs +++ b/test/groupher_server/cms/job_test.exs @@ -25,7 +25,7 @@ defmodule GroupherServer.Test.Job do assert found.title == job.title end - @tag :wip3 + @tag :wip2 test "read job should update views and meta viewed_user_list", ~m(job_attrs community user user2)a do {:ok, job} = CMS.create_content(community, :job, job_attrs, user) diff --git a/test/groupher_server/cms/post_test.exs b/test/groupher_server/cms/post_test.exs index 4f4743e80..e333d460d 100644 --- a/test/groupher_server/cms/post_test.exs +++ b/test/groupher_server/cms/post_test.exs @@ -26,7 +26,7 @@ defmodule GroupherServer.Test.CMS.Post do assert post.title == post_attrs.title end - @tag :wip3 + @tag :wip2 test "read post should update views and meta viewed_user_list", ~m(post_attrs community user user2)a do {:ok, post} = CMS.create_content(community, :post, post_attrs, user) diff --git a/test/groupher_server/cms/repo_test.exs b/test/groupher_server/cms/repo_test.exs index f818ecea0..e54709755 100644 --- a/test/groupher_server/cms/repo_test.exs +++ b/test/groupher_server/cms/repo_test.exs @@ -27,7 +27,7 @@ defmodule GroupherServer.Test.Repo do assert repo.contributors |> length !== 0 end - @tag :wip3 + @tag :wip2 test "read repo should update views and meta viewed_user_list", ~m(repo_attrs community user user2)a do {:ok, repo} = CMS.create_content(community, :repo, repo_attrs, user) diff --git a/test/groupher_server/cms/upvotes/job_upvote_test.exs b/test/groupher_server/cms/upvotes/job_upvote_test.exs new file mode 100644 index 000000000..daf36dd6c --- /dev/null +++ b/test/groupher_server/cms/upvotes/job_upvote_test.exs @@ -0,0 +1,88 @@ +defmodule GroupherServer.Test.Upvotes.JobUpvote do + @moduledoc false + use GroupherServer.TestTools + + alias GroupherServer.CMS + + setup do + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + {:ok, community} = db_insert(:community) + + job_attrs = mock_attrs(:job, %{community_id: community.id}) + + {:ok, ~m(user user2 community job_attrs)a} + end + + describe "[cms job upvote]" do + @tag :wip2 + test "job can be upvote && upvotes_count should inc by 1", + ~m(user user2 community job_attrs)a do + {:ok, job} = CMS.create_content(community, :job, job_attrs, user) + + {:ok, article} = CMS.upvote_article(:job, job.id, user) + assert article.id == job.id + assert article.upvotes_count == 1 + + {:ok, article} = CMS.upvote_article(:job, job.id, user2) + assert article.upvotes_count == 2 + end + + @tag :wip2 + test "job can be undo upvote && upvotes_count should dec by 1", + ~m(user user2 community job_attrs)a do + {:ok, job} = CMS.create_content(community, :job, job_attrs, user) + + {:ok, article} = CMS.upvote_article(:job, job.id, user) + assert article.id == job.id + assert article.upvotes_count == 1 + + {:ok, article} = CMS.undo_upvote_article(:job, job.id, user2) + assert article.upvotes_count == 0 + end + + @tag :wip2 + test "can get upvotes_users", ~m(user user2 community job_attrs)a do + {:ok, job} = CMS.create_content(community, :job, job_attrs, user) + + {:ok, _article} = CMS.upvote_article(:job, job.id, user) + {:ok, _article} = CMS.upvote_article(:job, job.id, user2) + + {:ok, users} = CMS.upvoted_users(:job, job.id, %{page: 1, size: 2}) + + assert users |> is_valid_pagination?(:raw) + assert user_exist_in?(user, users.entries) + assert user_exist_in?(user2, users.entries) + end + + @tag :wip2 + test "job meta history should be updated after upvote", + ~m(user user2 community job_attrs)a do + {:ok, job} = CMS.create_content(community, :job, job_attrs, user) + {:ok, article} = CMS.upvote_article(:job, job.id, user) + assert user.id in article.meta.upvoted_user_ids + + {:ok, article} = CMS.upvote_article(:job, job.id, user2) + assert user.id in article.meta.upvoted_user_ids + assert user2.id in article.meta.upvoted_user_ids + end + + @tag :wip2 + test "job meta history should be updated after undo upvote", + ~m(user user2 community job_attrs)a do + {:ok, job} = CMS.create_content(community, :job, job_attrs, user) + + {:ok, _article} = CMS.upvote_article(:job, job.id, user) + {:ok, article} = CMS.upvote_article(:job, job.id, user2) + + assert user.id in article.meta.upvoted_user_ids + assert user2.id in article.meta.upvoted_user_ids + + {:ok, article} = CMS.undo_upvote_article(:job, job.id, user2) + assert user2.id not in article.meta.upvoted_user_ids + + {:ok, article} = CMS.undo_upvote_article(:job, job.id, user) + assert user.id not in article.meta.upvoted_user_ids + end + end +end diff --git a/test/groupher_server/cms/article_upvote_test.exs b/test/groupher_server/cms/upvotes/post_upvote_test.exs similarity index 50% rename from test/groupher_server/cms/article_upvote_test.exs rename to test/groupher_server/cms/upvotes/post_upvote_test.exs index 87afa8370..fda68d1d1 100644 --- a/test/groupher_server/cms/article_upvote_test.exs +++ b/test/groupher_server/cms/upvotes/post_upvote_test.exs @@ -1,4 +1,4 @@ -defmodule GroupherServer.Test.ArticleUpvote do +defmodule GroupherServer.Test.Upvotes.PostUpvote do @moduledoc false use GroupherServer.TestTools @@ -10,13 +10,12 @@ defmodule GroupherServer.Test.ArticleUpvote do {:ok, community} = db_insert(:community) post_attrs = mock_attrs(:post, %{community_id: community.id}) - job_attrs = mock_attrs(:job, %{community_id: community.id}) - {:ok, ~m(user user2 community post_attrs job_attrs)a} + {:ok, ~m(user user2 community post_attrs)a} end describe "[cms post upvote]" do - @tag :wip + @tag :wip2 test "post can be upvote && upvotes_count should inc by 1", ~m(user user2 community post_attrs)a do {:ok, post} = CMS.create_content(community, :post, post_attrs, user) @@ -29,7 +28,7 @@ defmodule GroupherServer.Test.ArticleUpvote do assert article.upvotes_count == 2 end - @tag :wip + @tag :wip2 test "post can be undo upvote && upvotes_count should dec by 1", ~m(user user2 community post_attrs)a do {:ok, post} = CMS.create_content(community, :post, post_attrs, user) @@ -42,7 +41,7 @@ defmodule GroupherServer.Test.ArticleUpvote do assert article.upvotes_count == 0 end - @tag :wip + @tag :wip2 test "can get upvotes_users", ~m(user user2 community post_attrs)a do {:ok, post} = CMS.create_content(community, :post, post_attrs, user) @@ -56,6 +55,7 @@ defmodule GroupherServer.Test.ArticleUpvote do assert user_exist_in?(user2, users.entries) end + @tag :wip2 test "post meta history should be updated after upvote", ~m(user user2 community post_attrs)a do {:ok, post} = CMS.create_content(community, :post, post_attrs, user) @@ -67,6 +67,7 @@ defmodule GroupherServer.Test.ArticleUpvote do assert user2.id in article.meta.upvoted_user_ids end + @tag :wip2 test "post meta history should be updated after undo upvote", ~m(user user2 community post_attrs)a do {:ok, post} = CMS.create_content(community, :post, post_attrs, user) @@ -84,73 +85,4 @@ defmodule GroupherServer.Test.ArticleUpvote do assert user.id not in article.meta.upvoted_user_ids end end - - describe "[cms job upvote]" do - @tag :wip - test "job can be upvote && upvotes_count should inc by 1", - ~m(user user2 community job_attrs)a do - {:ok, job} = CMS.create_content(community, :job, job_attrs, user) - - {:ok, article} = CMS.upvote_article(:job, job.id, user) - assert article.id == job.id - assert article.upvotes_count == 1 - - {:ok, article} = CMS.upvote_article(:job, job.id, user2) - assert article.upvotes_count == 2 - end - - @tag :wip - test "job can be undo upvote && upvotes_count should dec by 1", - ~m(user user2 community job_attrs)a do - {:ok, job} = CMS.create_content(community, :job, job_attrs, user) - - {:ok, article} = CMS.upvote_article(:job, job.id, user) - assert article.id == job.id - assert article.upvotes_count == 1 - - {:ok, article} = CMS.undo_upvote_article(:job, job.id, user2) - assert article.upvotes_count == 0 - end - - @tag :wip - test "can get upvotes_users", ~m(user user2 community job_attrs)a do - {:ok, job} = CMS.create_content(community, :job, job_attrs, user) - - {:ok, _article} = CMS.upvote_article(:job, job.id, user) - {:ok, _article} = CMS.upvote_article(:job, job.id, user2) - - {:ok, users} = CMS.upvoted_users(:job, job.id, %{page: 1, size: 2}) - - assert users |> is_valid_pagination?(:raw) - assert user_exist_in?(user, users.entries) - assert user_exist_in?(user2, users.entries) - end - - test "job meta history should be updated", ~m(user user2 community job_attrs)a do - {:ok, job} = CMS.create_content(community, :job, job_attrs, user) - {:ok, article} = CMS.upvote_article(:job, job.id, user) - assert user.id in article.meta.upvoted_user_ids - - {:ok, article} = CMS.upvote_article(:job, job.id, user2) - assert user.id in article.meta.upvoted_user_ids - assert user2.id in article.meta.upvoted_user_ids - end - - test "job meta history should be updated after undo upvote", - ~m(user user2 community job_attrs)a do - {:ok, job} = CMS.create_content(community, :job, job_attrs, user) - - {:ok, _article} = CMS.upvote_article(:job, job.id, user) - {:ok, article} = CMS.upvote_article(:job, job.id, user2) - - assert user.id in article.meta.upvoted_user_ids - assert user2.id in article.meta.upvoted_user_ids - - {:ok, article} = CMS.undo_upvote_article(:job, job.id, user2) - assert user2.id not in article.meta.upvoted_user_ids - - {:ok, article} = CMS.undo_upvote_article(:job, job.id, user) - assert user.id not in article.meta.upvoted_user_ids - end - end end diff --git a/test/groupher_server_web/mutation/accounts/customization_test.exs b/test/groupher_server_web/mutation/accounts/customization_test.exs index 8f42ad231..de3de03cd 100644 --- a/test/groupher_server_web/mutation/accounts/customization_test.exs +++ b/test/groupher_server_web/mutation/accounts/customization_test.exs @@ -72,7 +72,7 @@ defmodule GroupherServer.Test.Mutation.Account.Customization do } } """ - @tag :wip3 + @tag :wip2 test "PageSizeProof middleware should lint c11n displayDensity size", ~m(user)a do user_conn = simu_conn(:user, user) db_insert_multi(:post, 50) diff --git a/test/groupher_server_web/mutation/cms/article_upvote_test.exs b/test/groupher_server_web/mutation/cms/article_upvote_test.exs deleted file mode 100644 index c971cbc46..000000000 --- a/test/groupher_server_web/mutation/cms/article_upvote_test.exs +++ /dev/null @@ -1,117 +0,0 @@ -defmodule GroupherServer.Test.Mutation.ArticleUpvote do - @moduledoc false - use GroupherServer.TestTools - - alias GroupherServer.CMS - - setup do - {:ok, post} = db_insert(:post) - {:ok, job} = db_insert(:job) - {:ok, user} = db_insert(:user) - - guest_conn = simu_conn(:guest) - user_conn = simu_conn(:user, user) - - {:ok, ~m(user_conn guest_conn post job user)a} - end - - describe "[post upvote]" do - @query """ - mutation($id: ID!, $thread: CmsThread!) { - upvoteArticle(id: $id, thread: $thread) { - id - } - } - """ - @tag :wip - test "login user can upvote a post", ~m(user_conn post)a do - variables = %{id: post.id, thread: "POST"} - created = user_conn |> mutation_result(@query, variables, "upvoteArticle") - - assert created["id"] == to_string(post.id) - end - - @tag :wip - test "unauth user upvote a post fails", ~m(guest_conn post)a do - variables = %{id: post.id, thread: "POST"} - - assert guest_conn - |> mutation_get_error?(@query, variables, ecode(:account_login)) - end - - @query """ - mutation($id: ID!, $thread: CmsThread!) { - undoUpvoteArticle(id: $id, thread: $thread) { - id - } - } - """ - @tag :wip - test "login user can undo upvote to a post", ~m(user_conn post user)a do - {:ok, _} = CMS.upvote_article(:post, post.id, user) - - variables = %{id: post.id, thread: "POST"} - updated = user_conn |> mutation_result(@query, variables, "undoUpvoteArticle") - - assert updated["id"] == to_string(post.id) - end - - @tag :wip - test "unauth user undo upvote a post fails", ~m(guest_conn post)a do - variables = %{id: post.id, thread: "POST"} - - assert guest_conn - |> mutation_get_error?(@query, variables, ecode(:account_login)) - end - end - - describe "[job upvote]" do - @query """ - mutation($id: ID!, $thread: CmsThread!) { - upvoteArticle(id: $id, thread: $thread) { - id - } - } - """ - @tag :wip - test "login user can upvote a job", ~m(user_conn job)a do - variables = %{id: job.id, thread: "JOB"} - created = user_conn |> mutation_result(@query, variables, "upvoteArticle") - - assert created["id"] == to_string(job.id) - end - - @tag :wip - test "unauth user upvote a job fails", ~m(guest_conn job)a do - variables = %{id: job.id, thread: "JOB"} - - assert guest_conn - |> mutation_get_error?(@query, variables, ecode(:account_login)) - end - - @query """ - mutation($id: ID!, $thread: CmsThread!) { - undoUpvoteArticle(id: $id, thread: $thread) { - id - } - } - """ - @tag :wip - test "login user can undo upvote to a job", ~m(user_conn job user)a do - {:ok, _} = CMS.upvote_article(:job, job.id, user) - - variables = %{id: job.id, thread: "JOB"} - updated = user_conn |> mutation_result(@query, variables, "undoUpvoteArticle") - - assert updated["id"] == to_string(job.id) - end - - @tag :wip - test "unauth user undo upvote a job fails", ~m(guest_conn job)a do - variables = %{id: job.id, thread: "JOB"} - - assert guest_conn - |> mutation_get_error?(@query, variables, ecode(:account_login)) - end - end -end diff --git a/test/groupher_server_web/mutation/cms/comments/job_comment_test.exs b/test/groupher_server_web/mutation/cms/comments/job_comment_test.exs index 787b5cfea..170f4bd0a 100644 --- a/test/groupher_server_web/mutation/cms/comments/job_comment_test.exs +++ b/test/groupher_server_web/mutation/cms/comments/job_comment_test.exs @@ -24,7 +24,7 @@ defmodule GroupherServer.Test.Mutation.Comments.JobComment do } } """ - @tag :wip3 + @tag :wip2 test "write article comment to a exsit job", ~m(job user_conn)a do comment = "a test comment" variables = %{thread: "JOB", id: job.id, content: comment} @@ -63,7 +63,7 @@ defmodule GroupherServer.Test.Mutation.Comments.JobComment do } } """ - @tag :wip3 + @tag :wip2 test "only owner can update a exsit comment", ~m(job user guest_conn user_conn owner_conn)a do {:ok, comment} = CMS.create_article_comment(:job, job.id, "job comment", user) @@ -88,7 +88,7 @@ defmodule GroupherServer.Test.Mutation.Comments.JobComment do } } """ - @tag :wip3 + @tag :wip2 test "only owner can delete a exsit comment", ~m(job user guest_conn user_conn owner_conn)a do {:ok, comment} = CMS.create_article_comment(:job, job.id, "job comment", user) @@ -123,7 +123,7 @@ defmodule GroupherServer.Test.Mutation.Comments.JobComment do } } """ - @tag :wip3 + @tag :wip2 test "login user can emotion to a comment", ~m(job user user_conn)a do {:ok, comment} = CMS.create_article_comment(:job, job.id, "job comment", user) variables = %{id: comment.id, emotion: "BEER"} @@ -150,7 +150,7 @@ defmodule GroupherServer.Test.Mutation.Comments.JobComment do } } """ - @tag :wip3 + @tag :wip2 test "login user can undo emotion to a comment", ~m(job user owner_conn)a do {:ok, comment} = CMS.create_article_comment(:job, job.id, "job comment", user) {:ok, _} = CMS.emotion_to_comment(comment.id, :beer, user) diff --git a/test/groupher_server_web/mutation/cms/comments/post_comment_test.exs b/test/groupher_server_web/mutation/cms/comments/post_comment_test.exs index c28013669..7ead35f15 100644 --- a/test/groupher_server_web/mutation/cms/comments/post_comment_test.exs +++ b/test/groupher_server_web/mutation/cms/comments/post_comment_test.exs @@ -24,7 +24,7 @@ defmodule GroupherServer.Test.Mutation.Comments.PostComment do } } """ - @tag :wip3 + @tag :wip2 test "write article comment to a exsit post", ~m(post user_conn)a do comment = "a test comment" variables = %{thread: "POST", id: post.id, content: comment} @@ -63,7 +63,7 @@ defmodule GroupherServer.Test.Mutation.Comments.PostComment do } } """ - @tag :wip3 + @tag :wip2 test "only owner can update a exsit comment", ~m(post user guest_conn user_conn owner_conn)a do {:ok, comment} = CMS.create_article_comment(:post, post.id, "post comment", user) @@ -88,7 +88,7 @@ defmodule GroupherServer.Test.Mutation.Comments.PostComment do } } """ - @tag :wip3 + @tag :wip2 test "only owner can delete a exsit comment", ~m(post user guest_conn user_conn owner_conn)a do {:ok, comment} = CMS.create_article_comment(:post, post.id, "post comment", user) @@ -123,7 +123,7 @@ defmodule GroupherServer.Test.Mutation.Comments.PostComment do } } """ - @tag :wip3 + @tag :wip2 test "login user can emotion to a comment", ~m(post user user_conn)a do {:ok, comment} = CMS.create_article_comment(:post, post.id, "post comment", user) variables = %{id: comment.id, emotion: "BEER"} @@ -150,7 +150,7 @@ defmodule GroupherServer.Test.Mutation.Comments.PostComment do } } """ - @tag :wip3 + @tag :wip2 test "login user can undo emotion to a comment", ~m(post user owner_conn)a do {:ok, comment} = CMS.create_article_comment(:post, post.id, "post comment", user) {:ok, _} = CMS.emotion_to_comment(comment.id, :beer, user) diff --git a/test/groupher_server_web/mutation/cms/upvotes/job_upvote_test.exs b/test/groupher_server_web/mutation/cms/upvotes/job_upvote_test.exs new file mode 100644 index 000000000..ff32d1cc7 --- /dev/null +++ b/test/groupher_server_web/mutation/cms/upvotes/job_upvote_test.exs @@ -0,0 +1,66 @@ +defmodule GroupherServer.Test.Mutation.Upvotes.JobUpvote do + @moduledoc false + use GroupherServer.TestTools + + alias GroupherServer.CMS + + setup do + {:ok, job} = db_insert(:job) + {:ok, user} = db_insert(:user) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user, user) + + {:ok, ~m(user_conn guest_conn job user)a} + end + + describe "[job upvote]" do + @query """ + mutation($id: ID!) { + upvoteJob(id: $id) { + id + } + } + """ + @tag :wip2 + test "login user can upvote a job", ~m(user_conn job)a do + variables = %{id: job.id} + created = user_conn |> mutation_result(@query, variables, "upvoteJob") + + assert created["id"] == to_string(job.id) + end + + @tag :wip2 + test "unauth user upvote a job fails", ~m(guest_conn job)a do + variables = %{id: job.id} + + assert guest_conn + |> mutation_get_error?(@query, variables, ecode(:account_login)) + end + + @query """ + mutation($id: ID!) { + undoUpvoteJob(id: $id) { + id + } + } + """ + @tag :wip2 + test "login user can undo upvote to a job", ~m(user_conn job user)a do + {:ok, _} = CMS.upvote_article(:job, job.id, user) + + variables = %{id: job.id} + updated = user_conn |> mutation_result(@query, variables, "undoUpvoteJob") + + assert updated["id"] == to_string(job.id) + end + + @tag :wip2 + test "unauth user undo upvote a job fails", ~m(guest_conn job)a do + variables = %{id: job.id} + + assert guest_conn + |> mutation_get_error?(@query, variables, ecode(:account_login)) + end + end +end diff --git a/test/groupher_server_web/mutation/cms/upvotes/post_upvote_test.exs b/test/groupher_server_web/mutation/cms/upvotes/post_upvote_test.exs new file mode 100644 index 000000000..002ff0e2f --- /dev/null +++ b/test/groupher_server_web/mutation/cms/upvotes/post_upvote_test.exs @@ -0,0 +1,64 @@ +defmodule GroupherServer.Test.Mutation.Upvotes.PostUpvote do + @moduledoc false + use GroupherServer.TestTools + + alias GroupherServer.CMS + + setup do + {:ok, post} = db_insert(:post) + {:ok, user} = db_insert(:user) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user, user) + + {:ok, ~m(user_conn guest_conn post user)a} + end + + describe "[post upvote]" do + @query """ + mutation($id: ID!) { + upvotePost(id: $id) { + id + } + } + """ + @tag :wip2 + test "login user can upvote a post", ~m(user_conn post)a do + variables = %{id: post.id} + created = user_conn |> mutation_result(@query, variables, "upvotePost") + + assert created["id"] == to_string(post.id) + end + + @tag :wip2 + test "unauth user upvote a post fails", ~m(guest_conn post)a do + variables = %{id: post.id} + + assert guest_conn |> mutation_get_error?(@query, variables, ecode(:account_login)) + end + + @query """ + mutation($id: ID!) { + undoUpvotePost(id: $id) { + id + } + } + """ + @tag :wip2 + test "login user can undo upvote to a post", ~m(user_conn post user)a do + {:ok, _} = CMS.upvote_article(:post, post.id, user) + + variables = %{id: post.id} + updated = user_conn |> mutation_result(@query, variables, "undoUpvotePost") + + assert updated["id"] == to_string(post.id) + end + + @tag :wip2 + test "unauth user undo upvote a post fails", ~m(guest_conn post)a do + variables = %{id: post.id} + + assert guest_conn |> mutation_get_error?(@query, variables, ecode(:account_login)) + end + end +end diff --git a/test/groupher_server_web/query/cms/article_reaction_users_test.exs b/test/groupher_server_web/query/cms/article_reaction_users_test.exs deleted file mode 100644 index 2f5edbefb..000000000 --- a/test/groupher_server_web/query/cms/article_reaction_users_test.exs +++ /dev/null @@ -1,124 +0,0 @@ -defmodule GroupherServer.Test.Query.ArticleReactionUsers do - @moduledoc false - use GroupherServer.TestTools - - alias GroupherServer.CMS - - setup do - {:ok, post} = db_insert(:post) - {:ok, job} = db_insert(:job) - {:ok, user} = db_insert(:user) - {:ok, user2} = db_insert(:user) - - guest_conn = simu_conn(:guest) - user_conn = simu_conn(:user, user) - - {:ok, ~m(user_conn guest_conn user user2 post job)a} - end - - describe "[upvoted users]" do - @query """ - query( - $id: ID! - $thread: CmsThread - $filter: PagedFilter! - ) { - upvotedUsers(id: $id, thread: $thread, filter: $filter) { - entries { - id - avatar - nickname - } - totalPages - totalCount - pageSize - pageNumber - } - } - """ - @tag :wip3 - test "guest can get upvoted users list after upvote to a post", - ~m(guest_conn post user user2)a do - {:ok, _} = CMS.upvote_article(:post, post.id, user) - {:ok, _} = CMS.upvote_article(:post, post.id, user2) - - variables = %{id: post.id, thread: "POST", filter: %{page: 1, size: 20}} - results = guest_conn |> query_result(@query, variables, "upvotedUsers") - - assert results |> is_valid_pagination? - assert results["totalCount"] == 2 - - assert user_exist_in?(user, results["entries"], :string_key) - assert user_exist_in?(user2, results["entries"], :string_key) - end - - @tag :wip3 - test "guest can get upvoted users list after upvote to a job", - ~m(guest_conn job user user2)a do - {:ok, _} = CMS.upvote_article(:job, job.id, user) - {:ok, _} = CMS.upvote_article(:job, job.id, user2) - - variables = %{id: job.id, thread: "JOB", filter: %{page: 1, size: 20}} - results = guest_conn |> query_result(@query, variables, "upvotedUsers") - - assert results |> is_valid_pagination? - assert results["totalCount"] == 2 - - assert user_exist_in?(user, results["entries"], :string_key) - assert user_exist_in?(user2, results["entries"], :string_key) - end - end - - describe "[collect users]" do - @query """ - query( - $id: ID! - $thread: CmsThread - $filter: PagedFilter! - ) { - collectedUsers(id: $id, thread: $thread, filter: $filter) { - entries { - id - avatar - nickname - } - totalPages - totalCount - pageSize - pageNumber - } - } - """ - @tag :wip3 - test "guest can get collected users list after collect a post", - ~m(guest_conn post user user2)a do - {:ok, _} = CMS.collect_article(:post, post.id, user) - {:ok, _} = CMS.collect_article(:post, post.id, user2) - - variables = %{id: post.id, thread: "POST", filter: %{page: 1, size: 20}} - results = guest_conn |> query_result(@query, variables, "collectedUsers") - - assert results |> is_valid_pagination? - assert results["totalCount"] == 2 - - assert user_exist_in?(user, results["entries"], :string_key) - assert user_exist_in?(user2, results["entries"], :string_key) - end - - @tag :wip3 - test "guest can get collected users list after collect a job", - ~m(guest_conn job user user2)a do - {:ok, _} = CMS.collect_article(:job, job.id, user) - {:ok, _} = CMS.collect_article(:job, job.id, user2) - - variables = %{id: job.id, thread: "JOB", filter: %{page: 1, size: 20}} - results = guest_conn |> query_result(@query, variables, "collectedUsers") - - assert results |> is_valid_pagination? - assert results["totalCount"] == 2 - - assert user_exist_in?(user, results["entries"], :string_key) - assert user_exist_in?(user2, results["entries"], :string_key) - end - end -end diff --git a/test/groupher_server_web/query/cms/collects/job_collect_test.exs b/test/groupher_server_web/query/cms/collects/job_collect_test.exs new file mode 100644 index 000000000..c2978cb51 --- /dev/null +++ b/test/groupher_server_web/query/cms/collects/job_collect_test.exs @@ -0,0 +1,54 @@ +defmodule GroupherServer.Test.Query.Collects.JobCollect do + @moduledoc false + use GroupherServer.TestTools + + alias GroupherServer.CMS + + setup do + {:ok, job} = db_insert(:job) + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user, user) + + {:ok, ~m(user_conn guest_conn user user2 job)a} + end + + describe "[collect users]" do + @query """ + query( + $id: ID! + $thread: CmsThread + $filter: PagedFilter! + ) { + collectedUsers(id: $id, thread: $thread, filter: $filter) { + entries { + id + avatar + nickname + } + totalPages + totalCount + pageSize + pageNumber + } + } + """ + @tag :wip2 + test "guest can get collected users list after collect a job", + ~m(guest_conn job user user2)a do + {:ok, _} = CMS.collect_article(:job, job.id, user) + {:ok, _} = CMS.collect_article(:job, job.id, user2) + + variables = %{id: job.id, thread: "JOB", filter: %{page: 1, size: 20}} + results = guest_conn |> query_result(@query, variables, "collectedUsers") + + assert results |> is_valid_pagination? + assert results["totalCount"] == 2 + + assert user_exist_in?(user, results["entries"], :string_key) + assert user_exist_in?(user2, results["entries"], :string_key) + end + end +end diff --git a/test/groupher_server_web/query/cms/collects/post_collect_test.exs b/test/groupher_server_web/query/cms/collects/post_collect_test.exs new file mode 100644 index 000000000..00a275965 --- /dev/null +++ b/test/groupher_server_web/query/cms/collects/post_collect_test.exs @@ -0,0 +1,54 @@ +defmodule GroupherServer.Test.Query.Collects.PostCollect do + @moduledoc false + use GroupherServer.TestTools + + alias GroupherServer.CMS + + setup do + {:ok, post} = db_insert(:post) + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user, user) + + {:ok, ~m(user_conn guest_conn user user2 post)a} + end + + describe "[collect users]" do + @query """ + query( + $id: ID! + $thread: CmsThread + $filter: PagedFilter! + ) { + collectedUsers(id: $id, thread: $thread, filter: $filter) { + entries { + id + avatar + nickname + } + totalPages + totalCount + pageSize + pageNumber + } + } + """ + @tag :wip2 + test "guest can get collected users list after collect a post", + ~m(guest_conn post user user2)a do + {:ok, _} = CMS.collect_article(:post, post.id, user) + {:ok, _} = CMS.collect_article(:post, post.id, user2) + + variables = %{id: post.id, thread: "POST", filter: %{page: 1, size: 20}} + results = guest_conn |> query_result(@query, variables, "collectedUsers") + + assert results |> is_valid_pagination? + assert results["totalCount"] == 2 + + assert user_exist_in?(user, results["entries"], :string_key) + assert user_exist_in?(user2, results["entries"], :string_key) + end + end +end diff --git a/test/groupher_server_web/query/cms/comments/job_comment_test.exs b/test/groupher_server_web/query/cms/comments/job_comment_test.exs index f74f82b01..072ab0b6f 100644 --- a/test/groupher_server_web/query/cms/comments/job_comment_test.exs +++ b/test/groupher_server_web/query/cms/comments/job_comment_test.exs @@ -373,7 +373,7 @@ defmodule GroupherServer.Test.Query.Comments.JobComment do assert the_random_comment |> get_in(["meta", "isArticleAuthorUpvoted"]) end - @tag :wip3 + @tag :wip2 test "guest user can get paged comment with emotions info", ~m(guest_conn job user user2)a do total_count = 2 @@ -451,7 +451,7 @@ defmodule GroupherServer.Test.Query.Comments.JobComment do |> get_in(["emotions", "viewerHasDownvoteed"]) end - @tag :wip3 + @tag :wip2 test "comment should have viewer has upvoted flag", ~m(user_conn job user)a do total_count = 10 page_size = 12 diff --git a/test/groupher_server_web/query/cms/comments/post_comment_test.exs b/test/groupher_server_web/query/cms/comments/post_comment_test.exs index 8e1dcb613..b1234f806 100644 --- a/test/groupher_server_web/query/cms/comments/post_comment_test.exs +++ b/test/groupher_server_web/query/cms/comments/post_comment_test.exs @@ -373,7 +373,7 @@ defmodule GroupherServer.Test.Query.Comments.PostComment do assert the_random_comment |> get_in(["meta", "isArticleAuthorUpvoted"]) end - @tag :wip3 + @tag :wip2 test "guest user can get paged comment with emotions info", ~m(guest_conn post user user2)a do total_count = 2 @@ -451,7 +451,7 @@ defmodule GroupherServer.Test.Query.Comments.PostComment do |> get_in(["emotions", "viewerHasDownvoteed"]) end - @tag :wip3 + @tag :wip2 test "comment should have viewer has upvoted flag", ~m(user_conn post user)a do total_count = 10 page_size = 12 diff --git a/test/groupher_server_web/query/cms/job_test.exs b/test/groupher_server_web/query/cms/job_test.exs index 417d8e867..eebf90599 100644 --- a/test/groupher_server_web/query/cms/job_test.exs +++ b/test/groupher_server_web/query/cms/job_test.exs @@ -19,7 +19,7 @@ defmodule GroupherServer.Test.Query.Job do } } """ - @tag :wip3 + @tag :wip2 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") @@ -30,7 +30,7 @@ defmodule GroupherServer.Test.Query.Job do assert length(Map.keys(results)) == 3 end - @tag :wip3 + @tag :wip2 test "basic graphql query on job with stranger(unloged user)", ~m(guest_conn job)a do variables = %{id: job.id} results = guest_conn |> query_result(@query, variables, "job") diff --git a/test/groupher_server_web/query/cms/paged_jobs_test.exs b/test/groupher_server_web/query/cms/paged_jobs_test.exs index b1254dde5..c68f415bd 100644 --- a/test/groupher_server_web/query/cms/paged_jobs_test.exs +++ b/test/groupher_server_web/query/cms/paged_jobs_test.exs @@ -97,7 +97,7 @@ defmodule GroupherServer.Test.Query.PagedJobs do } } """ - @tag :wip3 + @tag :wip2 test "has_xxx state should work", ~m(user)a do user_conn = simu_conn(:user, user) {:ok, community} = db_insert(:community) diff --git a/test/groupher_server_web/query/cms/paged_posts_test.exs b/test/groupher_server_web/query/cms/paged_posts_test.exs index bde4c3b74..b09740e57 100644 --- a/test/groupher_server_web/query/cms/paged_posts_test.exs +++ b/test/groupher_server_web/query/cms/paged_posts_test.exs @@ -170,7 +170,7 @@ defmodule GroupherServer.Test.Query.PagedPosts do } } """ - @tag :wip3 + @tag :wip2 test "has_xxx state should work", ~m(user)a do user_conn = simu_conn(:user, user) {:ok, community} = db_insert(:community) diff --git a/test/groupher_server_web/query/cms/paged_repos_test.exs b/test/groupher_server_web/query/cms/paged_repos_test.exs index e725b7a0f..c50aa0e6f 100644 --- a/test/groupher_server_web/query/cms/paged_repos_test.exs +++ b/test/groupher_server_web/query/cms/paged_repos_test.exs @@ -93,7 +93,7 @@ defmodule GroupherServer.Test.Query.PagedRepos do } } """ - @tag :wip3 + @tag :wip2 test "has_xxx state should work", ~m(user)a do user_conn = simu_conn(:user, user) {:ok, community} = db_insert(:community) diff --git a/test/groupher_server_web/query/cms/post_test.exs b/test/groupher_server_web/query/cms/post_test.exs index 3c53e9c6e..5e92fbb0e 100644 --- a/test/groupher_server_web/query/cms/post_test.exs +++ b/test/groupher_server_web/query/cms/post_test.exs @@ -28,7 +28,7 @@ defmodule GroupherServer.Test.Query.Post do } } """ - @tag :wip3 + @tag :wip2 test "basic graphql query on post with logined user", ~m(user_conn community user post_attrs)a do {:ok, post} = CMS.create_content(community, :post, post_attrs, user) @@ -43,7 +43,7 @@ defmodule GroupherServer.Test.Query.Post do assert length(Map.keys(results)) == 4 end - @tag :wip3 + @tag :wip2 test "basic graphql query on post with stranger(unloged user)", ~m(guest_conn post)a do variables = %{id: post.id} results = guest_conn |> query_result(@query, variables, "post") diff --git a/test/groupher_server_web/query/cms/posts_flags_test.exs b/test/groupher_server_web/query/cms/posts_flags_test.exs index a63fa5d41..0cd6f01ec 100644 --- a/test/groupher_server_web/query/cms/posts_flags_test.exs +++ b/test/groupher_server_web/query/cms/posts_flags_test.exs @@ -51,7 +51,7 @@ defmodule GroupherServer.Test.Query.PostsFlags do } } """ - @tag :wip3 + @tag :wip2 test "if have pined posts, the pined posts should at the top of entries", ~m(guest_conn community post_m)a do variables = %{filter: %{community: community.raw}} diff --git a/test/groupher_server_web/query/cms/repo_test.exs b/test/groupher_server_web/query/cms/repo_test.exs index a4c82f63a..dc982deb0 100644 --- a/test/groupher_server_web/query/cms/repo_test.exs +++ b/test/groupher_server_web/query/cms/repo_test.exs @@ -19,7 +19,7 @@ defmodule GroupherServer.Test.Query.Repo do } } """ - @tag :wip3 + @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") @@ -30,7 +30,7 @@ defmodule GroupherServer.Test.Query.Repo do assert length(Map.keys(results)) == 3 end - @tag :wip3 + @tag :wip2 test "basic graphql query on repo with stranger(unloged user)", ~m(guest_conn repo)a do variables = %{id: repo.id} results = guest_conn |> query_result(@query, variables, "repo") diff --git a/test/groupher_server_web/query/cms/upvotes/post_upvote_test.exs b/test/groupher_server_web/query/cms/upvotes/post_upvote_test.exs new file mode 100644 index 000000000..92754b89d --- /dev/null +++ b/test/groupher_server_web/query/cms/upvotes/post_upvote_test.exs @@ -0,0 +1,54 @@ +defmodule GroupherServer.Test.Query.Upvotes.PostUpvote do + @moduledoc false + use GroupherServer.TestTools + + alias GroupherServer.CMS + + setup do + {:ok, post} = db_insert(:post) + {:ok, user} = db_insert(:user) + {:ok, user2} = db_insert(:user) + + guest_conn = simu_conn(:guest) + user_conn = simu_conn(:user, user) + + {:ok, ~m(user_conn guest_conn user user2 post)a} + end + + describe "[upvoted users]" do + @query """ + query( + $id: ID! + $thread: CmsThread + $filter: PagedFilter! + ) { + upvotedUsers(id: $id, thread: $thread, filter: $filter) { + entries { + id + avatar + nickname + } + totalPages + totalCount + pageSize + pageNumber + } + } + """ + @tag :wip2 + test "guest can get upvoted users list after upvote to a post", + ~m(guest_conn post user user2)a do + {:ok, _} = CMS.upvote_article(:post, post.id, user) + {:ok, _} = CMS.upvote_article(:post, post.id, user2) + + variables = %{id: post.id, thread: "POST", filter: %{page: 1, size: 20}} + results = guest_conn |> query_result(@query, variables, "upvotedUsers") + + assert results |> is_valid_pagination? + assert results["totalCount"] == 2 + + assert user_exist_in?(user, results["entries"], :string_key) + assert user_exist_in?(user2, results["entries"], :string_key) + end + end +end