diff --git a/cover/excoveralls.json b/cover/excoveralls.json index b27cfda2d..abe45c27c 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.SpecType 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.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 diff --git a/lib/groupher_server/cms/delegates/community_sync.ex b/lib/groupher_server/cms/delegates/community_sync.ex index 9804fa4d5..f0cfec77e 100644 --- a/lib/groupher_server/cms/delegates/community_sync.ex +++ b/lib/groupher_server/cms/delegates/community_sync.ex @@ -9,7 +9,7 @@ defmodule GroupherServer.CMS.Delegate.CommunitySync do alias GroupherServer.CMS alias Helper.ORM - alias Helper.SpecType, as: T + alias Helper.Types, as: T alias CMS.{ Community, diff --git a/lib/helper/converter/editor_to_html/class.ex b/lib/helper/converter/editor_to_html/class.ex new file mode 100644 index 000000000..f6bcabd64 --- /dev/null +++ b/lib/helper/converter/editor_to_html/class.ex @@ -0,0 +1,48 @@ +defmodule Helper.Converter.EditorToHTML.Class do + @moduledoc """ + html article class names parsed from editor.js's json data + + currently use https://editorjs.io/ as rich-text editor + # NOTE: DONOT CHANGE ONCE SET, OTHERWISE IT WILL CAUSE INCOMPATIBILITY ISSUE + """ + + @doc """ + get all the class names of the parsed editor.js's html parts + """ + def article() do + %{ + # root wrapper + "viewer" => "article-viewer-wrapper", + "unknow_block" => "unknow-block", + "invalid_block" => "invalid-block", + # header + "header" => %{ + "wrapper" => "header-wrapper", + "eyebrow_title" => "eyebrow-title", + "footer_title" => "footer-title" + }, + # list + "list" => %{ + "wrapper" => "list-wrapper", + "label" => "list-label", + "label__default" => "list-label__default", + "label__red" => "list-label__red", + "label__green" => "list-label__green", + "label__warn" => "list-label__warn", + "unorder_list_prefix" => "list__item-unorder-prefix", + "order_list_prefix" => "list__item-order-prefix", + "list_item" => "list-item", + "checklist_item" => "list-checklist__item", + "checklist_checkbox" => "checklist__item-checkbox", + "checklist_checkbox_checked" => "checklist__item-check-sign-checked", + "checklist_checksign" => "checklist__item-check-sign", + "text" => "list-item-text", + "checklist_text" => "list-checklist__item-text", + "indent_0" => "", + "indent_1" => "list-indent-1", + "indent_2" => "list-indent-2", + "indent_3" => "list-indent-3" + } + } + end +end diff --git a/lib/helper/converter/editor_to_html/frags/header.ex b/lib/helper/converter/editor_to_html/frags/header.ex new file mode 100644 index 000000000..88b0d03a1 --- /dev/null +++ b/lib/helper/converter/editor_to_html/frags/header.ex @@ -0,0 +1,55 @@ +defmodule Helper.Converter.EditorToHTML.Frags.Header do + @moduledoc """ + parse editor.js's block fragments, use for test too + + see https://editorjs.io/ + """ + alias Helper.Types, as: T + + alias Helper.Converter.EditorToHTML.Class + + @class get_in(Class.article(), ["header"]) + + @spec get(T.editor_header()) :: T.html() + def get(%{"eyebrowTitle" => eyebrow_title, "footerTitle" => footer_title} = data) do + %{"text" => text, "level" => level} = data + + wrapper_class = @class["wrapper"] + eyebrow_class = @class["eyebrow_title"] + footer_class = @class["footer_title"] + + ~s(
#{text}
" - "#{text}
" - end - end - end -end diff --git a/lib/helper/converter/editor_to_html/validator/editor_schema.ex b/lib/helper/converter/editor_to_html/validator/editor_schema.ex index 6c72745df..d1b8fa656 100644 --- a/lib/helper/converter/editor_to_html/validator/editor_schema.ex +++ b/lib/helper/converter/editor_to_html/validator/editor_schema.ex @@ -6,8 +6,8 @@ defmodule Helper.Converter.EditorToHTML.Validator.EditorSchema do # list @valid_list_mode ["checklist", "order_list", "unorder_list"] - @valid_list_label_type ["success", "done", "todo"] - @valid_list_indent [0, 1, 2, 3, 4] + @valid_list_label_type ["green", "red", "warn", "default"] + @valid_list_indent [0, 1, 2, 3] def get("editor") do %{ @@ -36,6 +36,7 @@ defmodule Helper.Converter.EditorToHTML.Validator.EditorSchema do "hideLabel" => [:boolean], "label" => [:string], "labelType" => [enum: @valid_list_label_type], + "prefixIndex" => [:string, required: false], "indent" => [enum: @valid_list_indent], "text" => [:string] } diff --git a/lib/helper/converter/html_sanitizer.ex b/lib/helper/converter/html_sanitizer.ex index c2937d2ae..6aef2f6ae 100644 --- a/lib/helper/converter/html_sanitizer.ex +++ b/lib/helper/converter/html_sanitizer.ex @@ -20,6 +20,7 @@ defmodule Helper.Converter.HtmlSanitizer do # Meta.allow_tag_with_these_attributes("em", []) Meta.allow_tag_with_these_attributes("b", []) Meta.allow_tag_with_these_attributes("i", []) + Meta.allow_tag_with_these_attributes("mark", ["class"]) Meta.allow_tag_with_these_attributes("code", ["class"]) Meta.allow_tag_with_these_attributes("pre", ["class"]) @@ -32,11 +33,31 @@ defmodule Helper.Converter.HtmlSanitizer do # Meta.allow_tag_with_these_attributes("h6", ["class"]) Meta.allow_tag_with_these_attributes("p", ["class"]) Meta.allow_tag_with_these_attributes("img", ["class", "src"]) - Meta.allow_tag_with_these_attributes("div", ["class"]) + Meta.allow_tag_with_these_attributes("div", ["class", "data-index"]) Meta.allow_tag_with_these_attributes("ul", ["class"]) Meta.allow_tag_with_these_attributes("ol", ["class"]) Meta.allow_tag_with_these_attributes("li", ["class"]) + Meta.allow_tag_with_these_attributes("svg", [ + "t", + "p-id", + "class", + "version", + "xmlns", + # should be viewBox, see: https://github.com/rrrene/html_sanitize_ex/issues/48 + "viewbox", + "width", + "height", + "x", + "y" + # "baseProfile", + # "contentScriptType", + # "contentStyleType", + # "preserveAspectRatio", + ]) + + Meta.allow_tag_with_these_attributes("path", ["d", "p-id"]) + Meta.allow_tag_with_these_attributes("iframe", [ "sandbox", "allow-same-origin", @@ -56,6 +77,9 @@ defmodule Helper.Converter.HtmlSanitizer do def sanitize({:skip_sanitize, html}), do: html def sanitize(html) when is_binary(html) do - html |> HtmlSanitizeEx.Scrubber.scrub(Scrubber) + html + |> HtmlSanitizeEx.Scrubber.scrub(Scrubber) + # workarround for https://github.com/rrrene/html_sanitize_ex/issues/48 + |> String.replace(" viewbox=\"", " viewBox=\"") end end diff --git a/lib/helper/metric/article.ex b/lib/helper/metric/article.ex deleted file mode 100644 index c1f265d5b..000000000 --- a/lib/helper/metric/article.ex +++ /dev/null @@ -1,30 +0,0 @@ -defmodule Helper.Metric.Article do - @moduledoc """ - html article class names parsed from editor.js's json data - - currently use https://editorjs.io/ as rich-text editor - # NOTE: DONOT CHANGE ONCE SET, OTHERWISE IT WILL CAUSE INCOMPATIBILITY ISSUE - """ - - @doc """ - get all the class names of the parsed editor.js's html parts - """ - def class_names(:html) do - %{ - # root wrapper - viewer: "article-viewer-wrapper", - unknow_block: "unknow-block", - invalid_block: "invalid-block", - # header - header: %{ - wrapper: "header-wrapper", - eyebrow_title: "eyebrow-title", - footer_title: "footer-title" - }, - # list - list: %{ - wrapper: "list-wrapper" - } - } - end -end diff --git a/lib/helper/spec_type.ex b/lib/helper/spec_type.ex deleted file mode 100644 index 40dd76b42..000000000 --- a/lib/helper/spec_type.ex +++ /dev/null @@ -1,30 +0,0 @@ -defmodule Helper.SpecType do - @moduledoc """ - custom @types - """ - - @typedoc """ - Type GraphQL flavor the error format - """ - @type gq_error :: {:error, [message: String.t(), code: non_neg_integer()]} - - @typedoc """ - general response conventions - """ - @type done :: {:ok, map} | {:error, map} - - @type id :: non_neg_integer() | String.t() - - @typedoc """ - general contribute type for wiki and cheatshet - """ - @type github_contributor2 :: %{ - github_id: String.t(), - avatar: String.t(), - html_url: String.t(), - nickname: String.t(), - bio: nil | String.t(), - location: nil | String.t(), - company: nil | String.t() - } -end diff --git a/lib/helper/types.ex b/lib/helper/types.ex new file mode 100644 index 000000000..4063c3a4f --- /dev/null +++ b/lib/helper/types.ex @@ -0,0 +1,66 @@ +defmodule Helper.Types do + @moduledoc """ + custom @types + """ + + @typedoc """ + Type GraphQL flavor the error format + """ + @type gq_error :: {:error, [message: String.t(), code: non_neg_integer()]} + + @typedoc """ + general response conventions + """ + @type done :: {:ok, map} | {:error, map} + + @type id :: non_neg_integer() | String.t() + + @typedoc """ + general contribute type for wiki and cheatshet + """ + @type github_contributor2 :: %{ + github_id: String.t(), + avatar: String.t(), + html_url: String.t(), + nickname: String.t(), + bio: nil | String.t(), + location: nil | String.t(), + company: nil | String.t() + } + + @typedoc """ + editor.js's header tool data format + """ + @type editor_header :: %{ + required(:text) => String.t(), + required(:level) => String.t(), + eyebrowTitle: String.t(), + footerTitle: String.t() + } + + @typedoc """ + valid editor.js's list item indent + """ + @type editor_list_indent :: 0 | 1 | 2 | 3 + + @typedoc """ + valid editor.js's list item label type + """ + @type editor_list_label_type :: :default | :red | :green | :warn + + @typedoc """ + editor.js's list item for order_list | unorder_list | checklist + """ + @type editor_list_item :: %{ + required(:hideLabel) => String.t(), + required(:indent) => editor_list_indent, + required(:label) => String.t(), + required(:labelType) => editor_list_label_type, + required(:text) => String.t(), + prefixIndex: String.t() + } + @typedoc """ + html fragment + """ + @type html :: String.t() +end diff --git a/lib/helper/utils.ex b/lib/helper/utils.ex index 13b64b4ff..a522a6788 100644 --- a/lib/helper/utils.ex +++ b/lib/helper/utils.ex @@ -237,4 +237,13 @@ defmodule Helper.Utils do defp update_word_count(word, acc) do Map.update(acc, to_string(word), 1, &(&1 + 1)) end + + # see https://stackoverflow.com/a/49558074/4050784 + @spec str_occurence(String.t(), String.t()) :: Integer.t() + def str_occurence(string, substr) when is_binary(string) and is_binary(substr) do + len = string |> String.split(substr) |> length() + len - 1 + end + + def str_occurence(_, _), do: "must be strings" end diff --git a/test/helper/converter/editor_to_html_test/header_test.exs b/test/helper/converter/editor_to_html_test/header_test.exs index c77e15cad..30303a3c8 100644 --- a/test/helper/converter/editor_to_html_test/header_test.exs +++ b/test/helper/converter/editor_to_html_test/header_test.exs @@ -3,10 +3,10 @@ defmodule GroupherServer.Test.Helper.Converter.EditorToHTML.Header do use GroupherServerWeb.ConnCase, async: true - alias Helper.Metric alias Helper.Converter.EditorToHTML, as: Parser + alias Helper.Converter.EditorToHTML.{Class, Frags} - @clazz Metric.Article.class_names(:html) + @root_class Class.article() describe "[header block unit]" do @editor_json %{ @@ -36,13 +36,19 @@ defmodule GroupherServer.Test.Helper.Converter.EditorToHTML.Header do ], "version" => "2.15.0" } - @tag :wip + @tag :wip2 test "header parse should work" do {:ok, editor_string} = Jason.encode(@editor_json) {:ok, converted} = Parser.to_html(editor_string) + h1_frag = Frags.Header.get(%{"text" => "header content", "level" => 1}) + h2_frag = Frags.Header.get(%{"text" => "header content", "level" => 2}) + h3_frag = Frags.Header.get(%{"text" => "header content", "level" => 3}) + + viewer_class = @root_class["viewer"] + assert converted == - "evel script
evel script
Editor.js is an element <script>evel script</script>
Editor.js is an element <script>evel script</script>
paragraph content
paragraph content
this is a basic markdown text
My in-line-code-content
is best
this is a basic markdown text
My in-line-code-content
is best