21 |
22 |
23 |
24 | <%= if i != 0 do %>
25 |
38 | <% end %>
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/lib/app_web/templates/layout/app.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
Github Backup - dwyl
20 |
21 |
22 |
">
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
<%= get_flash(@conn, :info) %>
33 |
<%= get_flash(@conn, :error) %>
34 |
35 |
36 | <%= render @view_module, @view_template, assigns %>
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/assets/brunch-config.js:
--------------------------------------------------------------------------------
1 | exports.config = {
2 | // See https://brunch.io/#documentation for docs.
3 | files: {
4 | javascripts: {
5 | joinTo: "js/app.js"
6 |
7 | // To use a separate vendor.js bundle, specify two files path
8 | // https://brunch.io/docs/config#-files-
9 | // joinTo: {
10 | // "js/app.js": /^js/,
11 | // "js/vendor.js": /^(?!js)/
12 | // }
13 | //
14 | // To change the order of concatenation of files, explicitly mention here
15 | // order: {
16 | // before: [
17 | // "vendor/js/jquery-2.1.1.js",
18 | // "vendor/js/bootstrap.min.js"
19 | // ]
20 | // }
21 | },
22 | stylesheets: {
23 | joinTo: "css/app.css"
24 | },
25 | templates: {
26 | joinTo: "js/app.js"
27 | }
28 | },
29 |
30 | conventions: {
31 | // This option sets where we should place non-css and non-js assets in.
32 | // By default, we set this to "/assets/static". Files in this directory
33 | // will be copied to `paths.public`, which is "priv/static" by default.
34 | assets: /^(static)/
35 | },
36 |
37 | // Phoenix paths configuration
38 | paths: {
39 | // Dependencies and current project directories to watch
40 | watched: ["static", "css", "js", "vendor"],
41 | // Where to compile files to
42 | public: "../priv/static"
43 | },
44 |
45 | // Configure your plugins
46 | plugins: {
47 | babel: {
48 | // Do not use ES6 compiler in vendor code
49 | ignore: [/vendor/]
50 | }
51 | },
52 |
53 | modules: {
54 | autoRequire: {
55 | "js/app.js": ["js/app"]
56 | }
57 | },
58 |
59 | npm: {
60 | enabled: true
61 | }
62 | };
63 |
--------------------------------------------------------------------------------
/lib/app/helpers/issue.ex:
--------------------------------------------------------------------------------
1 | defmodule App.Helpers.IssueHelper do
2 | @moduledoc """
3 | Helpers function for issues
4 | """
5 | def get_issues(issues) do
6 | issues
7 | |> Enum.filter(fn issue ->
8 | !issue.pull_request
9 | end)
10 | end
11 |
12 | def get_map_comments(issues) do
13 | issues
14 | |> Enum.map(fn i ->
15 | Enum.map(i.comments, fn c ->
16 | %{c.comment_id => c.comment}
17 | end)
18 | end)
19 | |> List.flatten()
20 | |> Enum.reduce(%{}, fn(c, acc) ->
21 | Map.merge(c, acc)
22 | end)
23 | end
24 |
25 | def attach_comments(issues, comments) do
26 | comments = Enum.group_by(comments, &(&1["issue_url"]))
27 | Enum.map(issues, fn i ->
28 | Map.put(i, "comments", comments[i["url"]] || [])
29 | end)
30 | end
31 |
32 | def issues_as_comments(issues) do
33 | issues
34 | |> Enum.map(fn i ->
35 | comment_issue = %{
36 | comment_id: "#{i.issue_id}_1",
37 | versions: [%{
38 | author: i.issue_author.id,
39 | inserted_at: i.inserted_at,
40 | updated_at: i.updated_at
41 | }],
42 | comment: i.description,
43 | inserted_at: i.inserted_at,
44 | updated_at: i.updated_at,
45 | }
46 | comments = [comment_issue | i.comments]
47 | Map.put(i, :comments, comments)
48 | end)
49 | end
50 |
51 | def get_s3_content(issue, comments) do
52 | issue.comments
53 | |> Enum.map(fn c ->
54 | version = c.versions
55 | |> List.first()
56 | %{version.id => comments[c.comment_id]}
57 | end)
58 | |> Enum.reduce(%{}, fn(version, acc) ->
59 | Map.merge(version, acc)
60 | end)
61 | end
62 |
63 | end
64 |
--------------------------------------------------------------------------------
/lib/app_web.ex:
--------------------------------------------------------------------------------
1 | defmodule AppWeb do
2 | @moduledoc """
3 | The entrypoint for defining your web interface, such
4 | as controllers, views, channels and so on.
5 |
6 | This can be used in your application as:
7 |
8 | use AppWeb, :controller
9 | use AppWeb, :view
10 |
11 | The definitions below will be executed for every view,
12 | controller, etc, so keep them short and clean, focused
13 | on imports, uses and aliases.
14 |
15 | Do NOT define functions inside the quoted expressions
16 | below. Instead, define any helper function in modules
17 | and import those modules here.
18 | """
19 |
20 | def controller do
21 | quote do
22 | use Phoenix.Controller, namespace: AppWeb
23 | import Plug.Conn
24 | alias AppWeb.Router.Helpers, as: Routes
25 | import AppWeb.Gettext
26 | end
27 | end
28 |
29 | def view do
30 | quote do
31 | use Phoenix.View, root: "lib/app_web/templates",
32 | namespace: AppWeb
33 |
34 | # Import convenience functions from controllers
35 | import Phoenix.Controller, only: [get_flash: 2, view_module: 1]
36 |
37 | # Use all HTML functionality (forms, tags, etc)
38 | use Phoenix.HTML
39 |
40 | alias AppWeb.Router.Helpers, as: Routes
41 | import AppWeb.ErrorHelpers
42 | import AppWeb.Gettext
43 | end
44 | end
45 |
46 | def router do
47 | quote do
48 | use Phoenix.Router
49 | import Plug.Conn
50 | import Phoenix.Controller
51 | end
52 | end
53 |
54 | def channel do
55 | quote do
56 | use Phoenix.Channel
57 | import AppWeb.Gettext
58 | end
59 | end
60 |
61 | @doc """
62 | When used, dispatch to the appropriate controller/view/etc.
63 | """
64 | defmacro __using__(which) when is_atom(which) do
65 | apply(__MODULE__, which, [])
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/lib/app_web/endpoint.ex:
--------------------------------------------------------------------------------
1 | defmodule AppWeb.Endpoint do
2 | use Phoenix.Endpoint, otp_app: :app
3 |
4 | socket "/socket", AppWeb.UserSocket,
5 | websocket: true,
6 | longpoll: false
7 |
8 | # Serve at "/" the static files from "priv/static" directory.
9 | #
10 | # You should set gzip to true if you are running phoenix.digest
11 | # when deploying your static files in production.
12 | plug Plug.Static,
13 | at: "/", from: :app, gzip: false,
14 | only: ~w(css fonts images js favicon.ico robots.txt)
15 |
16 | # Code reloading can be explicitly enabled under the
17 | # :code_reloader configuration of your endpoint.
18 | if code_reloading? do
19 | socket "/phoenix/live_reload/socket", Phoenix.LiveReloader.Socket
20 | plug Phoenix.LiveReloader
21 | plug Phoenix.CodeReloader
22 | end
23 |
24 | plug Plug.RequestId
25 | plug Plug.Logger
26 |
27 | plug Plug.Parsers,
28 | parsers: [:urlencoded, :multipart, :json],
29 | pass: ["*/*"],
30 | json_decoder: Poison
31 |
32 | plug Plug.MethodOverride
33 | plug Plug.Head
34 |
35 | # The session will be stored in the cookie and signed,
36 | # this means its contents can be read but not tampered with.
37 | # Set :encryption_salt if you would also like to encrypt it.
38 | plug Plug.Session,
39 | store: :cookie,
40 | key: "_app_key",
41 | signing_salt: "1g+2CfaB"
42 |
43 | plug AppWeb.Router
44 |
45 | @doc """
46 | Callback invoked for dynamically configuring the endpoint.
47 |
48 | It receives the endpoint configuration and checks if
49 | configuration should be loaded from the system environment.
50 | """
51 | def init(_key, config) do
52 | if config[:load_from_system_env] do
53 | port = System.get_env("PORT") || raise "expected the PORT environment variable to be set"
54 | {:ok, Keyword.put(config, :http, [:inet6, port: port])}
55 | else
56 | {:ok, config}
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/lib/app_web/templates/issue/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
Github Backup
4 | <%= if @pull_request do %>
5 |
Pull Request
6 | <% else %>
7 |
Issue
8 | <% end %>
9 |
<%= @issue_title %>
10 | <%= if @issue_status == "closed" do %>
11 |
12 | Closed
13 |
14 | <% end %>
15 | <%= if @issue_status == "reopened" do %>
16 |
17 | Reopened
18 |
19 | <% end %>
20 |
21 |
22 | <%= for comment <- @comment_details do %>
23 |
24 |
25 | <%= if comment.deleted == true do %>
26 |
27 |
28 | <%= comment.deleted_by %>
29 | deleted this comment on <%= format_date comment.inserted_at %>
30 |
31 | <% end %>
32 |
33 | <%= for {version, i} <- Enum.with_index(comment.versions) do %>
34 |
35 |
36 |
37 |
38 |
39 |
48 |
49 | <% end %>
50 |
51 | <% end %>
52 |
53 |
--------------------------------------------------------------------------------
/lib/app_web/helpers/view.ex:
--------------------------------------------------------------------------------
1 | defmodule AppWeb.Helpers.View do
2 | @moduledoc """
3 | Helper functions for views to use
4 | """
5 | use Phoenix.HTML
6 |
7 | @shared_classes "ml3 mv3 tl w-100 ba gb-b--light-gray br2 comment relative"
8 | @checked_box "
"
9 | @unchecked_box "
"
10 | @regex_unchecked ~r/^[-,*,+] \[\s\]|\r\n[-,*,+] \[\s\]/
11 | @regex_unchecked_inc_text ~r/^[-,*,+]\s\[\s\]\s\S+|\r\n[-,*,+]\s\[\s\]\s\S+/
12 | @regex_checked ~r/\r\n[-,*,+] \[[X,x]\]|^[-,*,+] \[[X,x]\]/
13 | @regex_checked_inc_text ~r/\r\n[-,*,+] \[[X,x]\] \S+|^[-,*,+] \[[X,x]\] \S+/
14 |
15 | def format_date(date) do
16 | date
17 | |> Timex.from_now
18 | end
19 |
20 | # Function not in use, but will be when implementing: https://git.io/vxU7b
21 | # def tidyISODate(iso_date) do
22 | # iso_date
23 | # |> String.replace("T", " ")
24 | # |> String.split(".")
25 | # |> List.first
26 | # end
27 |
28 | def style_comments(version_index) do
29 | case version_index do
30 | 0 ->
31 | @shared_classes
32 | _ ->
33 | @shared_classes <> " bg-moon-gray"
34 | end
35 | end
36 |
37 | def display_markdown(text) do
38 | text
39 | |> sanitize_html
40 | |> detect_checkboxes
41 | |> Earmark.as_html!(%Earmark.Options{breaks: true})
42 | |> raw
43 | end
44 |
45 | def sanitize_html(html_comment) do
46 | HtmlSanitizeEx.basic_html(html_comment)
47 | end
48 |
49 | def detect_checkboxes(text) do
50 | text
51 | |> replace_checked_boxes
52 | |> replace_unchecked_boxes
53 | end
54 |
55 | def replace_checked_boxes(text) do
56 | if String.match?(text, @regex_checked_inc_text) do
57 | String.replace(text, @regex_checked, "\r\n " <> @checked_box)
58 | else
59 | text
60 | end
61 | end
62 |
63 | def replace_unchecked_boxes(text) do
64 | if String.match?(text, @regex_unchecked_inc_text) do
65 | String.replace(text, @regex_unchecked, "\r\n " <> @unchecked_box)
66 | else
67 | text
68 | end
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/test/app_web/controllers/event_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule AppWeb.EventTestController do
2 | use AppWeb.ConnCase
3 | alias Poison.Parser, as: PP
4 | alias Plug.Conn
5 |
6 | @fixtures [
7 | %{payload: "installation", event: "installation", json_reply: "new installation", status: 200},
8 | %{payload: "installation_repositories", event: "installation_repositories", json_reply: "new installation", status: 200},
9 | %{payload: "issue_opened", event: "issues", json_reply: "issue created", status: 200},
10 | %{payload: "issue_closed", event: "issues", json_reply: "issue closed", status: 200},
11 | %{payload: "issue_reopened", event: "issues", json_reply: "issue reopened", status: 200},
12 | %{payload: "issue_title_edited", event: "issues", json_reply: "issue edited", status: 200},
13 | %{payload: "issue_edited", event: "issues", json_reply: "issue edited", status: 200},
14 | %{payload: "pr_opened", event: "pull_request", json_reply: "issue created", status: 200},
15 | %{payload: "pr_edited", event: "pull_request", json_reply: "issue edited", status: 200},
16 | %{payload: "pr_closed", event: "pull_request", json_reply: "issue closed", status: 200},
17 | %{payload: "pr_reopened", event: "pull_request", json_reply: "issue reopened", status: 200},
18 | %{payload: "comment_created", event: "issue_comment", json_reply: "comment created", status: 200},
19 | %{payload: "comment_edited", event: "issue_comment", json_reply: "comment edited", status: 200},
20 | %{payload: "comment_deleted", event: "issue_comment", json_reply: "comment deleted", status: 200},
21 | %{payload: "integration_installation", event: "integration_installation", json_reply: "event unknow", status: 404}
22 | ]
23 | |> Enum.map(&(%{&1 | payload: "./test/fixtures/#{&1.payload}.json"}))
24 |
25 | test "POST /event/new", %{conn: conn} do
26 | for fixture <- @fixtures do
27 | payload = fixture.payload |> File.read! |> PP.parse!
28 | conn =
29 | conn
30 | |> Conn.put_req_header("x-github-event", "#{fixture.event}")
31 | |> post("/event/new", payload)
32 | assert json_response(conn, fixture.status)
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/lib/app_web/templates/page/index.html.eex:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Keep the history
5 | of your issues
6 | and comments.
7 |
8 |
9 | Gitbu
10 | has been created so you never lose track of the history of your issues
11 | and comments. Find out what was added, when and by who.
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
INSTALL GITBU
22 |
23 | Find the app in the
24 | Github Marketplace
25 | and download it. Choose which organisations and repositories to use the app
26 | with.
27 |
28 |
29 |
30 |
31 |
32 |
33 |
MAKE A CHANGE
34 |
35 | Create a new issue, add or edit a comment, close or reopen an issue.
36 |
37 |
38 |
39 |
40 |
41 |
42 |
TRACK YOUR CHANGES
43 |
44 | Select the issue meta table in the issue description and see your changes on
45 | Gitbu!
46 |
47 |
48 |
49 |
50 |
51 |
52 |
Coming Soon
53 |
54 | Gitbu is currently in development and some features are still being built:
55 |
56 |
Github Authorisation
57 |
Offline access
58 |
59 |
60 |
--------------------------------------------------------------------------------
/config/dev.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For development, we disable any cache and enable
4 | # debugging and code reloading.
5 | #
6 | # The watchers configuration can be used to run external
7 | # watchers to your application. For example, we use it
8 | # with brunch.io to recompile .js and .css sources.
9 | config :app, AppWeb.Endpoint,
10 | http: [port: 4000],
11 | debug_errors: true,
12 | code_reloader: true,
13 | check_origin: false,
14 | watchers: [node: ["node_modules/brunch/bin/brunch", "watch", "--stdin",
15 | cd: Path.expand("../assets", __DIR__)]]
16 |
17 | # ## SSL Support
18 | #
19 | # In order to use HTTPS in development, a self-signed
20 | # certificate can be generated by running the following
21 | # command from your terminal:
22 | #
23 | # openssl req -new -newkey rsa:4096 -days 365 -nodes -x509 -subj "/C=US/ST=Denial/L=Springfield/O=Dis/CN=www.example.com" -keyout priv/server.key -out priv/server.pem
24 | #
25 | # The `http:` config above can be replaced with:
26 | #
27 | # https: [port: 4000, keyfile: "priv/server.key", certfile: "priv/server.pem"],
28 | #
29 | # If desired, both `http:` and `https:` keys can be
30 | # configured to run both http and https servers on
31 | # different ports.
32 |
33 | # Watch static and templates for browser reloading.
34 | config :app, AppWeb.Endpoint,
35 | live_reload: [
36 | patterns: [
37 | ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
38 | ~r{priv/gettext/.*(po)$},
39 | ~r{lib/app_web/views/.*(ex)$},
40 | ~r{lib/app_web/templates/.*(eex)$}
41 | ]
42 | ]
43 |
44 | # Do not include metadata nor timestamps in development logs
45 | config :logger, :console, format: "[$level] $message\n"
46 |
47 | # Set a higher stacktrace during development. Avoid configuring such
48 | # in production as building large stacktraces may be expensive.
49 | config :phoenix, :stacktrace_depth, 20
50 |
51 | # Configure your database
52 | config :app, App.Repo,
53 | adapter: Ecto.Adapters.Postgres,
54 | username: "postgres",
55 | password: "postgres",
56 | database: "app_dev",
57 | hostname: "localhost",
58 | pool_size: 10
59 |
60 | config :app, :github_api, AppWeb.GithubAPI.HTTPClient
61 | config :app, :s3_api, AppWeb.AWS.S3
62 |
--------------------------------------------------------------------------------
/lib/app_web/controllers/event_type.ex:
--------------------------------------------------------------------------------
1 | defmodule AppWeb.EventType do
2 | alias AppWeb.EventTypeHandlers
3 |
4 | @moduledoc """
5 | Determines the type of event received by the Github Webhooks requests
6 | """
7 |
8 | def get_event_type(x_github_event, action, conn, payload) do
9 | case x_github_event do
10 | "installation" -> type("installation", action, conn, payload)
11 | "installation_repositories" -> type("installation_repositories", action, conn, payload)
12 | "issues" -> type("issues", action, conn, payload)
13 | "issue_comment" -> type("issue_comment", action, conn, payload)
14 | "pull_request"-> type("pull_request", action, conn, payload)
15 | _ -> type("unknow", conn)
16 | end
17 | end
18 |
19 | defp type("unknow", conn) do
20 | EventTypeHandlers.unknow_event(conn)
21 | end
22 |
23 | defp type("installation", action, conn, payload) do
24 | case action do
25 | "created" -> EventTypeHandlers.new_installation(conn, payload)
26 | end
27 | end
28 |
29 | defp type("installation_repositories", action, conn, payload) do
30 | case action do
31 | "added" -> EventTypeHandlers.new_installation(conn, payload)
32 | end
33 | end
34 |
35 | defp type("issues", action, conn, payload) do
36 | case action do
37 | "opened" -> EventTypeHandlers.issue_created(conn, payload)
38 | "edited" -> EventTypeHandlers.issue_edited(conn, payload)
39 | "closed" -> EventTypeHandlers.issue_closed(conn, payload)
40 | "reopened" -> EventTypeHandlers.issue_reopened(conn, payload)
41 | end
42 | end
43 |
44 | defp type("issue_comment", action, conn, payload) do
45 | case action do
46 | "created" -> EventTypeHandlers.comment_created(conn, payload)
47 | "edited" -> EventTypeHandlers.comment_edited(conn, payload)
48 | "deleted" -> EventTypeHandlers.comment_deleted(conn, payload)
49 | end
50 | end
51 |
52 | defp type("pull_request", action, conn, payload) do
53 | case action do
54 | "opened" -> EventTypeHandlers.issue_created(conn, payload)
55 | "edited" -> EventTypeHandlers.issue_edited(conn, payload)
56 | "closed" -> EventTypeHandlers.issue_closed(conn, payload)
57 | "reopened" -> EventTypeHandlers.issue_reopened(conn, payload)
58 | end
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/assets/js/socket.js:
--------------------------------------------------------------------------------
1 | // NOTE: The contents of this file will only be executed if
2 | // you uncomment its entry in "assets/js/app.js".
3 |
4 | // To use Phoenix channels, the first step is to import Socket
5 | // and connect at the socket path in "lib/web/endpoint.ex":
6 | import {Socket} from "phoenix"
7 |
8 | let socket = new Socket("/socket", {params: {token: window.userToken}})
9 |
10 | // When you connect, you'll often need to authenticate the client.
11 | // For example, imagine you have an authentication plug, `MyAuth`,
12 | // which authenticates the session and assigns a `:current_user`.
13 | // If the current user exists you can assign the user's token in
14 | // the connection for use in the layout.
15 | //
16 | // In your "lib/web/router.ex":
17 | //
18 | // pipeline :browser do
19 | // ...
20 | // plug MyAuth
21 | // plug :put_user_token
22 | // end
23 | //
24 | // defp put_user_token(conn, _) do
25 | // if current_user = conn.assigns[:current_user] do
26 | // token = Phoenix.Token.sign(conn, "user socket", current_user.id)
27 | // assign(conn, :user_token, token)
28 | // else
29 | // conn
30 | // end
31 | // end
32 | //
33 | // Now you need to pass this token to JavaScript. You can do so
34 | // inside a script tag in "lib/web/templates/layout/app.html.eex":
35 | //
36 | //
37 | //
38 | // You will need to verify the user token in the "connect/2" function
39 | // in "lib/web/channels/user_socket.ex":
40 | //
41 | // def connect(%{"token" => token}, socket) do
42 | // # max_age: 1209600 is equivalent to two weeks in seconds
43 | // case Phoenix.Token.verify(socket, "user socket", token, max_age: 1209600) do
44 | // {:ok, user_id} ->
45 | // {:ok, assign(socket, :user, user_id)}
46 | // {:error, reason} ->
47 | // :error
48 | // end
49 | // end
50 | //
51 | // Finally, pass the token on connect as below. Or remove it
52 | // from connect if you don't care about authentication.
53 |
54 | socket.connect()
55 |
56 | // Now that you are connected, you can join channels with a topic:
57 | let channel = socket.channel("topic:subtopic", {})
58 | channel.join()
59 | .receive("ok", resp => { console.log("Joined successfully", resp) })
60 | .receive("error", resp => { console.log("Unable to join", resp) })
61 |
62 | export default socket
63 |
--------------------------------------------------------------------------------
/mix.exs:
--------------------------------------------------------------------------------
1 | defmodule App.Mixfile do
2 | use Mix.Project
3 |
4 | def project do
5 | [
6 | app: :app,
7 | version: "0.0.1",
8 | elixir: "~> 1.4",
9 | elixirc_paths: elixirc_paths(Mix.env),
10 | compilers: [:phoenix, :gettext] ++ Mix.compilers,
11 | test_coverage: [tool: ExCoveralls],
12 | preferred_cli_env: [coveralls: :test, "coveralls.detail": :test, "coveralls.post": :test, "coveralls.html": :test],
13 | start_permanent: Mix.env == :prod,
14 | aliases: aliases(),
15 | deps: deps()
16 | ]
17 | end
18 |
19 | # Configuration for the OTP application.
20 | #
21 | # Type `mix help compile.app` for more information.
22 | def application do
23 | [
24 | mod: {App.Application, []},
25 | extra_applications: [:logger, :runtime_tools]
26 | ]
27 | end
28 |
29 | # Specifies which paths to compile per environment.
30 | defp elixirc_paths(:test), do: ["lib", "test/support"]
31 | defp elixirc_paths(_), do: ["lib"]
32 |
33 | # Specifies your project dependencies.
34 | #
35 | # Type `mix help deps` for examples and options.
36 | defp deps do
37 | [
38 | {:phoenix, "~> 1.4.0"},
39 | {:phoenix_pubsub, "~> 1.0"},
40 | {:ecto_sql, "~> 3.0"},
41 | {:phoenix_ecto, "~> 4.0"},
42 | {:postgrex, ">= 0.0.0"},
43 | {:phoenix_html, "~> 2.10"},
44 | {:phoenix_live_reload, "~> 1.0", only: :dev},
45 | {:gettext, "~> 0.11"},
46 | {:plug_cowboy, "~> 2.0"},
47 | {:plug, "~> 1.7"},
48 | {:poison, "~> 3.1"},
49 | {:httpoison, "~> 1.0"},
50 | {:excoveralls, "~> 0.8", only: :test},
51 | {:credo, "~> 0.3", only: [:dev, :test]},
52 | {:pre_commit, "~> 0.2.4", only: :dev},
53 | {:joken, "~> 1.5"},
54 | {:jose, "~> 1.8"},
55 | {:ex_aws, "~> 2.0"},
56 | {:ex_aws_s3, "~> 2.0"},
57 | {:earmark, "~> 1.2.4"},
58 | {:timex, "~> 3.1"},
59 | {:html_sanitize_ex, "~> 1.3"},
60 | {:jason, "~> 1.0"}
61 | ]
62 | end
63 |
64 | # Aliases are shortcuts or tasks specific to the current project.
65 | # For example, to create, migrate and run the seeds file at once:
66 | #
67 | # $ mix ecto.setup
68 | #
69 | # See the documentation for `Mix` for more info on aliases.
70 | defp aliases do
71 | [
72 | "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"],
73 | "ecto.reset": ["ecto.drop", "ecto.setup"],
74 | test: ["ecto.create --quiet", "ecto.migrate", "test"]
75 | ]
76 | end
77 | end
78 |
--------------------------------------------------------------------------------
/priv/gettext/en/LC_MESSAGES/errors.po:
--------------------------------------------------------------------------------
1 | ## `msgid`s in this file come from POT (.pot) files.
2 | ##
3 | ## Do not add, change, or remove `msgid`s manually here as
4 | ## they're tied to the ones in the corresponding POT file
5 | ## (with the same domain).
6 | ##
7 | ## Use `mix gettext.extract --merge` or `mix gettext.merge`
8 | ## to merge POT files into PO files.
9 | msgid ""
10 | msgstr ""
11 | "Language: en\n"
12 |
13 | ## From Ecto.Changeset.cast/4
14 | msgid "can't be blank"
15 | msgstr ""
16 |
17 | ## From Ecto.Changeset.unique_constraint/3
18 | msgid "has already been taken"
19 | msgstr ""
20 |
21 | ## From Ecto.Changeset.put_change/3
22 | msgid "is invalid"
23 | msgstr ""
24 |
25 | ## From Ecto.Changeset.validate_acceptance/3
26 | msgid "must be accepted"
27 | msgstr ""
28 |
29 | ## From Ecto.Changeset.validate_format/3
30 | msgid "has invalid format"
31 | msgstr ""
32 |
33 | ## From Ecto.Changeset.validate_subset/3
34 | msgid "has an invalid entry"
35 | msgstr ""
36 |
37 | ## From Ecto.Changeset.validate_exclusion/3
38 | msgid "is reserved"
39 | msgstr ""
40 |
41 | ## From Ecto.Changeset.validate_confirmation/3
42 | msgid "does not match confirmation"
43 | msgstr ""
44 |
45 | ## From Ecto.Changeset.no_assoc_constraint/3
46 | msgid "is still associated with this entry"
47 | msgstr ""
48 |
49 | msgid "are still associated with this entry"
50 | msgstr ""
51 |
52 | ## From Ecto.Changeset.validate_length/3
53 | msgid "should be %{count} character(s)"
54 | msgid_plural "should be %{count} character(s)"
55 | msgstr[0] ""
56 | msgstr[1] ""
57 |
58 | msgid "should have %{count} item(s)"
59 | msgid_plural "should have %{count} item(s)"
60 | msgstr[0] ""
61 | msgstr[1] ""
62 |
63 | msgid "should be at least %{count} character(s)"
64 | msgid_plural "should be at least %{count} character(s)"
65 | msgstr[0] ""
66 | msgstr[1] ""
67 |
68 | msgid "should have at least %{count} item(s)"
69 | msgid_plural "should have at least %{count} item(s)"
70 | msgstr[0] ""
71 | msgstr[1] ""
72 |
73 | msgid "should be at most %{count} character(s)"
74 | msgid_plural "should be at most %{count} character(s)"
75 | msgstr[0] ""
76 | msgstr[1] ""
77 |
78 | msgid "should have at most %{count} item(s)"
79 | msgid_plural "should have at most %{count} item(s)"
80 | msgstr[0] ""
81 | msgstr[1] ""
82 |
83 | ## From Ecto.Changeset.validate_number/3
84 | msgid "must be less than %{number}"
85 | msgstr ""
86 |
87 | msgid "must be greater than %{number}"
88 | msgstr ""
89 |
90 | msgid "must be less than or equal to %{number}"
91 | msgstr ""
92 |
93 | msgid "must be greater than or equal to %{number}"
94 | msgstr ""
95 |
96 | msgid "must be equal to %{number}"
97 | msgstr ""
98 |
--------------------------------------------------------------------------------
/priv/gettext/errors.pot:
--------------------------------------------------------------------------------
1 | ## This file is a PO Template file.
2 | ##
3 | ## `msgid`s here are often extracted from source code.
4 | ## Add new translations manually only if they're dynamic
5 | ## translations that can't be statically extracted.
6 | ##
7 | ## Run `mix gettext.extract` to bring this file up to
8 | ## date. Leave `msgstr`s empty as changing them here as no
9 | ## effect: edit them in PO (`.po`) files instead.
10 |
11 | ## From Ecto.Changeset.cast/4
12 | msgid "can't be blank"
13 | msgstr ""
14 |
15 | ## From Ecto.Changeset.unique_constraint/3
16 | msgid "has already been taken"
17 | msgstr ""
18 |
19 | ## From Ecto.Changeset.put_change/3
20 | msgid "is invalid"
21 | msgstr ""
22 |
23 | ## From Ecto.Changeset.validate_acceptance/3
24 | msgid "must be accepted"
25 | msgstr ""
26 |
27 | ## From Ecto.Changeset.validate_format/3
28 | msgid "has invalid format"
29 | msgstr ""
30 |
31 | ## From Ecto.Changeset.validate_subset/3
32 | msgid "has an invalid entry"
33 | msgstr ""
34 |
35 | ## From Ecto.Changeset.validate_exclusion/3
36 | msgid "is reserved"
37 | msgstr ""
38 |
39 | ## From Ecto.Changeset.validate_confirmation/3
40 | msgid "does not match confirmation"
41 | msgstr ""
42 |
43 | ## From Ecto.Changeset.no_assoc_constraint/3
44 | msgid "is still associated with this entry"
45 | msgstr ""
46 |
47 | msgid "are still associated with this entry"
48 | msgstr ""
49 |
50 | ## From Ecto.Changeset.validate_length/3
51 | msgid "should be %{count} character(s)"
52 | msgid_plural "should be %{count} character(s)"
53 | msgstr[0] ""
54 | msgstr[1] ""
55 |
56 | msgid "should have %{count} item(s)"
57 | msgid_plural "should have %{count} item(s)"
58 | msgstr[0] ""
59 | msgstr[1] ""
60 |
61 | msgid "should be at least %{count} character(s)"
62 | msgid_plural "should be at least %{count} character(s)"
63 | msgstr[0] ""
64 | msgstr[1] ""
65 |
66 | msgid "should have at least %{count} item(s)"
67 | msgid_plural "should have at least %{count} item(s)"
68 | msgstr[0] ""
69 | msgstr[1] ""
70 |
71 | msgid "should be at most %{count} character(s)"
72 | msgid_plural "should be at most %{count} character(s)"
73 | msgstr[0] ""
74 | msgstr[1] ""
75 |
76 | msgid "should have at most %{count} item(s)"
77 | msgid_plural "should have at most %{count} item(s)"
78 | msgstr[0] ""
79 | msgstr[1] ""
80 |
81 | ## From Ecto.Changeset.validate_number/3
82 | msgid "must be less than %{number}"
83 | msgstr ""
84 |
85 | msgid "must be greater than %{number}"
86 | msgstr ""
87 |
88 | msgid "must be less than or equal to %{number}"
89 | msgstr ""
90 |
91 | msgid "must be greater than or equal to %{number}"
92 | msgstr ""
93 |
94 | msgid "must be equal to %{number}"
95 | msgstr ""
96 |
--------------------------------------------------------------------------------
/test/app_web/controllers/issue_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule AppWeb.IssueControllerTest do
2 | use AppWeb.ConnCase
3 | alias App.{Issue, User, Repo}
4 |
5 | describe "loads issue page" do
6 | setup do
7 | user = %User{
8 | login: "user_login",
9 | user_id: 1,
10 | avatar_url: "/avatar.jpg",
11 | html_url: "/user/1"
12 | }
13 | |> User.changeset
14 | |> Repo.insert!
15 |
16 | issue_params = %{
17 | issue_id: 1,
18 | title: "Test issue title",
19 | comments: [
20 | %{
21 | comment_id: "1_1",
22 | versions: [%{author: user.id}]
23 | }
24 | ]
25 | }
26 | issue_params2 = %{
27 | issue_id: 2,
28 | title: "Test issue title 2",
29 | comments: [
30 | %{
31 | comment_id: "1_2",
32 | versions: [%{author: user.id}]
33 | }
34 | ],
35 | issue_status: [
36 | %{
37 | event: "closed"
38 | }
39 | ]
40 | }
41 |
42 | issue_params3 = %{
43 | issue_id: 3,
44 | title: "Test issue title 3",
45 | comments: [
46 | %{
47 | comment_id: "1_3",
48 | versions: [%{author: user.id}]
49 | }
50 | ],
51 | issue_status: [
52 | %{
53 | event: "reopened"
54 | }
55 | ]
56 | }
57 |
58 | changeset = Issue.changeset(%Issue{}, issue_params)
59 | changeset2 = Issue.changeset(%Issue{}, issue_params2)
60 | changeset3 = Issue.changeset(%Issue{}, issue_params3)
61 | issues = Repo.insert!(changeset)
62 | issue2 = Repo.insert!(changeset2)
63 | issue3 = Repo.insert!(changeset3)
64 |
65 | {:ok, conn: build_conn()
66 | |> assign(:issue, issues)
67 | |> assign(:issue2, issue2)
68 | |> assign(:issue3, issue3)}
69 | end
70 |
71 | test "GET /issues/1", %{conn: conn} do
72 | conn = get conn, "/issues/1"
73 | assert html_response(conn, 200) =~ "Test issue title"
74 | end
75 |
76 | test "GET /issues/2", %{conn: conn} do
77 | conn = get conn, "/issues/2"
78 | assert html_response(conn, 200) =~ "Test issue title 2"
79 | end
80 |
81 | test "GET /issues/3", %{conn: conn} do
82 | conn = get conn, "/issues/3"
83 | assert html_response(conn, 200) =~ "Test issue title 3"
84 | end
85 | end
86 | end
87 |
--------------------------------------------------------------------------------
/config/prod.exs:
--------------------------------------------------------------------------------
1 | use Mix.Config
2 |
3 | # For production, we often load configuration from external
4 | # sources, such as your system environment. For this reason,
5 | # you won't find the :http configuration below, but set inside
6 | # AppWeb.Endpoint.init/2 when load_from_system_env is
7 | # true. Any dynamic configuration should be done there.
8 | #
9 | # Don't forget to configure the url host to something meaningful,
10 | # Phoenix uses this information when generating URLs.
11 | #
12 | # Finally, we also include the path to a cache manifest
13 | # containing the digested version of static files. This
14 | # manifest is generated by the mix phx.digest task
15 | # which you typically run after static files are built.
16 | config :app, AppWeb.Endpoint,
17 | load_from_system_env: true,
18 | url: [host: "example.com", port: 80],
19 | cache_static_manifest: "priv/static/cache_manifest.json"
20 |
21 | # Configure your database
22 | config :app, App.Repo,
23 | adapter: Ecto.Adapters.Postgres,
24 | url: System.get_env("DATABASE_URL"),
25 | pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10"),
26 | ssl: true
27 |
28 | config :app, :github_api, AppWeb.GithubAPI.HTTPClient
29 | config :app, :s3_api, AppWeb.AWS.S3
30 |
31 | # Do not print debug messages in production
32 | config :logger, level: :info
33 |
34 | # Configures pre-commit commands
35 | config :pre_commit, commands: ["credo --strict", "test"], verbose: true
36 |
37 | # ## SSL Support
38 | #
39 | # To get SSL working, you will need to add the `https` key
40 | # to the previous section and set your `:url` port to 443:
41 | #
42 | # config :app, AppWeb.Endpoint,
43 | # ...
44 | # url: [host: "example.com", port: 443],
45 | # https: [:inet6,
46 | # port: 443,
47 | # keyfile: System.get_env("SOME_APP_SSL_KEY_PATH"),
48 | # certfile: System.get_env("SOME_APP_SSL_CERT_PATH")]
49 | #
50 | # Where those two env variables return an absolute path to
51 | # the key and cert in disk or a relative path inside priv,
52 | # for example "priv/ssl/server.key".
53 | #
54 | # We also recommend setting `force_ssl`, ensuring no data is
55 | # ever sent via http, always redirecting to https:
56 | #
57 | # config :app, AppWeb.Endpoint,
58 | # force_ssl: [hsts: true]
59 | #
60 | # Check `Plug.SSL` for all available options in `force_ssl`.
61 |
62 | # ## Using releases
63 | #
64 | # If you are doing OTP releases, you need to instruct Phoenix
65 | # to start the server for all endpoints:
66 | #
67 | # config :phoenix, :serve_endpoints, true
68 | #
69 | # Alternatively, you can configure exactly which server to
70 | # start per endpoint:
71 | #
72 | # config :app, AppWeb.Endpoint, server: true
73 |
--------------------------------------------------------------------------------
/test/fixtures/installation.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "created",
3 | "installation": {
4 | "id": 89022,
5 | "account": {
6 | "login": "Cleop",
7 | "id": 16775804,
8 | "avatar_url": "https://avatars3.githubusercontent.com/u/16775804?v=4",
9 | "gravatar_id": "",
10 | "url": "https://api.github.com/users/Cleop",
11 | "html_url": "https://github.com/Cleop",
12 | "followers_url": "https://api.github.com/users/Cleop/followers",
13 | "following_url": "https://api.github.com/users/Cleop/following{/other_user}",
14 | "gists_url": "https://api.github.com/users/Cleop/gists{/gist_id}",
15 | "starred_url": "https://api.github.com/users/Cleop/starred{/owner}{/repo}",
16 | "subscriptions_url": "https://api.github.com/users/Cleop/subscriptions",
17 | "organizations_url": "https://api.github.com/users/Cleop/orgs",
18 | "repos_url": "https://api.github.com/users/Cleop/repos",
19 | "events_url": "https://api.github.com/users/Cleop/events{/privacy}",
20 | "received_events_url": "https://api.github.com/users/Cleop/received_events",
21 | "type": "User",
22 | "site_admin": false
23 | },
24 | "repository_selection": "selected",
25 | "access_tokens_url": "https://api.github.com/installations/89022/access_tokens",
26 | "repositories_url": "https://api.github.com/installation/repositories",
27 | "html_url": "https://github.com/settings/installations/89022",
28 | "app_id": 9161,
29 | "target_id": 16775804,
30 | "target_type": "User",
31 | "permissions": {
32 | "issues": "read",
33 | "metadata": "read"
34 | },
35 | "events": [
36 | "issues",
37 | "issue_comment"
38 | ],
39 | "created_at": 1519138393,
40 | "updated_at": 1519138393,
41 | "single_file_name": null
42 | },
43 | "repositories": [
44 | {
45 | "id": 81315472,
46 | "name": "react-cv",
47 | "full_name": "Cleop/react-cv"
48 | }
49 | ],
50 | "sender": {
51 | "login": "Cleop",
52 | "id": 16775804,
53 | "avatar_url": "https://avatars3.githubusercontent.com/u/16775804?v=4",
54 | "gravatar_id": "",
55 | "url": "https://api.github.com/users/Cleop",
56 | "html_url": "https://github.com/Cleop",
57 | "followers_url": "https://api.github.com/users/Cleop/followers",
58 | "following_url": "https://api.github.com/users/Cleop/following{/other_user}",
59 | "gists_url": "https://api.github.com/users/Cleop/gists{/gist_id}",
60 | "starred_url": "https://api.github.com/users/Cleop/starred{/owner}{/repo}",
61 | "subscriptions_url": "https://api.github.com/users/Cleop/subscriptions",
62 | "organizations_url": "https://api.github.com/users/Cleop/orgs",
63 | "repos_url": "https://api.github.com/users/Cleop/repos",
64 | "events_url": "https://api.github.com/users/Cleop/events{/privacy}",
65 | "received_events_url": "https://api.github.com/users/Cleop/received_events",
66 | "type": "User",
67 | "site_admin": false
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/test/fixtures/integration_installation.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "created",
3 | "installation": {
4 | "id": 108622,
5 | "account": {
6 | "login": "SimonLab",
7 | "id": 6057298,
8 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
9 | "gravatar_id": "",
10 | "url": "https://api.github.com/users/SimonLab",
11 | "html_url": "https://github.com/SimonLab",
12 | "followers_url": "https://api.github.com/users/SimonLab/followers",
13 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
14 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
15 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
16 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
17 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
18 | "repos_url": "https://api.github.com/users/SimonLab/repos",
19 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
20 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
21 | "type": "User",
22 | "site_admin": false
23 | },
24 | "repository_selection": "selected",
25 | "access_tokens_url": "https://api.github.com/installations/108622/access_tokens",
26 | "repositories_url": "https://api.github.com/installation/repositories",
27 | "html_url": "https://github.com/settings/installations/108622",
28 | "app_id": 9496,
29 | "target_id": 6057298,
30 | "target_type": "User",
31 | "permissions": {
32 | "issues": "write",
33 | "metadata": "read"
34 | },
35 | "events": [
36 | "issues",
37 | "issue_comment"
38 | ],
39 | "created_at": 1520927918,
40 | "updated_at": 1520927918,
41 | "single_file_name": null
42 | },
43 | "repositories": [
44 | {
45 | "id": 40962142,
46 | "name": "cv-2",
47 | "full_name": "SimonLab/cv-2"
48 | },
49 | {
50 | "id": 92847353,
51 | "name": "github_app",
52 | "full_name": "SimonLab/github_app"
53 | }
54 | ],
55 | "sender": {
56 | "login": "SimonLab",
57 | "id": 6057298,
58 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
59 | "gravatar_id": "",
60 | "url": "https://api.github.com/users/SimonLab",
61 | "html_url": "https://github.com/SimonLab",
62 | "followers_url": "https://api.github.com/users/SimonLab/followers",
63 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
64 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
65 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
66 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
67 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
68 | "repos_url": "https://api.github.com/users/SimonLab/repos",
69 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
70 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
71 | "type": "User",
72 | "site_admin": false
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/test/fixtures/installation_repositories.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "added",
3 | "installation": {
4 | "id": 122511,
5 | "account": {
6 | "login": "RobStallion",
7 | "id": 15571853,
8 | "avatar_url": "https://avatars2.githubusercontent.com/u/15571853?v=4",
9 | "gravatar_id": "",
10 | "url": "https://api.github.com/users/RobStallion",
11 | "html_url": "https://github.com/RobStallion",
12 | "followers_url": "https://api.github.com/users/RobStallion/followers",
13 | "following_url": "https://api.github.com/users/RobStallion/following{/other_user}",
14 | "gists_url": "https://api.github.com/users/RobStallion/gists{/gist_id}",
15 | "starred_url": "https://api.github.com/users/RobStallion/starred{/owner}{/repo}",
16 | "subscriptions_url": "https://api.github.com/users/RobStallion/subscriptions",
17 | "organizations_url": "https://api.github.com/users/RobStallion/orgs",
18 | "repos_url": "https://api.github.com/users/RobStallion/repos",
19 | "events_url": "https://api.github.com/users/RobStallion/events{/privacy}",
20 | "received_events_url": "https://api.github.com/users/RobStallion/received_events",
21 | "type": "User",
22 | "site_admin": false
23 | },
24 | "repository_selection": "selected",
25 | "access_tokens_url": "https://api.github.com/installations/122511/access_tokens",
26 | "repositories_url": "https://api.github.com/installation/repositories",
27 | "html_url": "https://github.com/settings/installations/122511",
28 | "app_id": 10460,
29 | "target_id": 15571853,
30 | "target_type": "User",
31 | "permissions": {
32 | "issues": "write",
33 | "pull_requests": "read",
34 | "metadata": "read"
35 | },
36 | "events": [
37 | "issues",
38 | "issue_comment",
39 | "pull_request"
40 | ],
41 | "created_at": 1522934666,
42 | "updated_at": 1522934666,
43 | "single_file_name": null
44 | },
45 | "repository_selection": "selected",
46 | "repositories_added": [
47 | {
48 | "id": 111834414,
49 | "name": "spawn_tic_tac_toe",
50 | "full_name": "RobStallion/spawn_tic_tac_toe"
51 | }
52 | ],
53 | "repositories_removed": [
54 |
55 | ],
56 | "sender": {
57 | "login": "RobStallion",
58 | "id": 15571853,
59 | "avatar_url": "https://avatars2.githubusercontent.com/u/15571853?v=4",
60 | "gravatar_id": "",
61 | "url": "https://api.github.com/users/RobStallion",
62 | "html_url": "https://github.com/RobStallion",
63 | "followers_url": "https://api.github.com/users/RobStallion/followers",
64 | "following_url": "https://api.github.com/users/RobStallion/following{/other_user}",
65 | "gists_url": "https://api.github.com/users/RobStallion/gists{/gist_id}",
66 | "starred_url": "https://api.github.com/users/RobStallion/starred{/owner}{/repo}",
67 | "subscriptions_url": "https://api.github.com/users/RobStallion/subscriptions",
68 | "organizations_url": "https://api.github.com/users/RobStallion/orgs",
69 | "repos_url": "https://api.github.com/users/RobStallion/repos",
70 | "events_url": "https://api.github.com/users/RobStallion/events{/privacy}",
71 | "received_events_url": "https://api.github.com/users/RobStallion/received_events",
72 | "type": "User",
73 | "site_admin": false
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/lib/app_web/controllers/github_api/http_client.ex:
--------------------------------------------------------------------------------
1 | defmodule AppWeb.GithubAPI.HTTPClient do
2 | @moduledoc """
3 | wrapper functions for the API Github App API
4 | """
5 | alias Poison.Parser, as: PP
6 | alias JOSE.JWK, as: JJ
7 | import Joken
8 |
9 | @media_type "application/vnd.github.machine-man-preview+json"
10 | @github_root "https://api.github.com"
11 |
12 | defp header(token) do
13 | ["Authorization": "token #{token}", "Accept": @media_type]
14 | end
15 |
16 | defp headerBearer(token) do
17 | ["Authorization": "Bearer #{token}", "Accept": @media_type]
18 | end
19 |
20 | def get_installation_token(installation_id) do
21 | private_key = System.get_env("PRIVATE_KEY")
22 | github_app_id = System.get_env("GITHUB_APP_ID")
23 | key = JJ.from_pem(private_key)
24 | my_token = %{
25 | iss: github_app_id,
26 | iat: DateTime.utc_now |> DateTime.to_unix,
27 | exp: (DateTime.utc_now |> DateTime.to_unix) + 100
28 | }
29 | |> token()
30 | |> sign(rs256(key))
31 | |> get_compact()
32 |
33 | "#{@github_root}/installations/#{installation_id}/access_tokens"
34 | |> HTTPoison.post!([], headerBearer(my_token))
35 | |> Map.fetch!(:body)
36 | |> PP.parse!
37 | |> Map.get("token")
38 | end
39 |
40 | defp get(token, url) do
41 | url
42 | |> HTTPoison.get!(header(token), [])
43 | |> Map.fetch!(:body)
44 | |> PP.parse!()
45 | end
46 |
47 | def get_issues(token, repo, page, issues) do
48 | data = "#{@github_root}/repos/#{repo}/issues?state=all&per_page=100&page=#{page}"
49 | |> HTTPoison.get!(header(token), [])
50 |
51 | body = PP.parse!(Map.fetch!(data, :body))
52 | issues = issues ++ body
53 |
54 | if last_page?(Map.fetch!(data, :headers)) do
55 | issues
56 | else
57 | get_issues(token, repo, page + 1, issues)
58 | end
59 | end
60 |
61 | def get_comments(token, repo, page, comments) do
62 | data = "#{@github_root}/repos/#{repo}/issues/comments?per_page=100&page=#{page}"
63 | |> HTTPoison.get!(header(token), [])
64 |
65 | body = Map.fetch!(data, :body)
66 | body = body
67 | |> PP.parse!
68 | comments = comments ++ body
69 |
70 | if last_page?(Map.fetch!(data, :headers)) do
71 | comments
72 | else
73 | get_comments(token, repo, page + 1, comments)
74 | end
75 | end
76 |
77 | def add_meta_table(repo_name, issue_id, content, token) do
78 | "#{@github_root}/repos/#{repo_name}/issues/#{issue_id}"
79 | |> HTTPoison.patch!(Poison.encode!(%{body: content}), header(token))
80 | end
81 |
82 | defp last_page?(headers) do
83 | links_header = Map.get(Enum.into(headers, %{}), "Link")
84 | if links_header do
85 | links_header
86 | |> String.split(",")
87 | |> Enum.map(fn l ->
88 | regex = Regex.named_captures(~r/<(?
.*)>;\s*rel=\"(?
.*)\"/, l)
89 | regex
90 | |> case do
91 | %{"link" => _link, "rel" => "next"} -> true
92 | _ -> nil
93 | end
94 | end)
95 | |> Enum.filter(&(not is_nil(&1)))
96 | |> Enum.empty?()
97 | else
98 | true
99 | end
100 | end
101 |
102 | def get_issue(token, repo, issue_number) do
103 | "#{@github_root}/repos/#{repo}/issues/#{issue_number}"
104 | |> HTTPoison.get!(header(token), [])
105 | |> Map.fetch!(:body)
106 | |> PP.parse!
107 | end
108 |
109 | end
110 |
--------------------------------------------------------------------------------
/test/app_web/helpers/issue_test.exs:
--------------------------------------------------------------------------------
1 | defmodule AppWeb.IssueHelperTest do
2 | use AppWeb.ConnCase
3 | alias App.Helpers.IssueHelper
4 |
5 | test "filter issue (exclue PRs)" do
6 | issues = [
7 | %{issue_id: 1, pull_request: false},
8 | %{issue_id: 2, pull_request: true}
9 | ]
10 |
11 | filtered_issues = IssueHelper.get_issues(issues)
12 | assert length(filtered_issues) == 1
13 | assert List.first(filtered_issues) == %{issue_id: 1, pull_request: false}
14 | end
15 |
16 | test "generate map of comments" do
17 | issues = [
18 | %{issue_id: 1, comments: [
19 | %{comment_id: "1", comment: "comment 1"},
20 | %{comment_id: "2", comment: "comment 2"}
21 | ]},
22 | %{issue_id: 2, comments: [
23 | %{comment_id: "3", comment: "comment 3"},
24 | %{comment_id: "4", comment: "comment 4"}
25 | ]}
26 | ]
27 | expected = %{
28 | "1" => "comment 1",
29 | "2" => "comment 2",
30 | "3" => "comment 3",
31 | "4" => "comment 4"
32 | }
33 |
34 | assert IssueHelper.get_map_comments(issues) == expected
35 | end
36 |
37 | test "create comment from issue description" do
38 | issues = [
39 | %{
40 | issue_id: "1",
41 | issue_author: %{id: 1},
42 | description: "new issue",
43 | inserted_at: "2018",
44 | updated_at: "2018",
45 | comments: []
46 | }
47 | ]
48 | expected = [
49 | %{
50 | issue_id: "1",
51 | issue_author: %{id: 1},
52 | description: "new issue",
53 | inserted_at: "2018",
54 | updated_at: "2018",
55 | comments: [
56 | %{
57 | comment_id: "1_1",
58 | comment: "new issue",
59 | inserted_at: "2018",
60 | updated_at: "2018",
61 | versions: [
62 | %{
63 | author: 1,
64 | inserted_at: "2018",
65 | updated_at: "2018"
66 | }
67 | ]
68 | }
69 | ]
70 | }
71 | ]
72 |
73 | assert IssueHelper.issues_as_comments(issues) == expected
74 | end
75 |
76 | test "generate s3 content file" do
77 | issue = %{
78 | issue_id: "1",
79 | issue_author: "me",
80 | description: "new issue",
81 | comments: [
82 | %{
83 | comment_id: "1_1",
84 | comment: "new issue",
85 | versions: [%{id: 1, author: "me"}]
86 | },
87 | %{
88 | comment_id: "2",
89 | versions: [%{id: 2, author: "me"}]
90 | }
91 | ]
92 | }
93 | comments = %{"1_1" => "comment 1", "2" => "comment 2"}
94 | expected = %{1 => "comment 1", 2 => "comment 2"}
95 | assert IssueHelper.get_s3_content(issue, comments) == expected
96 | end
97 |
98 | test "attach comments to issues" do
99 | comments = [
100 | %{"issue_url" => "url1", "comment_id" => 1},
101 | %{"issue_url" => "url1", "comment_id" => 2},
102 | %{"issue_url" => "url42", "comment_id" => 42}
103 | ]
104 | issues = [%{"url" => "url1"}, %{"url" => "url2"}, %{"url" => "url42"}]
105 | expected = [
106 | %{"url" => "url1", "comments" => [
107 | %{"comment_id" => 1, "issue_url" => "url1"},
108 | %{"comment_id" => 2, "issue_url" => "url1"}]},
109 | %{"url" => "url2", "comments" => []},
110 | %{"url" => "url42", "comments" => [
111 | %{"comment_id" => 42, "issue_url" => "url42"}
112 | ]}
113 | ]
114 |
115 | assert IssueHelper.attach_comments(issues, comments) == expected
116 | end
117 | end
118 |
--------------------------------------------------------------------------------
/lib/app_web/controllers/github_api/in_memory.ex:
--------------------------------------------------------------------------------
1 | defmodule AppWeb.GithubAPI.InMemory do
2 | alias App.{Repo, Issue}
3 | @moduledoc """
4 | mock of github api functions for tests
5 | """
6 | @comment %{
7 | "author_association" => "NONE",
8 | "body" => ":warning: @SimonLab, the pull request has a **merge conflict**.\nPlease resolve the conflict and reassign when ready :+1:\nThanks!\n\nAny questions, complaints, feedback, contributions?\n[](https://github.com/dwyl/dwylbot/issues \"Discuss your ideas/suggestions with us!\")\nIf you prefer, you can also send us anonymous feedback: https://dwyl-feedback.herokuapp.com/feedback/new\n",
9 | "created_at" => "2017-06-26T13:21:23Z",
10 | "html_url" => "https://github.com/SimonLab/github_app/pull/23#issuecomment-311057291",
11 | "id" => 311057291,
12 | "issue_url" => "https://api.github.com/repos/SimonLab/github_app/issues/18",
13 | "performed_via_github_app" => nil,
14 | "updated_at" => "2017-06-26T13:21:23Z",
15 | "url" => "https://api.github.com/repos/SimonLab/github_app/issues/comments/311057291",
16 | "user" => %{
17 | "avatar_url" => "https://avatars2.githubusercontent.com/in/2666?v=4",
18 | "events_url" => "https://api.github.com/users/simonlabapp%5Bbot%5D/events{/privacy}",
19 | "followers_url" => "https://api.github.com/users/simonlabapp%5Bbot%5D/followers",
20 | "following_url" => "https://api.github.com/users/simonlabapp%5Bbot%5D/following{/other_user}",
21 | "gists_url" => "https://api.github.com/users/simonlabapp%5Bbot%5D/gists{/gist_id}",
22 | "gravatar_id" => "",
23 | "html_url" => "https://github.com/apps/simonlabapp",
24 | "id" => 29067442,
25 | "login" => "simonlabapp[bot]",
26 | "organizations_url" => "https://api.github.com/users/simonlabapp%5Bbot%5D/orgs",
27 | }
28 | }
29 | @issue %{
30 | "assignee" => nil,
31 | "assignees" => [],
32 | "author_association" => "OWNER",
33 | "body" => "a",
34 | "closed_at" => "2017-06-16T14:55:39Z",
35 | "comments" => [@comment],
36 | "comments_url" => "https://api.github.com/repos/SimonLab/github_app/issues/18/comments",
37 | "created_at" => "2017-06-16T14:52:18Z",
38 | "events_url" => "https://api.github.com/repos/SimonLab/github_app/issues/18/events",
39 | "html_url" => "https://github.com/SimonLab/github_app/pull/18",
40 | "id" => 236506221,
41 | "labels" => [],
42 | "labels_url" => "https://api.github.com/repos/SimonLab/github_app/issues/18/labels{/name}",
43 | "locked" => false,
44 | "milestone" => nil,
45 | "number" => 18,
46 | "performed_via_github_app" => nil,
47 | "repository_url" => "https://api.github.com/repos/SimonLab/github_app",
48 | "state" => "closed",
49 | "title" => "aaaaaaa",
50 | "updated_at" => "2017-06-16T14:55:39Z",
51 | "url" => "https://api.github.com/repos/SimonLab/github_app/issues/18",
52 | "user" => %{
53 | "login" => "bob",
54 | "id" => 1,
55 | "html_url" => "/bob",
56 | "avatar_url" => "https://avatars2.githubusercontent.com/u/6057298?v=4",
57 | "events_url" => "https://api.github.com/users/SimonLab/events{/privacy}",
58 | }
59 | }
60 |
61 | @issue2 %{
62 | "assignee" => nil,
63 | "assignees" => [],
64 | "author_association" => "OWNER",
65 | "body" => "a",
66 | "closed_at" => "2017-06-16T14:55:39Z",
67 | "comments" => [@comment],
68 | "comments_url" => "https://api.github.com/repos/SimonLab/github_app/issues/18/comments",
69 | "created_at" => "2017-06-16T14:52:18Z",
70 | "events_url" => "https://api.github.com/repos/SimonLab/github_app/issues/18/events",
71 | "html_url" => "https://github.com/SimonLab/github_app/pull/18",
72 | "id" => 836506221,
73 | "labels" => [],
74 | "labels_url" => "https://api.github.com/repos/SimonLab/github_app/issues/18/labels{/name}",
75 | "locked" => false,
76 | "milestone" => nil,
77 | "number" => 18,
78 | "performed_via_github_app" => nil,
79 | "repository_url" => "https://api.github.com/repos/SimonLab/github_app",
80 | "state" => "closed",
81 | "title" => "aaaaaaa",
82 | "updated_at" => "2017-06-16T14:55:39Z",
83 | "url" => "https://api.github.com/repos/SimonLab/github_app/issues/18",
84 | "user" => %{
85 | "login" => "bob",
86 | "id" => 1,
87 | "html_url" => "/bob",
88 | "avatar_url" => "https://avatars2.githubusercontent.com/u/6057298?v=4",
89 | "events_url" => "https://api.github.com/users/SimonLab/events{/privacy}",
90 | }
91 | }
92 |
93 |
94 | def get_installation_token(_installation_id) do
95 | "token_installation_1234"
96 | end
97 |
98 | def get_issues(_token, _payload, _page, _issues) do
99 | case Repo.get_by(Issue, issue_id: 236506221) do
100 | nil ->
101 | [@issue]
102 | _ ->
103 | []
104 | end
105 | end
106 |
107 | def get_comments(_token, _repo, _page, _comments) do
108 | [@comment]
109 | end
110 |
111 | def add_meta_table(_repo_name, _issue_id, _content, _token) do
112 | :ok
113 | end
114 |
115 | def get_issue(_token, _repo, _issue_number) do
116 | @issue2
117 | end
118 | end
119 |
--------------------------------------------------------------------------------
/.credo.exs:
--------------------------------------------------------------------------------
1 | # This file contains the configuration for Credo and you are probably reading
2 | # this after creating it with `mix credo.gen.config`.
3 | #
4 | # If you find anything wrong or unclear in this file, please report an
5 | # issue on GitHub: https://github.com/rrrene/credo/issues
6 | #
7 | %{
8 | #
9 | # You can have as many configs as you like in the `configs:` field.
10 | configs: [
11 | %{
12 | #
13 | # Run any exec using `mix credo -C `. If no exec name is given
14 | # "default" is used.
15 | #
16 | name: "default",
17 | #
18 | # These are the files included in the analysis:
19 | files: %{
20 | #
21 | # You can give explicit globs or simply directories.
22 | # In the latter case `**/*.{ex,exs}` will be used.
23 | #
24 | included: ["lib/", "src/", "web/", "apps/"],
25 | excluded: [
26 | ~r"/_build/",
27 | ~r"/deps/",
28 | "lib/app.ex",
29 | "lib/app_web.ex",
30 | "lib/app_web/channels/",
31 | "lib/app/application.ex"]
32 | },
33 | #
34 | # If you create your own checks, you must specify the source files for
35 | # them here, so they can be loaded by Credo before running the analysis.
36 | #
37 | requires: [],
38 | #
39 | # If you want to enforce a style guide and need a more traditional linting
40 | # experience, you can change `strict` to `true` below:
41 | #
42 | strict: false,
43 | #
44 | # If you want to use uncolored output by default, you can change `color`
45 | # to `false` below:
46 | #
47 | color: true,
48 | #
49 | # You can customize the parameters of any check by adding a second element
50 | # to the tuple.
51 | #
52 | # To disable a check put `false` as second element:
53 | #
54 | # {Credo.Check.Design.DuplicatedCode, false}
55 | #
56 | checks: [
57 | #
58 | ## Consistency Checks
59 | #
60 | {Credo.Check.Consistency.ExceptionNames},
61 | {Credo.Check.Consistency.LineEndings},
62 | {Credo.Check.Consistency.ParameterPatternMatching},
63 | {Credo.Check.Consistency.SpaceAroundOperators},
64 | {Credo.Check.Consistency.SpaceInParentheses},
65 | {Credo.Check.Consistency.TabsOrSpaces},
66 |
67 | #
68 | ## Design Checks
69 | #
70 | # You can customize the priority of any check
71 | # Priority values are: `low, normal, high, higher`
72 | #
73 | {Credo.Check.Design.AliasUsage, priority: :low},
74 | # For some checks, you can also set other parameters
75 | #
76 | # If you don't want the `setup` and `test` macro calls in ExUnit tests
77 | # or the `schema` macro in Ecto schemas to trigger DuplicatedCode, just
78 | # set the `excluded_macros` parameter to `[:schema, :setup, :test]`.
79 | #
80 | {Credo.Check.Design.DuplicatedCode, excluded_macros: []},
81 | # You can also customize the exit_status of each check.
82 | # If you don't want TODO comments to cause `mix credo` to fail, just
83 | # set this value to 0 (zero).
84 | #
85 | {Credo.Check.Design.TagTODO, exit_status: 2},
86 | {Credo.Check.Design.TagFIXME},
87 |
88 | #
89 | ## Readability Checks
90 | #
91 | {Credo.Check.Readability.FunctionNames},
92 | {Credo.Check.Readability.LargeNumbers},
93 | {Credo.Check.Readability.MaxLineLength, priority: :low, max_length: 80},
94 | {Credo.Check.Readability.ModuleAttributeNames},
95 | {Credo.Check.Readability.ModuleDoc},
96 | {Credo.Check.Readability.ModuleNames},
97 | {Credo.Check.Readability.ParenthesesOnZeroArityDefs},
98 | {Credo.Check.Readability.ParenthesesInCondition},
99 | {Credo.Check.Readability.PredicateFunctionNames},
100 | {Credo.Check.Readability.PreferImplicitTry},
101 | {Credo.Check.Readability.RedundantBlankLines},
102 | {Credo.Check.Readability.StringSigils},
103 | {Credo.Check.Readability.TrailingBlankLine},
104 | {Credo.Check.Readability.TrailingWhiteSpace},
105 | {Credo.Check.Readability.VariableNames},
106 | {Credo.Check.Readability.Semicolons},
107 | {Credo.Check.Readability.SpaceAfterCommas},
108 |
109 | #
110 | ## Refactoring Opportunities
111 | #
112 | {Credo.Check.Refactor.DoubleBooleanNegation},
113 | {Credo.Check.Refactor.CondStatements},
114 | {Credo.Check.Refactor.CyclomaticComplexity},
115 | {Credo.Check.Refactor.FunctionArity},
116 | {Credo.Check.Refactor.LongQuoteBlocks},
117 | {Credo.Check.Refactor.MatchInCondition},
118 | {Credo.Check.Refactor.NegatedConditionsInUnless},
119 | {Credo.Check.Refactor.NegatedConditionsWithElse},
120 | {Credo.Check.Refactor.Nesting},
121 | {Credo.Check.Refactor.PipeChainStart,
122 | excluded_argument_types: [:atom, :binary, :fn, :keyword], excluded_functions: []},
123 | {Credo.Check.Refactor.UnlessWithElse},
124 |
125 | #
126 | ## Warnings
127 | #
128 | {Credo.Check.Warning.BoolOperationOnSameValues},
129 | {Credo.Check.Warning.ExpensiveEmptyEnumCheck},
130 | {Credo.Check.Warning.IExPry},
131 | {Credo.Check.Warning.IoInspect},
132 | {Credo.Check.Warning.LazyLogging},
133 | {Credo.Check.Warning.OperationOnSameValues},
134 | {Credo.Check.Warning.OperationWithConstantResult},
135 | {Credo.Check.Warning.UnusedEnumOperation},
136 | {Credo.Check.Warning.UnusedFileOperation},
137 | {Credo.Check.Warning.UnusedKeywordOperation},
138 | {Credo.Check.Warning.UnusedListOperation},
139 | {Credo.Check.Warning.UnusedPathOperation},
140 | {Credo.Check.Warning.UnusedRegexOperation},
141 | {Credo.Check.Warning.UnusedStringOperation},
142 | {Credo.Check.Warning.UnusedTupleOperation},
143 | {Credo.Check.Warning.RaiseInsideRescue},
144 |
145 | #
146 | # Controversial and experimental checks (opt-in, just remove `, false`)
147 | #
148 | {Credo.Check.Refactor.ABCSize, false},
149 | {Credo.Check.Refactor.AppendSingleItem, false},
150 | {Credo.Check.Refactor.VariableRebinding, false},
151 | {Credo.Check.Warning.MapGetUnsafePass, false},
152 | {Credo.Check.Consistency.MultiAliasImportRequireUse, false},
153 |
154 | #
155 | # Deprecated checks (these will be deleted after a grace period)
156 | #
157 | {Credo.Check.Readability.Specs, false}
158 |
159 | #
160 | # Custom checks can be created using `mix credo.gen.check`.
161 | #
162 | ]
163 | }
164 | ]
165 | }
166 |
--------------------------------------------------------------------------------
/test/fixtures/issue_reopened.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "reopened",
3 | "issue": {
4 | "url": "https://api.github.com/repos/Cleop/react-cv/issues/9",
5 | "repository_url": "https://api.github.com/repos/Cleop/react-cv",
6 | "labels_url": "https://api.github.com/repos/Cleop/react-cv/issues/9/labels{/name}",
7 | "comments_url": "https://api.github.com/repos/Cleop/react-cv/issues/9/comments",
8 | "events_url": "https://api.github.com/repos/Cleop/react-cv/issues/9/events",
9 | "html_url": "https://github.com/Cleop/react-cv/issues/9",
10 | "id": 236506221,
11 | "number": 8,
12 | "title": "Brand new issue so new...",
13 | "user": {
14 | "login": "Cleop",
15 | "id": 16775804,
16 | "avatar_url": "https://avatars3.githubusercontent.com/u/16775804?v=4",
17 | "gravatar_id": "",
18 | "url": "https://api.github.com/users/Cleop",
19 | "html_url": "https://github.com/Cleop",
20 | "followers_url": "https://api.github.com/users/Cleop/followers",
21 | "following_url": "https://api.github.com/users/Cleop/following{/other_user}",
22 | "gists_url": "https://api.github.com/users/Cleop/gists{/gist_id}",
23 | "starred_url": "https://api.github.com/users/Cleop/starred{/owner}{/repo}",
24 | "subscriptions_url": "https://api.github.com/users/Cleop/subscriptions",
25 | "organizations_url": "https://api.github.com/users/Cleop/orgs",
26 | "repos_url": "https://api.github.com/users/Cleop/repos",
27 | "events_url": "https://api.github.com/users/Cleop/events{/privacy}",
28 | "received_events_url": "https://api.github.com/users/Cleop/received_events",
29 | "type": "User",
30 | "site_admin": false
31 | },
32 | "labels": [
33 |
34 | ],
35 | "state": "closed",
36 | "locked": false,
37 | "assignee": null,
38 | "assignees": [
39 |
40 | ],
41 | "milestone": null,
42 | "comments": 0,
43 | "created_at": "2018-03-15T18:03:32Z",
44 | "updated_at": "2018-03-16T11:29:20Z",
45 | "closed_at": "2018-03-16T11:29:20Z",
46 | "author_association": "OWNER",
47 | "body": "I'm going to close the issue"
48 | },
49 | "repository": {
50 | "id": 81315472,
51 | "name": "react-cv",
52 | "full_name": "Cleop/react-cv",
53 | "owner": {
54 | "login": "Cleop",
55 | "id": 16775804,
56 | "avatar_url": "https://avatars3.githubusercontent.com/u/16775804?v=4",
57 | "gravatar_id": "",
58 | "url": "https://api.github.com/users/Cleop",
59 | "html_url": "https://github.com/Cleop",
60 | "followers_url": "https://api.github.com/users/Cleop/followers",
61 | "following_url": "https://api.github.com/users/Cleop/following{/other_user}",
62 | "gists_url": "https://api.github.com/users/Cleop/gists{/gist_id}",
63 | "starred_url": "https://api.github.com/users/Cleop/starred{/owner}{/repo}",
64 | "subscriptions_url": "https://api.github.com/users/Cleop/subscriptions",
65 | "organizations_url": "https://api.github.com/users/Cleop/orgs",
66 | "repos_url": "https://api.github.com/users/Cleop/repos",
67 | "events_url": "https://api.github.com/users/Cleop/events{/privacy}",
68 | "received_events_url": "https://api.github.com/users/Cleop/received_events",
69 | "type": "User",
70 | "site_admin": false
71 | },
72 | "private": false,
73 | "html_url": "https://github.com/Cleop/react-cv",
74 | "description": "Building a CV / portfolio site using React",
75 | "fork": false,
76 | "url": "https://api.github.com/repos/Cleop/react-cv",
77 | "forks_url": "https://api.github.com/repos/Cleop/react-cv/forks",
78 | "keys_url": "https://api.github.com/repos/Cleop/react-cv/keys{/key_id}",
79 | "collaborators_url": "https://api.github.com/repos/Cleop/react-cv/collaborators{/collaborator}",
80 | "teams_url": "https://api.github.com/repos/Cleop/react-cv/teams",
81 | "hooks_url": "https://api.github.com/repos/Cleop/react-cv/hooks",
82 | "issue_events_url": "https://api.github.com/repos/Cleop/react-cv/issues/events{/number}",
83 | "events_url": "https://api.github.com/repos/Cleop/react-cv/events",
84 | "assignees_url": "https://api.github.com/repos/Cleop/react-cv/assignees{/user}",
85 | "branches_url": "https://api.github.com/repos/Cleop/react-cv/branches{/branch}",
86 | "tags_url": "https://api.github.com/repos/Cleop/react-cv/tags",
87 | "blobs_url": "https://api.github.com/repos/Cleop/react-cv/git/blobs{/sha}",
88 | "git_tags_url": "https://api.github.com/repos/Cleop/react-cv/git/tags{/sha}",
89 | "git_refs_url": "https://api.github.com/repos/Cleop/react-cv/git/refs{/sha}",
90 | "trees_url": "https://api.github.com/repos/Cleop/react-cv/git/trees{/sha}",
91 | "statuses_url": "https://api.github.com/repos/Cleop/react-cv/statuses/{sha}",
92 | "languages_url": "https://api.github.com/repos/Cleop/react-cv/languages",
93 | "stargazers_url": "https://api.github.com/repos/Cleop/react-cv/stargazers",
94 | "contributors_url": "https://api.github.com/repos/Cleop/react-cv/contributors",
95 | "subscribers_url": "https://api.github.com/repos/Cleop/react-cv/subscribers",
96 | "subscription_url": "https://api.github.com/repos/Cleop/react-cv/subscription",
97 | "commits_url": "https://api.github.com/repos/Cleop/react-cv/commits{/sha}",
98 | "git_commits_url": "https://api.github.com/repos/Cleop/react-cv/git/commits{/sha}",
99 | "comments_url": "https://api.github.com/repos/Cleop/react-cv/comments{/number}",
100 | "issue_comment_url": "https://api.github.com/repos/Cleop/react-cv/issues/comments{/number}",
101 | "contents_url": "https://api.github.com/repos/Cleop/react-cv/contents/{+path}",
102 | "compare_url": "https://api.github.com/repos/Cleop/react-cv/compare/{base}...{head}",
103 | "merges_url": "https://api.github.com/repos/Cleop/react-cv/merges",
104 | "archive_url": "https://api.github.com/repos/Cleop/react-cv/{archive_format}{/ref}",
105 | "downloads_url": "https://api.github.com/repos/Cleop/react-cv/downloads",
106 | "issues_url": "https://api.github.com/repos/Cleop/react-cv/issues{/number}",
107 | "pulls_url": "https://api.github.com/repos/Cleop/react-cv/pulls{/number}",
108 | "milestones_url": "https://api.github.com/repos/Cleop/react-cv/milestones{/number}",
109 | "notifications_url": "https://api.github.com/repos/Cleop/react-cv/notifications{?since,all,participating}",
110 | "labels_url": "https://api.github.com/repos/Cleop/react-cv/labels{/name}",
111 | "releases_url": "https://api.github.com/repos/Cleop/react-cv/releases{/id}",
112 | "deployments_url": "https://api.github.com/repos/Cleop/react-cv/deployments",
113 | "created_at": "2017-02-08T10:04:41Z",
114 | "updated_at": "2017-02-23T08:50:57Z",
115 | "pushed_at": "2017-02-22T12:22:57Z",
116 | "git_url": "git://github.com/Cleop/react-cv.git",
117 | "ssh_url": "git@github.com:Cleop/react-cv.git",
118 | "clone_url": "https://github.com/Cleop/react-cv.git",
119 | "svn_url": "https://github.com/Cleop/react-cv",
120 | "homepage": "",
121 | "size": 69,
122 | "stargazers_count": 0,
123 | "watchers_count": 0,
124 | "language": "JavaScript",
125 | "has_issues": true,
126 | "has_projects": true,
127 | "has_downloads": true,
128 | "has_wiki": true,
129 | "has_pages": false,
130 | "forks_count": 0,
131 | "mirror_url": null,
132 | "archived": false,
133 | "open_issues_count": 6,
134 | "license": {
135 | "key": "mit",
136 | "name": "MIT License",
137 | "spdx_id": "MIT",
138 | "url": "https://api.github.com/licenses/mit"
139 | },
140 | "forks": 0,
141 | "open_issues": 6,
142 | "watchers": 0,
143 | "default_branch": "master"
144 | },
145 | "sender": {
146 | "login": "Cleop",
147 | "id": 16775804,
148 | "avatar_url": "https://avatars3.githubusercontent.com/u/16775804?v=4",
149 | "gravatar_id": "",
150 | "url": "https://api.github.com/users/Cleop",
151 | "html_url": "https://github.com/Cleop",
152 | "followers_url": "https://api.github.com/users/Cleop/followers",
153 | "following_url": "https://api.github.com/users/Cleop/following{/other_user}",
154 | "gists_url": "https://api.github.com/users/Cleop/gists{/gist_id}",
155 | "starred_url": "https://api.github.com/users/Cleop/starred{/owner}{/repo}",
156 | "subscriptions_url": "https://api.github.com/users/Cleop/subscriptions",
157 | "organizations_url": "https://api.github.com/users/Cleop/orgs",
158 | "repos_url": "https://api.github.com/users/Cleop/repos",
159 | "events_url": "https://api.github.com/users/Cleop/events{/privacy}",
160 | "received_events_url": "https://api.github.com/users/Cleop/received_events",
161 | "type": "User",
162 | "site_admin": false
163 | },
164 | "installation": {
165 | "id": 92614
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/test/fixtures/issue_closed.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "closed",
3 | "issue": {
4 | "url": "https://api.github.com/repos/Cleop/react-cv/issues/8",
5 | "repository_url": "https://api.github.com/repos/Cleop/react-cv",
6 | "labels_url": "https://api.github.com/repos/Cleop/react-cv/issues/8/labels{/name}",
7 | "comments_url": "https://api.github.com/repos/Cleop/react-cv/issues/8/comments",
8 | "events_url": "https://api.github.com/repos/Cleop/react-cv/issues/8/events",
9 | "html_url": "https://github.com/Cleop/react-cv/issues/8",
10 | "id": 236506221,
11 | "number": 8,
12 | "title": "Brand new issue so new...",
13 | "user": {
14 | "login": "Cleop",
15 | "id": 16775804,
16 | "avatar_url": "https://avatars3.githubusercontent.com/u/16775804?v=4",
17 | "gravatar_id": "",
18 | "url": "https://api.github.com/users/Cleop",
19 | "html_url": "https://github.com/Cleop",
20 | "followers_url": "https://api.github.com/users/Cleop/followers",
21 | "following_url": "https://api.github.com/users/Cleop/following{/other_user}",
22 | "gists_url": "https://api.github.com/users/Cleop/gists{/gist_id}",
23 | "starred_url": "https://api.github.com/users/Cleop/starred{/owner}{/repo}",
24 | "subscriptions_url": "https://api.github.com/users/Cleop/subscriptions",
25 | "organizations_url": "https://api.github.com/users/Cleop/orgs",
26 | "repos_url": "https://api.github.com/users/Cleop/repos",
27 | "events_url": "https://api.github.com/users/Cleop/events{/privacy}",
28 | "received_events_url": "https://api.github.com/users/Cleop/received_events",
29 | "type": "User",
30 | "site_admin": false
31 | },
32 | "labels": [
33 |
34 | ],
35 | "state": "closed",
36 | "locked": false,
37 | "assignee": null,
38 | "assignees": [
39 |
40 | ],
41 | "milestone": null,
42 | "comments": 3,
43 | "created_at": "2018-03-06T14:45:57Z",
44 | "updated_at": "2018-03-15T10:53:06Z",
45 | "closed_at": "2018-03-15T10:53:06Z",
46 | "author_association": "OWNER",
47 | "body": "This is the copy of the new issue. \r\nI've edited it!..."
48 | },
49 | "repository": {
50 | "id": 81315472,
51 | "name": "react-cv",
52 | "full_name": "Cleop/react-cv",
53 | "owner": {
54 | "login": "Cleop",
55 | "id": 16775804,
56 | "avatar_url": "https://avatars3.githubusercontent.com/u/16775804?v=4",
57 | "gravatar_id": "",
58 | "url": "https://api.github.com/users/Cleop",
59 | "html_url": "https://github.com/Cleop",
60 | "followers_url": "https://api.github.com/users/Cleop/followers",
61 | "following_url": "https://api.github.com/users/Cleop/following{/other_user}",
62 | "gists_url": "https://api.github.com/users/Cleop/gists{/gist_id}",
63 | "starred_url": "https://api.github.com/users/Cleop/starred{/owner}{/repo}",
64 | "subscriptions_url": "https://api.github.com/users/Cleop/subscriptions",
65 | "organizations_url": "https://api.github.com/users/Cleop/orgs",
66 | "repos_url": "https://api.github.com/users/Cleop/repos",
67 | "events_url": "https://api.github.com/users/Cleop/events{/privacy}",
68 | "received_events_url": "https://api.github.com/users/Cleop/received_events",
69 | "type": "User",
70 | "site_admin": false
71 | },
72 | "private": false,
73 | "html_url": "https://github.com/Cleop/react-cv",
74 | "description": "Building a CV / portfolio site using React",
75 | "fork": false,
76 | "url": "https://api.github.com/repos/Cleop/react-cv",
77 | "forks_url": "https://api.github.com/repos/Cleop/react-cv/forks",
78 | "keys_url": "https://api.github.com/repos/Cleop/react-cv/keys{/key_id}",
79 | "collaborators_url": "https://api.github.com/repos/Cleop/react-cv/collaborators{/collaborator}",
80 | "teams_url": "https://api.github.com/repos/Cleop/react-cv/teams",
81 | "hooks_url": "https://api.github.com/repos/Cleop/react-cv/hooks",
82 | "issue_events_url": "https://api.github.com/repos/Cleop/react-cv/issues/events{/number}",
83 | "events_url": "https://api.github.com/repos/Cleop/react-cv/events",
84 | "assignees_url": "https://api.github.com/repos/Cleop/react-cv/assignees{/user}",
85 | "branches_url": "https://api.github.com/repos/Cleop/react-cv/branches{/branch}",
86 | "tags_url": "https://api.github.com/repos/Cleop/react-cv/tags",
87 | "blobs_url": "https://api.github.com/repos/Cleop/react-cv/git/blobs{/sha}",
88 | "git_tags_url": "https://api.github.com/repos/Cleop/react-cv/git/tags{/sha}",
89 | "git_refs_url": "https://api.github.com/repos/Cleop/react-cv/git/refs{/sha}",
90 | "trees_url": "https://api.github.com/repos/Cleop/react-cv/git/trees{/sha}",
91 | "statuses_url": "https://api.github.com/repos/Cleop/react-cv/statuses/{sha}",
92 | "languages_url": "https://api.github.com/repos/Cleop/react-cv/languages",
93 | "stargazers_url": "https://api.github.com/repos/Cleop/react-cv/stargazers",
94 | "contributors_url": "https://api.github.com/repos/Cleop/react-cv/contributors",
95 | "subscribers_url": "https://api.github.com/repos/Cleop/react-cv/subscribers",
96 | "subscription_url": "https://api.github.com/repos/Cleop/react-cv/subscription",
97 | "commits_url": "https://api.github.com/repos/Cleop/react-cv/commits{/sha}",
98 | "git_commits_url": "https://api.github.com/repos/Cleop/react-cv/git/commits{/sha}",
99 | "comments_url": "https://api.github.com/repos/Cleop/react-cv/comments{/number}",
100 | "issue_comment_url": "https://api.github.com/repos/Cleop/react-cv/issues/comments{/number}",
101 | "contents_url": "https://api.github.com/repos/Cleop/react-cv/contents/{+path}",
102 | "compare_url": "https://api.github.com/repos/Cleop/react-cv/compare/{base}...{head}",
103 | "merges_url": "https://api.github.com/repos/Cleop/react-cv/merges",
104 | "archive_url": "https://api.github.com/repos/Cleop/react-cv/{archive_format}{/ref}",
105 | "downloads_url": "https://api.github.com/repos/Cleop/react-cv/downloads",
106 | "issues_url": "https://api.github.com/repos/Cleop/react-cv/issues{/number}",
107 | "pulls_url": "https://api.github.com/repos/Cleop/react-cv/pulls{/number}",
108 | "milestones_url": "https://api.github.com/repos/Cleop/react-cv/milestones{/number}",
109 | "notifications_url": "https://api.github.com/repos/Cleop/react-cv/notifications{?since,all,participating}",
110 | "labels_url": "https://api.github.com/repos/Cleop/react-cv/labels{/name}",
111 | "releases_url": "https://api.github.com/repos/Cleop/react-cv/releases{/id}",
112 | "deployments_url": "https://api.github.com/repos/Cleop/react-cv/deployments",
113 | "created_at": "2017-02-08T10:04:41Z",
114 | "updated_at": "2017-02-23T08:50:57Z",
115 | "pushed_at": "2017-02-22T12:22:57Z",
116 | "git_url": "git://github.com/Cleop/react-cv.git",
117 | "ssh_url": "git@github.com:Cleop/react-cv.git",
118 | "clone_url": "https://github.com/Cleop/react-cv.git",
119 | "svn_url": "https://github.com/Cleop/react-cv",
120 | "homepage": "",
121 | "size": 69,
122 | "stargazers_count": 0,
123 | "watchers_count": 0,
124 | "language": "JavaScript",
125 | "has_issues": true,
126 | "has_projects": true,
127 | "has_downloads": true,
128 | "has_wiki": true,
129 | "has_pages": false,
130 | "forks_count": 0,
131 | "mirror_url": null,
132 | "archived": false,
133 | "open_issues_count": 6,
134 | "license": {
135 | "key": "mit",
136 | "name": "MIT License",
137 | "spdx_id": "MIT",
138 | "url": "https://api.github.com/licenses/mit"
139 | },
140 | "forks": 0,
141 | "open_issues": 6,
142 | "watchers": 0,
143 | "default_branch": "master"
144 | },
145 | "sender": {
146 | "login": "Cleop",
147 | "id": 16775804,
148 | "avatar_url": "https://avatars3.githubusercontent.com/u/16775804?v=4",
149 | "gravatar_id": "",
150 | "url": "https://api.github.com/users/Cleop",
151 | "html_url": "https://github.com/Cleop",
152 | "followers_url": "https://api.github.com/users/Cleop/followers",
153 | "following_url": "https://api.github.com/users/Cleop/following{/other_user}",
154 | "gists_url": "https://api.github.com/users/Cleop/gists{/gist_id}",
155 | "starred_url": "https://api.github.com/users/Cleop/starred{/owner}{/repo}",
156 | "subscriptions_url": "https://api.github.com/users/Cleop/subscriptions",
157 | "organizations_url": "https://api.github.com/users/Cleop/orgs",
158 | "repos_url": "https://api.github.com/users/Cleop/repos",
159 | "events_url": "https://api.github.com/users/Cleop/events{/privacy}",
160 | "received_events_url": "https://api.github.com/users/Cleop/received_events",
161 | "type": "User",
162 | "site_admin": false
163 | },
164 | "installation": {
165 | "id": 92614
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/test/fixtures/issue_opened.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "opened",
3 | "issue": {
4 | "url": "https://api.github.com/repos/SimonLab/github_app/issues/33",
5 | "repository_url": "https://api.github.com/repos/SimonLab/github_app",
6 | "labels_url": "https://api.github.com/repos/SimonLab/github_app/issues/33/labels{/name}",
7 | "comments_url": "https://api.github.com/repos/SimonLab/github_app/issues/33/comments",
8 | "events_url": "https://api.github.com/repos/SimonLab/github_app/issues/33/events",
9 | "html_url": "https://github.com/SimonLab/github_app/issues/33",
10 | "id": 301369661,
11 | "number": 33,
12 | "title": "new issue",
13 | "user": {
14 | "login": "SimonLab",
15 | "id": 6057298,
16 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
17 | "gravatar_id": "",
18 | "url": "https://api.github.com/users/SimonLab",
19 | "html_url": "https://github.com/SimonLab",
20 | "followers_url": "https://api.github.com/users/SimonLab/followers",
21 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
22 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
23 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
24 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
25 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
26 | "repos_url": "https://api.github.com/users/SimonLab/repos",
27 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
28 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
29 | "type": "User",
30 | "site_admin": false
31 | },
32 | "labels": [
33 |
34 | ],
35 | "state": "open",
36 | "locked": false,
37 | "assignee": null,
38 | "assignees": [
39 |
40 | ],
41 | "milestone": null,
42 | "comments": 0,
43 | "created_at": "2018-03-01T11:25:14Z",
44 | "updated_at": "2018-03-01T11:25:14Z",
45 | "closed_at": null,
46 | "author_association": "OWNER",
47 | "body": "another issue"
48 | },
49 | "repository": {
50 | "id": 92847353,
51 | "name": "github_app",
52 | "full_name": "SimonLab/github_app",
53 | "owner": {
54 | "login": "SimonLab",
55 | "id": 6057298,
56 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
57 | "gravatar_id": "",
58 | "url": "https://api.github.com/users/SimonLab",
59 | "html_url": "https://github.com/SimonLab",
60 | "followers_url": "https://api.github.com/users/SimonLab/followers",
61 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
62 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
63 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
64 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
65 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
66 | "repos_url": "https://api.github.com/users/SimonLab/repos",
67 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
68 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
69 | "type": "User",
70 | "site_admin": false
71 | },
72 | "private": false,
73 | "html_url": "https://github.com/SimonLab/github_app",
74 | "description": "a Github App to play with integrations",
75 | "fork": false,
76 | "url": "https://api.github.com/repos/SimonLab/github_app",
77 | "forks_url": "https://api.github.com/repos/SimonLab/github_app/forks",
78 | "keys_url": "https://api.github.com/repos/SimonLab/github_app/keys{/key_id}",
79 | "collaborators_url": "https://api.github.com/repos/SimonLab/github_app/collaborators{/collaborator}",
80 | "teams_url": "https://api.github.com/repos/SimonLab/github_app/teams",
81 | "hooks_url": "https://api.github.com/repos/SimonLab/github_app/hooks",
82 | "issue_events_url": "https://api.github.com/repos/SimonLab/github_app/issues/events{/number}",
83 | "events_url": "https://api.github.com/repos/SimonLab/github_app/events",
84 | "assignees_url": "https://api.github.com/repos/SimonLab/github_app/assignees{/user}",
85 | "branches_url": "https://api.github.com/repos/SimonLab/github_app/branches{/branch}",
86 | "tags_url": "https://api.github.com/repos/SimonLab/github_app/tags",
87 | "blobs_url": "https://api.github.com/repos/SimonLab/github_app/git/blobs{/sha}",
88 | "git_tags_url": "https://api.github.com/repos/SimonLab/github_app/git/tags{/sha}",
89 | "git_refs_url": "https://api.github.com/repos/SimonLab/github_app/git/refs{/sha}",
90 | "trees_url": "https://api.github.com/repos/SimonLab/github_app/git/trees{/sha}",
91 | "statuses_url": "https://api.github.com/repos/SimonLab/github_app/statuses/{sha}",
92 | "languages_url": "https://api.github.com/repos/SimonLab/github_app/languages",
93 | "stargazers_url": "https://api.github.com/repos/SimonLab/github_app/stargazers",
94 | "contributors_url": "https://api.github.com/repos/SimonLab/github_app/contributors",
95 | "subscribers_url": "https://api.github.com/repos/SimonLab/github_app/subscribers",
96 | "subscription_url": "https://api.github.com/repos/SimonLab/github_app/subscription",
97 | "commits_url": "https://api.github.com/repos/SimonLab/github_app/commits{/sha}",
98 | "git_commits_url": "https://api.github.com/repos/SimonLab/github_app/git/commits{/sha}",
99 | "comments_url": "https://api.github.com/repos/SimonLab/github_app/comments{/number}",
100 | "issue_comment_url": "https://api.github.com/repos/SimonLab/github_app/issues/comments{/number}",
101 | "contents_url": "https://api.github.com/repos/SimonLab/github_app/contents/{+path}",
102 | "compare_url": "https://api.github.com/repos/SimonLab/github_app/compare/{base}...{head}",
103 | "merges_url": "https://api.github.com/repos/SimonLab/github_app/merges",
104 | "archive_url": "https://api.github.com/repos/SimonLab/github_app/{archive_format}{/ref}",
105 | "downloads_url": "https://api.github.com/repos/SimonLab/github_app/downloads",
106 | "issues_url": "https://api.github.com/repos/SimonLab/github_app/issues{/number}",
107 | "pulls_url": "https://api.github.com/repos/SimonLab/github_app/pulls{/number}",
108 | "milestones_url": "https://api.github.com/repos/SimonLab/github_app/milestones{/number}",
109 | "notifications_url": "https://api.github.com/repos/SimonLab/github_app/notifications{?since,all,participating}",
110 | "labels_url": "https://api.github.com/repos/SimonLab/github_app/labels{/name}",
111 | "releases_url": "https://api.github.com/repos/SimonLab/github_app/releases{/id}",
112 | "deployments_url": "https://api.github.com/repos/SimonLab/github_app/deployments",
113 | "created_at": "2017-05-30T15:26:26Z",
114 | "updated_at": "2017-05-30T15:36:26Z",
115 | "pushed_at": "2017-06-26T14:03:32Z",
116 | "git_url": "git://github.com/SimonLab/github_app.git",
117 | "ssh_url": "git@github.com:SimonLab/github_app.git",
118 | "clone_url": "https://github.com/SimonLab/github_app.git",
119 | "svn_url": "https://github.com/SimonLab/github_app",
120 | "homepage": null,
121 | "size": 63,
122 | "stargazers_count": 0,
123 | "watchers_count": 0,
124 | "language": "Elixir",
125 | "has_issues": true,
126 | "has_projects": true,
127 | "has_downloads": true,
128 | "has_wiki": true,
129 | "has_pages": false,
130 | "forks_count": 0,
131 | "mirror_url": null,
132 | "archived": false,
133 | "open_issues_count": 12,
134 | "license": null,
135 | "forks": 0,
136 | "open_issues": 12,
137 | "watchers": 0,
138 | "default_branch": "b1"
139 | },
140 | "sender": {
141 | "login": "SimonLab",
142 | "id": 6057298,
143 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
144 | "gravatar_id": "",
145 | "url": "https://api.github.com/users/SimonLab",
146 | "html_url": "https://github.com/SimonLab",
147 | "followers_url": "https://api.github.com/users/SimonLab/followers",
148 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
149 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
150 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
151 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
152 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
153 | "repos_url": "https://api.github.com/users/SimonLab/repos",
154 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
155 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
156 | "type": "User",
157 | "site_admin": false
158 | },
159 | "installation": {
160 | "id": 92693
161 | }
162 | }
163 |
--------------------------------------------------------------------------------
/test/fixtures/issue_edited.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "edited",
3 | "issue": {
4 | "url": "https://api.github.com/repos/SimonLab/github_app/issues/35",
5 | "repository_url": "https://api.github.com/repos/SimonLab/github_app",
6 | "labels_url": "https://api.github.com/repos/SimonLab/github_app/issues/35/labels{/name}",
7 | "comments_url": "https://api.github.com/repos/SimonLab/github_app/issues/35/comments",
8 | "events_url": "https://api.github.com/repos/SimonLab/github_app/issues/35/events",
9 | "html_url": "https://github.com/SimonLab/github_app/issues/35",
10 | "id": 301369661,
11 | "number": 35,
12 | "title": "How to",
13 | "user": {
14 | "login": "SimonLab",
15 | "id": 6057298,
16 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
17 | "gravatar_id": "",
18 | "url": "https://api.github.com/users/SimonLab",
19 | "html_url": "https://github.com/SimonLab",
20 | "followers_url": "https://api.github.com/users/SimonLab/followers",
21 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
22 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
23 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
24 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
25 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
26 | "repos_url": "https://api.github.com/users/SimonLab/repos",
27 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
28 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
29 | "type": "User",
30 | "site_admin": false
31 | },
32 | "labels": [
33 |
34 | ],
35 | "state": "open",
36 | "locked": false,
37 | "assignee": null,
38 | "assignees": [
39 |
40 | ],
41 | "milestone": null,
42 | "comments": 0,
43 | "created_at": "2018-03-05T08:35:55Z",
44 | "updated_at": "2018-03-05T08:40:30Z",
45 | "closed_at": null,
46 | "author_association": "OWNER",
47 | "body": "save edited issue??"
48 | },
49 | "changes": {
50 | "body": {
51 | "from": "save edited issue?"
52 | }
53 | },
54 | "repository": {
55 | "id": 92847353,
56 | "name": "github_app",
57 | "full_name": "SimonLab/github_app",
58 | "owner": {
59 | "login": "SimonLab",
60 | "id": 6057298,
61 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
62 | "gravatar_id": "",
63 | "url": "https://api.github.com/users/SimonLab",
64 | "html_url": "https://github.com/SimonLab",
65 | "followers_url": "https://api.github.com/users/SimonLab/followers",
66 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
67 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
68 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
69 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
70 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
71 | "repos_url": "https://api.github.com/users/SimonLab/repos",
72 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
73 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
74 | "type": "User",
75 | "site_admin": false
76 | },
77 | "private": false,
78 | "html_url": "https://github.com/SimonLab/github_app",
79 | "description": "a Github App to play with integrations",
80 | "fork": false,
81 | "url": "https://api.github.com/repos/SimonLab/github_app",
82 | "forks_url": "https://api.github.com/repos/SimonLab/github_app/forks",
83 | "keys_url": "https://api.github.com/repos/SimonLab/github_app/keys{/key_id}",
84 | "collaborators_url": "https://api.github.com/repos/SimonLab/github_app/collaborators{/collaborator}",
85 | "teams_url": "https://api.github.com/repos/SimonLab/github_app/teams",
86 | "hooks_url": "https://api.github.com/repos/SimonLab/github_app/hooks",
87 | "issue_events_url": "https://api.github.com/repos/SimonLab/github_app/issues/events{/number}",
88 | "events_url": "https://api.github.com/repos/SimonLab/github_app/events",
89 | "assignees_url": "https://api.github.com/repos/SimonLab/github_app/assignees{/user}",
90 | "branches_url": "https://api.github.com/repos/SimonLab/github_app/branches{/branch}",
91 | "tags_url": "https://api.github.com/repos/SimonLab/github_app/tags",
92 | "blobs_url": "https://api.github.com/repos/SimonLab/github_app/git/blobs{/sha}",
93 | "git_tags_url": "https://api.github.com/repos/SimonLab/github_app/git/tags{/sha}",
94 | "git_refs_url": "https://api.github.com/repos/SimonLab/github_app/git/refs{/sha}",
95 | "trees_url": "https://api.github.com/repos/SimonLab/github_app/git/trees{/sha}",
96 | "statuses_url": "https://api.github.com/repos/SimonLab/github_app/statuses/{sha}",
97 | "languages_url": "https://api.github.com/repos/SimonLab/github_app/languages",
98 | "stargazers_url": "https://api.github.com/repos/SimonLab/github_app/stargazers",
99 | "contributors_url": "https://api.github.com/repos/SimonLab/github_app/contributors",
100 | "subscribers_url": "https://api.github.com/repos/SimonLab/github_app/subscribers",
101 | "subscription_url": "https://api.github.com/repos/SimonLab/github_app/subscription",
102 | "commits_url": "https://api.github.com/repos/SimonLab/github_app/commits{/sha}",
103 | "git_commits_url": "https://api.github.com/repos/SimonLab/github_app/git/commits{/sha}",
104 | "comments_url": "https://api.github.com/repos/SimonLab/github_app/comments{/number}",
105 | "issue_comment_url": "https://api.github.com/repos/SimonLab/github_app/issues/comments{/number}",
106 | "contents_url": "https://api.github.com/repos/SimonLab/github_app/contents/{+path}",
107 | "compare_url": "https://api.github.com/repos/SimonLab/github_app/compare/{base}...{head}",
108 | "merges_url": "https://api.github.com/repos/SimonLab/github_app/merges",
109 | "archive_url": "https://api.github.com/repos/SimonLab/github_app/{archive_format}{/ref}",
110 | "downloads_url": "https://api.github.com/repos/SimonLab/github_app/downloads",
111 | "issues_url": "https://api.github.com/repos/SimonLab/github_app/issues{/number}",
112 | "pulls_url": "https://api.github.com/repos/SimonLab/github_app/pulls{/number}",
113 | "milestones_url": "https://api.github.com/repos/SimonLab/github_app/milestones{/number}",
114 | "notifications_url": "https://api.github.com/repos/SimonLab/github_app/notifications{?since,all,participating}",
115 | "labels_url": "https://api.github.com/repos/SimonLab/github_app/labels{/name}",
116 | "releases_url": "https://api.github.com/repos/SimonLab/github_app/releases{/id}",
117 | "deployments_url": "https://api.github.com/repos/SimonLab/github_app/deployments",
118 | "created_at": "2017-05-30T15:26:26Z",
119 | "updated_at": "2017-05-30T15:36:26Z",
120 | "pushed_at": "2017-06-26T14:03:32Z",
121 | "git_url": "git://github.com/SimonLab/github_app.git",
122 | "ssh_url": "git@github.com:SimonLab/github_app.git",
123 | "clone_url": "https://github.com/SimonLab/github_app.git",
124 | "svn_url": "https://github.com/SimonLab/github_app",
125 | "homepage": null,
126 | "size": 63,
127 | "stargazers_count": 0,
128 | "watchers_count": 0,
129 | "language": "Elixir",
130 | "has_issues": true,
131 | "has_projects": true,
132 | "has_downloads": true,
133 | "has_wiki": true,
134 | "has_pages": false,
135 | "forks_count": 0,
136 | "mirror_url": null,
137 | "archived": false,
138 | "open_issues_count": 14,
139 | "license": null,
140 | "forks": 0,
141 | "open_issues": 14,
142 | "watchers": 0,
143 | "default_branch": "b1"
144 | },
145 | "sender": {
146 | "login": "SimonLab",
147 | "id": 6057298,
148 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
149 | "gravatar_id": "",
150 | "url": "https://api.github.com/users/SimonLab",
151 | "html_url": "https://github.com/SimonLab",
152 | "followers_url": "https://api.github.com/users/SimonLab/followers",
153 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
154 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
155 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
156 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
157 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
158 | "repos_url": "https://api.github.com/users/SimonLab/repos",
159 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
160 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
161 | "type": "User",
162 | "site_admin": false
163 | },
164 | "installation": {
165 | "id": 92693
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/test/fixtures/issue_title_edited.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "edited",
3 | "issue": {
4 | "url": "https://api.github.com/repos/SimonLab/github_app/issues/35",
5 | "repository_url": "https://api.github.com/repos/SimonLab/github_app",
6 | "labels_url": "https://api.github.com/repos/SimonLab/github_app/issues/35/labels{/name}",
7 | "comments_url": "https://api.github.com/repos/SimonLab/github_app/issues/35/comments",
8 | "events_url": "https://api.github.com/repos/SimonLab/github_app/issues/35/events",
9 | "html_url": "https://github.com/SimonLab/github_app/issues/35",
10 | "id": 301369661,
11 | "number": 35,
12 | "title": "How to...",
13 | "user": {
14 | "login": "SimonLab",
15 | "id": 6057298,
16 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
17 | "gravatar_id": "",
18 | "url": "https://api.github.com/users/SimonLab",
19 | "html_url": "https://github.com/SimonLab",
20 | "followers_url": "https://api.github.com/users/SimonLab/followers",
21 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
22 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
23 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
24 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
25 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
26 | "repos_url": "https://api.github.com/users/SimonLab/repos",
27 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
28 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
29 | "type": "User",
30 | "site_admin": false
31 | },
32 | "labels": [
33 |
34 | ],
35 | "state": "open",
36 | "locked": false,
37 | "assignee": null,
38 | "assignees": [
39 |
40 | ],
41 | "milestone": null,
42 | "comments": 0,
43 | "created_at": "2018-03-05T08:35:55Z",
44 | "updated_at": "2018-03-05T09:41:44Z",
45 | "closed_at": null,
46 | "author_association": "OWNER",
47 | "body": "save edited issue??"
48 | },
49 | "changes": {
50 | "title": {
51 | "from": "How to"
52 | }
53 | },
54 | "repository": {
55 | "id": 92847353,
56 | "name": "github_app",
57 | "full_name": "SimonLab/github_app",
58 | "owner": {
59 | "login": "SimonLab",
60 | "id": 6057298,
61 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
62 | "gravatar_id": "",
63 | "url": "https://api.github.com/users/SimonLab",
64 | "html_url": "https://github.com/SimonLab",
65 | "followers_url": "https://api.github.com/users/SimonLab/followers",
66 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
67 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
68 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
69 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
70 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
71 | "repos_url": "https://api.github.com/users/SimonLab/repos",
72 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
73 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
74 | "type": "User",
75 | "site_admin": false
76 | },
77 | "private": false,
78 | "html_url": "https://github.com/SimonLab/github_app",
79 | "description": "a Github App to play with integrations",
80 | "fork": false,
81 | "url": "https://api.github.com/repos/SimonLab/github_app",
82 | "forks_url": "https://api.github.com/repos/SimonLab/github_app/forks",
83 | "keys_url": "https://api.github.com/repos/SimonLab/github_app/keys{/key_id}",
84 | "collaborators_url": "https://api.github.com/repos/SimonLab/github_app/collaborators{/collaborator}",
85 | "teams_url": "https://api.github.com/repos/SimonLab/github_app/teams",
86 | "hooks_url": "https://api.github.com/repos/SimonLab/github_app/hooks",
87 | "issue_events_url": "https://api.github.com/repos/SimonLab/github_app/issues/events{/number}",
88 | "events_url": "https://api.github.com/repos/SimonLab/github_app/events",
89 | "assignees_url": "https://api.github.com/repos/SimonLab/github_app/assignees{/user}",
90 | "branches_url": "https://api.github.com/repos/SimonLab/github_app/branches{/branch}",
91 | "tags_url": "https://api.github.com/repos/SimonLab/github_app/tags",
92 | "blobs_url": "https://api.github.com/repos/SimonLab/github_app/git/blobs{/sha}",
93 | "git_tags_url": "https://api.github.com/repos/SimonLab/github_app/git/tags{/sha}",
94 | "git_refs_url": "https://api.github.com/repos/SimonLab/github_app/git/refs{/sha}",
95 | "trees_url": "https://api.github.com/repos/SimonLab/github_app/git/trees{/sha}",
96 | "statuses_url": "https://api.github.com/repos/SimonLab/github_app/statuses/{sha}",
97 | "languages_url": "https://api.github.com/repos/SimonLab/github_app/languages",
98 | "stargazers_url": "https://api.github.com/repos/SimonLab/github_app/stargazers",
99 | "contributors_url": "https://api.github.com/repos/SimonLab/github_app/contributors",
100 | "subscribers_url": "https://api.github.com/repos/SimonLab/github_app/subscribers",
101 | "subscription_url": "https://api.github.com/repos/SimonLab/github_app/subscription",
102 | "commits_url": "https://api.github.com/repos/SimonLab/github_app/commits{/sha}",
103 | "git_commits_url": "https://api.github.com/repos/SimonLab/github_app/git/commits{/sha}",
104 | "comments_url": "https://api.github.com/repos/SimonLab/github_app/comments{/number}",
105 | "issue_comment_url": "https://api.github.com/repos/SimonLab/github_app/issues/comments{/number}",
106 | "contents_url": "https://api.github.com/repos/SimonLab/github_app/contents/{+path}",
107 | "compare_url": "https://api.github.com/repos/SimonLab/github_app/compare/{base}...{head}",
108 | "merges_url": "https://api.github.com/repos/SimonLab/github_app/merges",
109 | "archive_url": "https://api.github.com/repos/SimonLab/github_app/{archive_format}{/ref}",
110 | "downloads_url": "https://api.github.com/repos/SimonLab/github_app/downloads",
111 | "issues_url": "https://api.github.com/repos/SimonLab/github_app/issues{/number}",
112 | "pulls_url": "https://api.github.com/repos/SimonLab/github_app/pulls{/number}",
113 | "milestones_url": "https://api.github.com/repos/SimonLab/github_app/milestones{/number}",
114 | "notifications_url": "https://api.github.com/repos/SimonLab/github_app/notifications{?since,all,participating}",
115 | "labels_url": "https://api.github.com/repos/SimonLab/github_app/labels{/name}",
116 | "releases_url": "https://api.github.com/repos/SimonLab/github_app/releases{/id}",
117 | "deployments_url": "https://api.github.com/repos/SimonLab/github_app/deployments",
118 | "created_at": "2017-05-30T15:26:26Z",
119 | "updated_at": "2017-05-30T15:36:26Z",
120 | "pushed_at": "2017-06-26T14:03:32Z",
121 | "git_url": "git://github.com/SimonLab/github_app.git",
122 | "ssh_url": "git@github.com:SimonLab/github_app.git",
123 | "clone_url": "https://github.com/SimonLab/github_app.git",
124 | "svn_url": "https://github.com/SimonLab/github_app",
125 | "homepage": null,
126 | "size": 63,
127 | "stargazers_count": 0,
128 | "watchers_count": 0,
129 | "language": "Elixir",
130 | "has_issues": true,
131 | "has_projects": true,
132 | "has_downloads": true,
133 | "has_wiki": true,
134 | "has_pages": false,
135 | "forks_count": 0,
136 | "mirror_url": null,
137 | "archived": false,
138 | "open_issues_count": 14,
139 | "license": null,
140 | "forks": 0,
141 | "open_issues": 14,
142 | "watchers": 0,
143 | "default_branch": "b1"
144 | },
145 | "sender": {
146 | "login": "SimonLab",
147 | "id": 6057298,
148 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
149 | "gravatar_id": "",
150 | "url": "https://api.github.com/users/SimonLab",
151 | "html_url": "https://github.com/SimonLab",
152 | "followers_url": "https://api.github.com/users/SimonLab/followers",
153 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
154 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
155 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
156 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
157 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
158 | "repos_url": "https://api.github.com/users/SimonLab/repos",
159 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
160 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
161 | "type": "User",
162 | "site_admin": false
163 | },
164 | "installation": {
165 | "id": 92693
166 | }
167 | }
168 |
--------------------------------------------------------------------------------
/mix.lock:
--------------------------------------------------------------------------------
1 | %{
2 | "base64url": {:hex, :base64url, "0.0.1", "36a90125f5948e3afd7be97662a1504b934dd5dac78451ca6e9abf85a10286be", [:rebar], []},
3 | "bunt": {:hex, :bunt, "0.2.0", "951c6e801e8b1d2cbe58ebbd3e616a869061ddadcc4863d0a2182541acae9a38", [:mix], []},
4 | "certifi": {:hex, :certifi, "2.0.0", "a0c0e475107135f76b8c1d5bc7efb33cd3815cb3cf3dea7aefdd174dabead064", [:rebar3], []},
5 | "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], []},
6 | "connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], []},
7 | "cowboy": {:hex, :cowboy, "2.6.1", "f2e06f757c337b3b311f9437e6e072b678fcd71545a7b2865bdaa154d078593f", [:rebar3], [{:cowlib, "~> 2.7.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
8 | "cowlib": {:hex, :cowlib, "2.7.0", "3ef16e77562f9855a2605900cedb15c1462d76fb1be6a32fc3ae91973ee543d2", [:rebar3], [], "hexpm"},
9 | "credo": {:hex, :credo, "0.8.10", "261862bb7363247762e1063713bb85df2bbd84af8d8610d1272cd9c1943bba63", [:mix], [{:bunt, "~> 0.2.0", [hex: :bunt, optional: false]}]},
10 | "db_connection": {:hex, :db_connection, "2.0.3", "b4e8aa43c100e16f122ccd6798cd51c48c79fd391c39d411f42b3cd765daccb0", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"},
11 | "decimal": {:hex, :decimal, "1.6.0", "bfd84d90ff966e1f5d4370bdd3943432d8f65f07d3bab48001aebd7030590dcc", [:mix], [], "hexpm"},
12 | "earmark": {:hex, :earmark, "1.2.4", "99b637c62a4d65a20a9fb674b8cffb8baa771c04605a80c911c4418c69b75439", [:mix], []},
13 | "ecto": {:hex, :ecto, "3.0.4", "5d0e2b89baaa03eac37ec49f9018c39a4e2fb6501dc3ff5a839de742e171a09f", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:poison, "~> 2.2 or ~> 3.0", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm"},
14 | "ecto_sql": {:hex, :ecto_sql, "3.0.3", "dd17f2401a69bb2ec91d5564bd259ad0bc63ee32c2cb2e616d04f1559801dba6", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.0.4", [hex: :ecto, repo: "hexpm", optional: false]}, {:mariaex, "~> 0.9.1", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.14.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.2.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"},
15 | "ex_aws": {:hex, :ex_aws, "2.0.2", "8df2f96f58624a399abe5a0ce26db648ee848aca6393b9c65c939ece9ac07bfa", [:mix], [{:configparser_ex, "~> 2.0", [hex: :configparser_ex, optional: true]}, {:hackney, "1.6.3 or 1.6.5 or 1.7.1 or 1.8.6 or ~> 1.9", [hex: :hackney, optional: true]}, {:jsx, "~> 2.8", [hex: :jsx, optional: true]}, {:poison, ">= 1.2.0", [hex: :poison, optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, optional: true]}, {:xml_builder, "~> 0.1.0", [hex: :xml_builder, optional: true]}]},
16 | "ex_aws_s3": {:hex, :ex_aws_s3, "2.0.0", "404e7951820d79774cb67b76a1576ad955dcc44773be5a75fbb0b5bc4278a524", [:mix], [{:ex_aws, "~> 2.0.0", [hex: :ex_aws, optional: false]}]},
17 | "excoveralls": {:hex, :excoveralls, "0.8.1", "0bbf67f22c7dbf7503981d21a5eef5db8bbc3cb86e70d3798e8c802c74fa5e27", [:mix], [{:exjsx, ">= 3.0.0", [hex: :exjsx, optional: false]}, {:hackney, ">= 0.12.0", [hex: :hackney, optional: false]}]},
18 | "exjsx": {:hex, :exjsx, "4.0.0", "60548841e0212df401e38e63c0078ec57b33e7ea49b032c796ccad8cde794b5c", [:mix], [{:jsx, "~> 2.8.0", [hex: :jsx, optional: false]}]},
19 | "file_system": {:hex, :file_system, "0.2.4", "f0bdda195c0e46e987333e986452ec523aed21d784189144f647c43eaf307064", [:mix], []},
20 | "gettext": {:hex, :gettext, "0.15.0", "40a2b8ce33a80ced7727e36768499fc9286881c43ebafccae6bab731e2b2b8ce", [:mix], []},
21 | "hackney": {:hex, :hackney, "1.11.0", "4951ee019df102492dabba66a09e305f61919a8a183a7860236c0fde586134b6", [:rebar3], [{:certifi, "2.0.0", [hex: :certifi, optional: false]}, {:idna, "5.1.0", [hex: :idna, optional: false]}, {:metrics, "1.0.1", [hex: :metrics, optional: false]}, {:mimerl, "1.0.2", [hex: :mimerl, optional: false]}, {:ssl_verify_fun, "1.1.1", [hex: :ssl_verify_fun, optional: false]}]},
22 | "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.3.0", "f005ad692b717691203f940c686208aa3d8ffd9dd4bb3699240096a51fa9564e", [:mix], [{:mochiweb, "~> 2.15", [hex: :mochiweb, optional: false]}]},
23 | "httpoison": {:hex, :httpoison, "1.0.0", "1f02f827148d945d40b24f0b0a89afe40bfe037171a6cf70f2486976d86921cd", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, optional: false]}]},
24 | "idna": {:hex, :idna, "5.1.0", "d72b4effeb324ad5da3cab1767cb16b17939004e789d8c0ad5b70f3cea20c89a", [:rebar3], [{:unicode_util_compat, "0.3.1", [hex: :unicode_util_compat, optional: false]}]},
25 | "jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm"},
26 | "joken": {:hex, :joken, "1.5.0", "42a0953e80bd933fc98a0874e156771f78bf0e92abe6c3a9c22feb6da28efb0b", [:mix], [{:jose, "~> 1.8", [hex: :jose, optional: false]}, {:plug, "~> 1.0", [hex: :plug, optional: true]}, {:poison, "~> 1.5 or ~> 2.0 or ~> 3.0", [hex: :poison, optional: true]}]},
27 | "jose": {:hex, :jose, "1.8.4", "7946d1e5c03a76ac9ef42a6e6a20001d35987afd68c2107bcd8f01a84e75aa73", [:mix, :rebar3], [{:base64url, "~> 0.0.1", [hex: :base64url, optional: false]}]},
28 | "jsx": {:hex, :jsx, "2.8.3", "a05252d381885240744d955fbe3cf810504eb2567164824e19303ea59eef62cf", [:mix, :rebar3], []},
29 | "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], []},
30 | "mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm"},
31 | "mimerl": {:hex, :mimerl, "1.0.2", "993f9b0e084083405ed8252b99460c4f0563e41729ab42d9074fd5e52439be88", [:rebar3], []},
32 | "mochiweb": {:hex, :mochiweb, "2.15.0", "e1daac474df07651e5d17cc1e642c4069c7850dc4508d3db7263a0651330aacc", [:rebar3], []},
33 | "phoenix": {:hex, :phoenix, "1.4.0", "56fe9a809e0e735f3e3b9b31c1b749d4b436e466d8da627b8d82f90eaae714d2", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm"},
34 | "phoenix_ecto": {:hex, :phoenix_ecto, "4.0.0", "c43117a136e7399ea04ecaac73f8f23ee0ffe3e07acfcb8062fe5f4c9f0f6531", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.9", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
35 | "phoenix_html": {:hex, :phoenix_html, "2.12.0", "1fb3c2e48b4b66d75564d8d63df6d53655469216d6b553e7e14ced2b46f97622", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
36 | "phoenix_live_reload": {:hex, :phoenix_live_reload, "1.1.3", "1d178429fc8950b12457d09c6afec247bfe1fcb6f36209e18fbb0221bdfe4d41", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, optional: false]}, {:phoenix, "~> 1.0 or ~> 1.2 or ~> 1.3", [hex: :phoenix, optional: false]}]},
37 | "phoenix_pubsub": {:hex, :phoenix_pubsub, "1.1.1", "6668d787e602981f24f17a5fbb69cc98f8ab085114ebfac6cc36e10a90c8e93c", [:mix], [], "hexpm"},
38 | "plug": {:hex, :plug, "1.7.1", "8516d565fb84a6a8b2ca722e74e2cd25ca0fc9d64f364ec9dbec09d33eb78ccd", [:mix], [{:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}], "hexpm"},
39 | "plug_cowboy": {:hex, :plug_cowboy, "2.0.0", "ab0c92728f2ba43c544cce85f0f220d8d30fc0c90eaa1e6203683ab039655062", [:mix], [{:cowboy, "~> 2.5", [hex: :cowboy, repo: "hexpm", optional: false]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"},
40 | "plug_crypto": {:hex, :plug_crypto, "1.0.0", "18e49317d3fa343f24620ed22795ec29d4a5e602d52d1513ccea0b07d8ea7d4d", [:mix], [], "hexpm"},
41 | "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], []},
42 | "poolboy": {:hex, :poolboy, "1.5.1", "6b46163901cfd0a1b43d692657ed9d7e599853b3b21b95ae5ae0a777cf9b6ca8", [:rebar], []},
43 | "postgrex": {:hex, :postgrex, "0.14.1", "63247d4a5ad6b9de57a0bac5d807e1c32d41e39c04b8a4156a26c63bcd8a2e49", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"},
44 | "pre_commit": {:hex, :pre_commit, "0.2.4", "e26feaa149d55d3a6f14ca1340bd8738942d98405de389c0b3c7d48e71e62d66", [:mix], []},
45 | "ranch": {:hex, :ranch, "1.7.1", "6b1fab51b49196860b733a49c07604465a47bdb78aa10c1c16a3d199f7f8c881", [:rebar3], [], "hexpm"},
46 | "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.1", "28a4d65b7f59893bc2c7de786dec1e1555bd742d336043fe644ae956c3497fbe", [:make, :rebar], []},
47 | "telemetry": {:hex, :telemetry, "0.2.0", "5b40caa3efe4deb30fb12d7cd8ed4f556f6d6bd15c374c2366772161311ce377", [:mix], [], "hexpm"},
48 | "timex": {:hex, :timex, "3.2.1", "639975eac45c4c08c2dbf7fc53033c313ff1f94fad9282af03619a3826493612", [:mix], [{:combine, "~> 0.10", [hex: :combine, optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5", [hex: :tzdata, optional: false]}]},
49 | "tzdata": {:hex, :tzdata, "0.5.16", "13424d3afc76c68ff607f2df966c0ab4f3258859bbe3c979c9ed1606135e7352", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, optional: false]}]},
50 | "unicode_util_compat": {:hex, :unicode_util_compat, "0.3.1", "a1f612a7b512638634a603c8f401892afbf99b8ce93a45041f8aaca99cadb85e", [:rebar3], []},
51 | }
52 |
--------------------------------------------------------------------------------
/test/fixtures/comment_deleted.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "deleted",
3 | "issue": {
4 | "url": "https://api.github.com/repos/Cleop/react-cv/issues/2",
5 | "repository_url": "https://api.github.com/repos/Cleop/react-cv",
6 | "labels_url": "https://api.github.com/repos/Cleop/react-cv/issues/2/labels{/name}",
7 | "comments_url": "https://api.github.com/repos/Cleop/react-cv/issues/2/comments",
8 | "events_url": "https://api.github.com/repos/Cleop/react-cv/issues/2/events",
9 | "html_url": "https://github.com/Cleop/react-cv/issues/2",
10 | "id": 299402596,
11 | "number": 2,
12 | "title": "2nd test issue",
13 | "user": {
14 | "login": "Cleop",
15 | "id": 16775804,
16 | "avatar_url": "https://avatars3.githubusercontent.com/u/16775804?v=4",
17 | "gravatar_id": "",
18 | "url": "https://api.github.com/users/Cleop",
19 | "html_url": "https://github.com/Cleop",
20 | "followers_url": "https://api.github.com/users/Cleop/followers",
21 | "following_url": "https://api.github.com/users/Cleop/following{/other_user}",
22 | "gists_url": "https://api.github.com/users/Cleop/gists{/gist_id}",
23 | "starred_url": "https://api.github.com/users/Cleop/starred{/owner}{/repo}",
24 | "subscriptions_url": "https://api.github.com/users/Cleop/subscriptions",
25 | "organizations_url": "https://api.github.com/users/Cleop/orgs",
26 | "repos_url": "https://api.github.com/users/Cleop/repos",
27 | "events_url": "https://api.github.com/users/Cleop/events{/privacy}",
28 | "received_events_url": "https://api.github.com/users/Cleop/received_events",
29 | "type": "User",
30 | "site_admin": false
31 | },
32 | "labels": [
33 |
34 | ],
35 | "state": "open",
36 | "locked": false,
37 | "assignee": null,
38 | "assignees": [
39 |
40 | ],
41 | "milestone": null,
42 | "comments": 1,
43 | "created_at": "2018-02-22T15:44:19Z",
44 | "updated_at": "2018-03-07T10:50:35Z",
45 | "closed_at": null,
46 | "author_association": "OWNER",
47 | "body": "Testing 123"
48 | },
49 | "comment": {
50 | "url": "https://api.github.com/repos/Cleop/react-cv/issues/comments/371100466",
51 | "html_url": "https://github.com/Cleop/react-cv/issues/2#issuecomment-371100466",
52 | "issue_url": "https://api.github.com/repos/Cleop/react-cv/issues/2",
53 | "id": 370447582,
54 | "user": {
55 | "login": "Cleop",
56 | "id": 16775804,
57 | "avatar_url": "https://avatars3.githubusercontent.com/u/16775804?v=4",
58 | "gravatar_id": "",
59 | "url": "https://api.github.com/users/Cleop",
60 | "html_url": "https://github.com/Cleop",
61 | "followers_url": "https://api.github.com/users/Cleop/followers",
62 | "following_url": "https://api.github.com/users/Cleop/following{/other_user}",
63 | "gists_url": "https://api.github.com/users/Cleop/gists{/gist_id}",
64 | "starred_url": "https://api.github.com/users/Cleop/starred{/owner}{/repo}",
65 | "subscriptions_url": "https://api.github.com/users/Cleop/subscriptions",
66 | "organizations_url": "https://api.github.com/users/Cleop/orgs",
67 | "repos_url": "https://api.github.com/users/Cleop/repos",
68 | "events_url": "https://api.github.com/users/Cleop/events{/privacy}",
69 | "received_events_url": "https://api.github.com/users/Cleop/received_events",
70 | "type": "User",
71 | "site_admin": false
72 | },
73 | "created_at": "2018-03-07T10:50:32Z",
74 | "updated_at": "2018-03-07T10:50:32Z",
75 | "author_association": "OWNER",
76 | "body": "test"
77 | },
78 | "repository": {
79 | "id": 81315472,
80 | "name": "react-cv",
81 | "full_name": "Cleop/react-cv",
82 | "owner": {
83 | "login": "Cleop",
84 | "id": 16775804,
85 | "avatar_url": "https://avatars3.githubusercontent.com/u/16775804?v=4",
86 | "gravatar_id": "",
87 | "url": "https://api.github.com/users/Cleop",
88 | "html_url": "https://github.com/Cleop",
89 | "followers_url": "https://api.github.com/users/Cleop/followers",
90 | "following_url": "https://api.github.com/users/Cleop/following{/other_user}",
91 | "gists_url": "https://api.github.com/users/Cleop/gists{/gist_id}",
92 | "starred_url": "https://api.github.com/users/Cleop/starred{/owner}{/repo}",
93 | "subscriptions_url": "https://api.github.com/users/Cleop/subscriptions",
94 | "organizations_url": "https://api.github.com/users/Cleop/orgs",
95 | "repos_url": "https://api.github.com/users/Cleop/repos",
96 | "events_url": "https://api.github.com/users/Cleop/events{/privacy}",
97 | "received_events_url": "https://api.github.com/users/Cleop/received_events",
98 | "type": "User",
99 | "site_admin": false
100 | },
101 | "private": false,
102 | "html_url": "https://github.com/Cleop/react-cv",
103 | "description": "Building a CV / portfolio site using React",
104 | "fork": false,
105 | "url": "https://api.github.com/repos/Cleop/react-cv",
106 | "forks_url": "https://api.github.com/repos/Cleop/react-cv/forks",
107 | "keys_url": "https://api.github.com/repos/Cleop/react-cv/keys{/key_id}",
108 | "collaborators_url": "https://api.github.com/repos/Cleop/react-cv/collaborators{/collaborator}",
109 | "teams_url": "https://api.github.com/repos/Cleop/react-cv/teams",
110 | "hooks_url": "https://api.github.com/repos/Cleop/react-cv/hooks",
111 | "issue_events_url": "https://api.github.com/repos/Cleop/react-cv/issues/events{/number}",
112 | "events_url": "https://api.github.com/repos/Cleop/react-cv/events",
113 | "assignees_url": "https://api.github.com/repos/Cleop/react-cv/assignees{/user}",
114 | "branches_url": "https://api.github.com/repos/Cleop/react-cv/branches{/branch}",
115 | "tags_url": "https://api.github.com/repos/Cleop/react-cv/tags",
116 | "blobs_url": "https://api.github.com/repos/Cleop/react-cv/git/blobs{/sha}",
117 | "git_tags_url": "https://api.github.com/repos/Cleop/react-cv/git/tags{/sha}",
118 | "git_refs_url": "https://api.github.com/repos/Cleop/react-cv/git/refs{/sha}",
119 | "trees_url": "https://api.github.com/repos/Cleop/react-cv/git/trees{/sha}",
120 | "statuses_url": "https://api.github.com/repos/Cleop/react-cv/statuses/{sha}",
121 | "languages_url": "https://api.github.com/repos/Cleop/react-cv/languages",
122 | "stargazers_url": "https://api.github.com/repos/Cleop/react-cv/stargazers",
123 | "contributors_url": "https://api.github.com/repos/Cleop/react-cv/contributors",
124 | "subscribers_url": "https://api.github.com/repos/Cleop/react-cv/subscribers",
125 | "subscription_url": "https://api.github.com/repos/Cleop/react-cv/subscription",
126 | "commits_url": "https://api.github.com/repos/Cleop/react-cv/commits{/sha}",
127 | "git_commits_url": "https://api.github.com/repos/Cleop/react-cv/git/commits{/sha}",
128 | "comments_url": "https://api.github.com/repos/Cleop/react-cv/comments{/number}",
129 | "issue_comment_url": "https://api.github.com/repos/Cleop/react-cv/issues/comments{/number}",
130 | "contents_url": "https://api.github.com/repos/Cleop/react-cv/contents/{+path}",
131 | "compare_url": "https://api.github.com/repos/Cleop/react-cv/compare/{base}...{head}",
132 | "merges_url": "https://api.github.com/repos/Cleop/react-cv/merges",
133 | "archive_url": "https://api.github.com/repos/Cleop/react-cv/{archive_format}{/ref}",
134 | "downloads_url": "https://api.github.com/repos/Cleop/react-cv/downloads",
135 | "issues_url": "https://api.github.com/repos/Cleop/react-cv/issues{/number}",
136 | "pulls_url": "https://api.github.com/repos/Cleop/react-cv/pulls{/number}",
137 | "milestones_url": "https://api.github.com/repos/Cleop/react-cv/milestones{/number}",
138 | "notifications_url": "https://api.github.com/repos/Cleop/react-cv/notifications{?since,all,participating}",
139 | "labels_url": "https://api.github.com/repos/Cleop/react-cv/labels{/name}",
140 | "releases_url": "https://api.github.com/repos/Cleop/react-cv/releases{/id}",
141 | "deployments_url": "https://api.github.com/repos/Cleop/react-cv/deployments",
142 | "created_at": "2017-02-08T10:04:41Z",
143 | "updated_at": "2017-02-23T08:50:57Z",
144 | "pushed_at": "2017-02-22T12:22:57Z",
145 | "git_url": "git://github.com/Cleop/react-cv.git",
146 | "ssh_url": "git@github.com:Cleop/react-cv.git",
147 | "clone_url": "https://github.com/Cleop/react-cv.git",
148 | "svn_url": "https://github.com/Cleop/react-cv",
149 | "homepage": "",
150 | "size": 69,
151 | "stargazers_count": 0,
152 | "watchers_count": 0,
153 | "language": "JavaScript",
154 | "has_issues": true,
155 | "has_projects": true,
156 | "has_downloads": true,
157 | "has_wiki": true,
158 | "has_pages": false,
159 | "forks_count": 0,
160 | "mirror_url": null,
161 | "archived": false,
162 | "open_issues_count": 6,
163 | "license": {
164 | "key": "mit",
165 | "name": "MIT License",
166 | "spdx_id": "MIT",
167 | "url": "https://api.github.com/licenses/mit"
168 | },
169 | "forks": 0,
170 | "open_issues": 6,
171 | "watchers": 0,
172 | "default_branch": "master"
173 | },
174 | "sender": {
175 | "login": "SimonLab",
176 | "id": 6057298,
177 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
178 | "gravatar_id": "",
179 | "url": "https://api.github.com/users/SimonLab",
180 | "html_url": "https://github.com/SimonLab",
181 | "followers_url": "https://api.github.com/users/SimonLab/followers",
182 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
183 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
184 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
185 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
186 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
187 | "repos_url": "https://api.github.com/users/SimonLab/repos",
188 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
189 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
190 | "type": "User",
191 | "site_admin": false
192 | },
193 | "installation": {
194 | "id": 92614
195 | }
196 | }
197 |
--------------------------------------------------------------------------------
/lib/app_web/controllers/event_type_handlers.ex:
--------------------------------------------------------------------------------
1 | defmodule AppWeb.EventTypeHandlers do
2 | use AppWeb, :controller
3 | alias App.{Comment, Issue, IssueStatus, Repo, User}
4 | alias App.Helpers.{IssueHelper, UserHelper}
5 | alias Ecto.Changeset
6 | alias AppWeb.MetaTable
7 |
8 | @github_api Application.get_env(:app, :github_api)
9 | @github_app_name Application.get_env(:app, :github_app_name)
10 | @s3_api Application.get_env(:app, :s3_api)
11 |
12 | @moduledoc """
13 | Determines the type of event received by the Github Webhooks requests
14 | """
15 |
16 | def unknow_event(conn) do
17 | conn
18 | |> put_status(404)
19 | |> json(%{ok: "event unkown"})
20 | end
21 |
22 | def new_installation(conn, payload) do
23 | token = @github_api.get_installation_token(payload["installation"]["id"])
24 | repositories = payload["repositories"] || payload["repositories_added"]
25 |
26 | repo_data = Enum.map(repositories, fn r ->
27 | issues = @github_api.get_issues(token, r["full_name"], 1, [])
28 | comments = @github_api.get_comments(token, r["full_name"], 1, [])
29 | issues = IssueHelper.attach_comments(issues, comments)
30 | %{
31 | repository: r,
32 | issues: issues,
33 | }
34 | end)
35 |
36 | # create list of issue schema
37 | issues = Enum.flat_map(repo_data, fn r ->
38 | r.issues
39 | |> Enum.map(fn i ->
40 | author_params = %{
41 | login: i["user"]["login"],
42 | user_id: i["user"]["id"],
43 | avatar_url: i["user"]["avatar_url"],
44 | html_url: i["user"]["html_url"],
45 | }
46 | author_issue = UserHelper.insert_or_update_user(author_params)
47 |
48 | %{
49 | pull_request: Map.has_key?(i, "pull_request"),
50 | issue_id: i["id"],
51 | title: i["title"],
52 | description: i["body"],
53 | issue_author: author_issue,
54 | inserted_at: NaiveDateTime.from_iso8601!(i["created_at"]),
55 | updated_at: NaiveDateTime.from_iso8601!(i["created_at"]),
56 | comments: Enum.map(i["comments"], fn c ->
57 | author_params = %{
58 | login: c["user"]["login"],
59 | user_id: c["user"]["id"],
60 | avatar_url: c["user"]["avatar_url"],
61 | html_url: c["user"]["html_url"],
62 | }
63 | author_comment = UserHelper.insert_or_update_user(author_params)
64 |
65 | %{
66 | comment_id: "#{c["id"]}",
67 | versions: [%{
68 | author: author_comment.id,
69 | inserted_at: NaiveDateTime.from_iso8601!(c["created_at"]),
70 | updated_at: NaiveDateTime.from_iso8601!(c["created_at"])
71 | }],
72 | comment: c["body"],
73 | inserted_at: NaiveDateTime.from_iso8601!(c["created_at"]),
74 | updated_at: NaiveDateTime.from_iso8601!(c["created_at"])
75 | }
76 | end)
77 | }
78 | end)
79 | end)
80 |
81 | # Add description issue has comment too!
82 | issues = IssueHelper.issues_as_comments(issues)
83 |
84 | # filter issue to remove PRs
85 | issues = IssueHelper.get_issues(issues)
86 |
87 | # create map for comments: %{comment_id => comment_text, ...}
88 | comments_body = IssueHelper.get_map_comments(issues)
89 |
90 | # save issue
91 | Enum.each(issues, fn i ->
92 | changeset = Issue.changeset(%Issue{}, i)
93 | issue = Repo.insert!(changeset)
94 |
95 | s3_data = IssueHelper.get_s3_content(issue, comments_body)
96 | content = Poison.encode!(s3_data)
97 | @s3_api.save_comment(issue.issue_id, content)
98 | end)
99 |
100 | conn
101 | |> put_status(200)
102 | |> json(%{ok: "new installation"})
103 | end
104 |
105 | def issue_created(conn, payload) do
106 | issue = get_issue_from_pr(payload)
107 |
108 | payload = Map.put(payload, "issue", issue)
109 |
110 | author_params = %{
111 | login: payload["issue"]["user"]["login"],
112 | user_id: payload["issue"]["user"]["id"],
113 | avatar_url: payload["issue"]["user"]["avatar_url"],
114 | html_url: payload["issue"]["user"]["html_url"]
115 | }
116 | author = UserHelper.insert_or_update_user(author_params)
117 |
118 | issue_params = %{
119 | issue_id: payload["issue"]["id"],
120 | title: payload["issue"]["title"],
121 | pull_request: Map.has_key?(payload, "pull_request"),
122 | comments: [
123 | %{
124 | comment_id: "#{payload["issue"]["id"]}_1",
125 | versions: [%{author: author.id}]
126 | }
127 | ]
128 | }
129 |
130 | changeset = Issue.changeset(%Issue{}, issue_params)
131 | issue = Repo.insert!(changeset)
132 |
133 | comment = payload["issue"]["body"]
134 | version_id = issue.comments
135 | |> List.first()
136 | |> Map.get(:versions)
137 | |> List.first()
138 | |> Map.get(:id)
139 | content = Poison.encode!(%{version_id => comment})
140 | @s3_api.save_comment(issue.issue_id, content)
141 |
142 | meta_table = MetaTable.get_meta_table(payload["issue"]["id"])
143 | content = comment <> "\r\n\n" <> meta_table
144 |
145 | repo_name = payload["repository"]["full_name"]
146 | issue_number = payload["issue"]["number"]
147 | token = @github_api.get_installation_token(payload["installation"]["id"])
148 | @github_api.add_meta_table(repo_name, issue_number, content, token)
149 |
150 | conn
151 | |> put_status(200)
152 | |> json(%{ok: "issue created"})
153 | end
154 |
155 | def issue_edited(conn, payload) do
156 | issue = get_issue_from_pr(payload)
157 | payload = Map.put(payload, "issue", issue)
158 |
159 | issue_id = payload["issue"]["id"]
160 |
161 | if Map.has_key?(payload["changes"], "title") do
162 | issue = Repo.get_by!(Issue, issue_id: issue_id)
163 | issue = Changeset.change issue, title: payload["issue"]["title"]
164 | Repo.update!(issue)
165 | end
166 |
167 | body_change = Map.has_key?(payload["changes"], "body")
168 | not_bot = payload["sender"]["login"] != @github_app_name <> "[bot]"
169 | if body_change && not_bot do
170 | comment = payload["issue"]["body"]
171 | author_params = %{
172 | login: payload["sender"]["login"],
173 | user_id: payload["sender"]["id"],
174 | avatar_url: payload["sender"]["avatar_url"],
175 | html_url: payload["sender"]["html_url"],
176 | }
177 | author = UserHelper.insert_or_update_user(author_params)
178 | add_comment_version(issue_id, "#{issue_id}_1", comment, author)
179 | end
180 |
181 | conn
182 | |> put_status(200)
183 | |> json(%{ok: "issue edited"})
184 | end
185 |
186 | def issue_closed(conn, payload) do
187 | issue_status_params = %{
188 | event: "closed"
189 | }
190 | issue = get_issue_from_pr(payload)
191 | payload = Map.put(payload, "issue", issue)
192 |
193 | issue = Repo.get_by!(Issue, issue_id: payload["issue"]["id"])
194 |
195 | %IssueStatus{}
196 | |> IssueStatus.changeset(issue_status_params)
197 | |> Changeset.put_change(:issue_id, issue.id)
198 | |> Repo.insert!
199 |
200 | conn
201 | |> put_status(200)
202 | |> json(%{ok: "issue closed"})
203 | end
204 |
205 | def issue_reopened(conn, payload) do
206 | issue_status_params = %{
207 | event: "reopened"
208 | }
209 | issue_payload = get_issue_from_pr(payload)
210 | payload = Map.put(payload, "issue", issue_payload)
211 | issue = Repo.get_by!(Issue, issue_id: payload["issue"]["id"])
212 |
213 | %IssueStatus{}
214 | |> IssueStatus.changeset(issue_status_params)
215 | |> Changeset.put_change(:issue_id, issue.id)
216 | |> Repo.insert!
217 |
218 | conn
219 | |> put_status(200)
220 | |> json(%{ok: "issue reopened"})
221 | end
222 |
223 | def add_comment_version(issue_id, comment_id, content, author) do
224 | comment = Repo.get_by!(Comment, comment_id: "#{comment_id}")
225 | version_params = %{author: author.id}
226 | changeset = Ecto.build_assoc(comment, :versions, version_params)
227 | version = Repo.insert!(changeset)
228 | update_s3_file(issue_id, version.id, content)
229 | end
230 |
231 | def update_s3_file(issue_id, version_id, comment) do
232 | {:ok, s3_issue} = @s3_api.get_issue(issue_id)
233 | content = Poison.decode!(s3_issue.body)
234 | content = Map.put(content, version_id, comment)
235 | @s3_api.save_comment(issue_id, Poison.encode!(content))
236 | end
237 |
238 | def comment_created(conn, payload) do
239 | issue_id = payload["issue"]["id"]
240 | issue = Repo.get_by!(Issue, issue_id: issue_id)
241 | author_params = %{
242 | login: payload["comment"]["user"]["login"],
243 | user_id: payload["comment"]["user"]["id"],
244 | avatar_url: payload["comment"]["user"]["avatar_url"],
245 | html_url: payload["comment"]["user"]["html_url"],
246 | }
247 | author = UserHelper.insert_or_update_user(author_params)
248 | comment_params = %{
249 | comment_id: "#{payload["comment"]["id"]}",
250 | versions: [%{author: author.id}]
251 | }
252 | changeset = Ecto.build_assoc(issue, :comments, comment_params)
253 | comment = Repo.insert!(changeset)
254 |
255 | version = List.first(comment.versions)
256 | update_s3_file(issue_id, version.id, payload["comment"]["body"])
257 |
258 | conn
259 | |> put_status(200)
260 | |> json(%{ok: "comment created"})
261 | end
262 |
263 | def comment_edited(conn, payload) do
264 | issue_id = payload["issue"]["id"]
265 | comment_id = payload["comment"]["id"]
266 | comment = payload["comment"]["body"]
267 | author_params = %{
268 | login: payload["sender"]["login"],
269 | user_id: payload["sender"]["id"],
270 | avatar_url: payload["sender"]["avatar_url"],
271 | html_url: payload["sender"]["html_url"],
272 | }
273 | author = UserHelper.insert_or_update_user(author_params)
274 | add_comment_version(issue_id, comment_id, comment, author)
275 |
276 | conn
277 | |> put_status(200)
278 | |> json(%{ok: "comment edited"})
279 | end
280 |
281 | def comment_deleted(conn, payload) do
282 | comment_id = payload["comment"]["id"]
283 | author_params = %{
284 | login: payload["sender"]["login"],
285 | user_id: payload["sender"]["id"],
286 | avatar_url: payload["sender"]["avatar_url"],
287 | html_url: payload["sender"]["html_url"],
288 | }
289 | author = UserHelper.insert_or_update_user(author_params)
290 | comment = Repo.get_by!(Comment, comment_id: "#{comment_id}")
291 | changeset = Comment.changeset(comment)
292 | changeset = changeset
293 | |> Changeset.put_change(:deleted, true)
294 | |> Changeset.put_change(:deleted_by, author.id)
295 | Repo.update!(changeset)
296 |
297 | conn
298 | |> put_status(200)
299 | |> json(%{ok: "comment deleted"})
300 | end
301 |
302 | defp get_issue_from_pr(payload) do
303 | token = @github_api.get_installation_token(payload["installation"]["id"])
304 | if Map.has_key?(payload, "pull_request") do
305 | repo_name = payload["repository"]["full_name"]
306 | issue_number = payload["pull_request"]["number"]
307 | @github_api.get_issue(token, repo_name, issue_number)
308 | else
309 | payload["issue"]
310 | end
311 | end
312 |
313 | end
314 |
--------------------------------------------------------------------------------
/test/fixtures/comment_created.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "created",
3 | "issue": {
4 | "url": "https://api.github.com/repos/SimonLab/github_app/issues/35",
5 | "repository_url": "https://api.github.com/repos/SimonLab/github_app",
6 | "labels_url": "https://api.github.com/repos/SimonLab/github_app/issues/35/labels{/name}",
7 | "comments_url": "https://api.github.com/repos/SimonLab/github_app/issues/35/comments",
8 | "events_url": "https://api.github.com/repos/SimonLab/github_app/issues/35/events",
9 | "html_url": "https://github.com/SimonLab/github_app/issues/35",
10 | "id": 301369661,
11 | "number": 35,
12 | "title": "How to...",
13 | "user": {
14 | "login": "SimonLab",
15 | "id": 6057298,
16 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
17 | "gravatar_id": "",
18 | "url": "https://api.github.com/users/SimonLab",
19 | "html_url": "https://github.com/SimonLab",
20 | "followers_url": "https://api.github.com/users/SimonLab/followers",
21 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
22 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
23 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
24 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
25 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
26 | "repos_url": "https://api.github.com/users/SimonLab/repos",
27 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
28 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
29 | "type": "User",
30 | "site_admin": false
31 | },
32 | "labels": [
33 |
34 | ],
35 | "state": "open",
36 | "locked": false,
37 | "assignee": {
38 | "login": "SimonLab",
39 | "id": 6057298,
40 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
41 | "gravatar_id": "",
42 | "url": "https://api.github.com/users/SimonLab",
43 | "html_url": "https://github.com/SimonLab",
44 | "followers_url": "https://api.github.com/users/SimonLab/followers",
45 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
46 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
47 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
48 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
49 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
50 | "repos_url": "https://api.github.com/users/SimonLab/repos",
51 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
52 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
53 | "type": "User",
54 | "site_admin": false
55 | },
56 | "assignees": [
57 | {
58 | "login": "SimonLab",
59 | "id": 6057298,
60 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
61 | "gravatar_id": "",
62 | "url": "https://api.github.com/users/SimonLab",
63 | "html_url": "https://github.com/SimonLab",
64 | "followers_url": "https://api.github.com/users/SimonLab/followers",
65 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
66 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
67 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
68 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
69 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
70 | "repos_url": "https://api.github.com/users/SimonLab/repos",
71 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
72 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
73 | "type": "User",
74 | "site_admin": false
75 | }
76 | ],
77 | "milestone": null,
78 | "comments": 0,
79 | "created_at": "2018-03-05T08:35:55Z",
80 | "updated_at": "2018-03-05T15:04:29Z",
81 | "closed_at": null,
82 | "author_association": "OWNER",
83 | "body": "save edited issue??\r\n!!!"
84 | },
85 | "comment": {
86 | "url": "https://api.github.com/repos/SimonLab/github_app/issues/comments/370447582",
87 | "html_url": "https://github.com/SimonLab/github_app/issues/35#issuecomment-370447582",
88 | "issue_url": "https://api.github.com/repos/SimonLab/github_app/issues/35",
89 | "id": 370447582,
90 | "user": {
91 | "login": "SimonLab",
92 | "id": 6057298,
93 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
94 | "gravatar_id": "",
95 | "url": "https://api.github.com/users/SimonLab",
96 | "html_url": "https://github.com/SimonLab",
97 | "followers_url": "https://api.github.com/users/SimonLab/followers",
98 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
99 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
100 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
101 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
102 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
103 | "repos_url": "https://api.github.com/users/SimonLab/repos",
104 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
105 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
106 | "type": "User",
107 | "site_admin": false
108 | },
109 | "created_at": "2018-03-05T15:04:29Z",
110 | "updated_at": "2018-03-05T15:04:29Z",
111 | "author_association": "OWNER",
112 | "body": "save a comment?"
113 | },
114 | "repository": {
115 | "id": 92847353,
116 | "name": "github_app",
117 | "full_name": "SimonLab/github_app",
118 | "owner": {
119 | "login": "SimonLab",
120 | "id": 6057298,
121 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
122 | "gravatar_id": "",
123 | "url": "https://api.github.com/users/SimonLab",
124 | "html_url": "https://github.com/SimonLab",
125 | "followers_url": "https://api.github.com/users/SimonLab/followers",
126 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
127 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
128 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
129 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
130 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
131 | "repos_url": "https://api.github.com/users/SimonLab/repos",
132 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
133 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
134 | "type": "User",
135 | "site_admin": false
136 | },
137 | "private": false,
138 | "html_url": "https://github.com/SimonLab/github_app",
139 | "description": "a Github App to play with integrations",
140 | "fork": false,
141 | "url": "https://api.github.com/repos/SimonLab/github_app",
142 | "forks_url": "https://api.github.com/repos/SimonLab/github_app/forks",
143 | "keys_url": "https://api.github.com/repos/SimonLab/github_app/keys{/key_id}",
144 | "collaborators_url": "https://api.github.com/repos/SimonLab/github_app/collaborators{/collaborator}",
145 | "teams_url": "https://api.github.com/repos/SimonLab/github_app/teams",
146 | "hooks_url": "https://api.github.com/repos/SimonLab/github_app/hooks",
147 | "issue_events_url": "https://api.github.com/repos/SimonLab/github_app/issues/events{/number}",
148 | "events_url": "https://api.github.com/repos/SimonLab/github_app/events",
149 | "assignees_url": "https://api.github.com/repos/SimonLab/github_app/assignees{/user}",
150 | "branches_url": "https://api.github.com/repos/SimonLab/github_app/branches{/branch}",
151 | "tags_url": "https://api.github.com/repos/SimonLab/github_app/tags",
152 | "blobs_url": "https://api.github.com/repos/SimonLab/github_app/git/blobs{/sha}",
153 | "git_tags_url": "https://api.github.com/repos/SimonLab/github_app/git/tags{/sha}",
154 | "git_refs_url": "https://api.github.com/repos/SimonLab/github_app/git/refs{/sha}",
155 | "trees_url": "https://api.github.com/repos/SimonLab/github_app/git/trees{/sha}",
156 | "statuses_url": "https://api.github.com/repos/SimonLab/github_app/statuses/{sha}",
157 | "languages_url": "https://api.github.com/repos/SimonLab/github_app/languages",
158 | "stargazers_url": "https://api.github.com/repos/SimonLab/github_app/stargazers",
159 | "contributors_url": "https://api.github.com/repos/SimonLab/github_app/contributors",
160 | "subscribers_url": "https://api.github.com/repos/SimonLab/github_app/subscribers",
161 | "subscription_url": "https://api.github.com/repos/SimonLab/github_app/subscription",
162 | "commits_url": "https://api.github.com/repos/SimonLab/github_app/commits{/sha}",
163 | "git_commits_url": "https://api.github.com/repos/SimonLab/github_app/git/commits{/sha}",
164 | "comments_url": "https://api.github.com/repos/SimonLab/github_app/comments{/number}",
165 | "issue_comment_url": "https://api.github.com/repos/SimonLab/github_app/issues/comments{/number}",
166 | "contents_url": "https://api.github.com/repos/SimonLab/github_app/contents/{+path}",
167 | "compare_url": "https://api.github.com/repos/SimonLab/github_app/compare/{base}...{head}",
168 | "merges_url": "https://api.github.com/repos/SimonLab/github_app/merges",
169 | "archive_url": "https://api.github.com/repos/SimonLab/github_app/{archive_format}{/ref}",
170 | "downloads_url": "https://api.github.com/repos/SimonLab/github_app/downloads",
171 | "issues_url": "https://api.github.com/repos/SimonLab/github_app/issues{/number}",
172 | "pulls_url": "https://api.github.com/repos/SimonLab/github_app/pulls{/number}",
173 | "milestones_url": "https://api.github.com/repos/SimonLab/github_app/milestones{/number}",
174 | "notifications_url": "https://api.github.com/repos/SimonLab/github_app/notifications{?since,all,participating}",
175 | "labels_url": "https://api.github.com/repos/SimonLab/github_app/labels{/name}",
176 | "releases_url": "https://api.github.com/repos/SimonLab/github_app/releases{/id}",
177 | "deployments_url": "https://api.github.com/repos/SimonLab/github_app/deployments",
178 | "created_at": "2017-05-30T15:26:26Z",
179 | "updated_at": "2017-05-30T15:36:26Z",
180 | "pushed_at": "2017-06-26T14:03:32Z",
181 | "git_url": "git://github.com/SimonLab/github_app.git",
182 | "ssh_url": "git@github.com:SimonLab/github_app.git",
183 | "clone_url": "https://github.com/SimonLab/github_app.git",
184 | "svn_url": "https://github.com/SimonLab/github_app",
185 | "homepage": null,
186 | "size": 63,
187 | "stargazers_count": 0,
188 | "watchers_count": 0,
189 | "language": "Elixir",
190 | "has_issues": true,
191 | "has_projects": true,
192 | "has_downloads": true,
193 | "has_wiki": true,
194 | "has_pages": false,
195 | "forks_count": 0,
196 | "mirror_url": null,
197 | "archived": false,
198 | "open_issues_count": 14,
199 | "license": null,
200 | "forks": 0,
201 | "open_issues": 14,
202 | "watchers": 0,
203 | "default_branch": "b1"
204 | },
205 | "sender": {
206 | "login": "SimonLab",
207 | "id": 6057298,
208 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
209 | "gravatar_id": "",
210 | "url": "https://api.github.com/users/SimonLab",
211 | "html_url": "https://github.com/SimonLab",
212 | "followers_url": "https://api.github.com/users/SimonLab/followers",
213 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
214 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
215 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
216 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
217 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
218 | "repos_url": "https://api.github.com/users/SimonLab/repos",
219 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
220 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
221 | "type": "User",
222 | "site_admin": false
223 | },
224 | "installation": {
225 | "id": 92693
226 | }
227 | }
228 |
--------------------------------------------------------------------------------
/test/fixtures/comment_edited.json:
--------------------------------------------------------------------------------
1 | {
2 | "action": "edited",
3 | "changes": {
4 | "body": {
5 | "from": "edit a comment?!"
6 | }
7 | },
8 | "issue": {
9 | "url": "https://api.github.com/repos/SimonLab/github_app/issues/35",
10 | "repository_url": "https://api.github.com/repos/SimonLab/github_app",
11 | "labels_url": "https://api.github.com/repos/SimonLab/github_app/issues/35/labels{/name}",
12 | "comments_url": "https://api.github.com/repos/SimonLab/github_app/issues/35/comments",
13 | "events_url": "https://api.github.com/repos/SimonLab/github_app/issues/35/events",
14 | "html_url": "https://github.com/SimonLab/github_app/issues/35",
15 | "id": 301369661,
16 | "number": 35,
17 | "title": "How to...",
18 | "user": {
19 | "login": "SimonLab",
20 | "id": 6057298,
21 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
22 | "gravatar_id": "",
23 | "url": "https://api.github.com/users/SimonLab",
24 | "html_url": "https://github.com/SimonLab",
25 | "followers_url": "https://api.github.com/users/SimonLab/followers",
26 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
27 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
28 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
29 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
30 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
31 | "repos_url": "https://api.github.com/users/SimonLab/repos",
32 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
33 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
34 | "type": "User",
35 | "site_admin": false
36 | },
37 | "labels": [
38 |
39 | ],
40 | "state": "open",
41 | "locked": false,
42 | "assignee": {
43 | "login": "SimonLab",
44 | "id": 6057298,
45 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
46 | "gravatar_id": "",
47 | "url": "https://api.github.com/users/SimonLab",
48 | "html_url": "https://github.com/SimonLab",
49 | "followers_url": "https://api.github.com/users/SimonLab/followers",
50 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
51 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
52 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
53 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
54 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
55 | "repos_url": "https://api.github.com/users/SimonLab/repos",
56 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
57 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
58 | "type": "User",
59 | "site_admin": false
60 | },
61 | "assignees": [
62 | {
63 | "login": "SimonLab",
64 | "id": 6057298,
65 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
66 | "gravatar_id": "",
67 | "url": "https://api.github.com/users/SimonLab",
68 | "html_url": "https://github.com/SimonLab",
69 | "followers_url": "https://api.github.com/users/SimonLab/followers",
70 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
71 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
72 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
73 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
74 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
75 | "repos_url": "https://api.github.com/users/SimonLab/repos",
76 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
77 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
78 | "type": "User",
79 | "site_admin": false
80 | }
81 | ],
82 | "milestone": null,
83 | "comments": 2,
84 | "created_at": "2018-03-05T08:35:55Z",
85 | "updated_at": "2018-03-06T14:22:41Z",
86 | "closed_at": null,
87 | "author_association": "OWNER",
88 | "body": "save edited issue??\r\n!!!"
89 | },
90 | "comment": {
91 | "url": "https://api.github.com/repos/SimonLab/github_app/issues/comments/370793143",
92 | "html_url": "https://github.com/SimonLab/github_app/issues/35#issuecomment-370793143",
93 | "issue_url": "https://api.github.com/repos/SimonLab/github_app/issues/35",
94 | "id": 370447582,
95 | "user": {
96 | "login": "SimonLab",
97 | "id": 6057298,
98 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
99 | "gravatar_id": "",
100 | "url": "https://api.github.com/users/SimonLab",
101 | "html_url": "https://github.com/SimonLab",
102 | "followers_url": "https://api.github.com/users/SimonLab/followers",
103 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
104 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
105 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
106 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
107 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
108 | "repos_url": "https://api.github.com/users/SimonLab/repos",
109 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
110 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
111 | "type": "User",
112 | "site_admin": false
113 | },
114 | "created_at": "2018-03-06T14:11:18Z",
115 | "updated_at": "2018-03-06T14:22:41Z",
116 | "author_association": "OWNER",
117 | "body": "edit a comment?!!!"
118 | },
119 | "repository": {
120 | "id": 92847353,
121 | "name": "github_app",
122 | "full_name": "SimonLab/github_app",
123 | "owner": {
124 | "login": "SimonLab",
125 | "id": 6057298,
126 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
127 | "gravatar_id": "",
128 | "url": "https://api.github.com/users/SimonLab",
129 | "html_url": "https://github.com/SimonLab",
130 | "followers_url": "https://api.github.com/users/SimonLab/followers",
131 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
132 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
133 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
134 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
135 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
136 | "repos_url": "https://api.github.com/users/SimonLab/repos",
137 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
138 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
139 | "type": "User",
140 | "site_admin": false
141 | },
142 | "private": false,
143 | "html_url": "https://github.com/SimonLab/github_app",
144 | "description": "a Github App to play with integrations",
145 | "fork": false,
146 | "url": "https://api.github.com/repos/SimonLab/github_app",
147 | "forks_url": "https://api.github.com/repos/SimonLab/github_app/forks",
148 | "keys_url": "https://api.github.com/repos/SimonLab/github_app/keys{/key_id}",
149 | "collaborators_url": "https://api.github.com/repos/SimonLab/github_app/collaborators{/collaborator}",
150 | "teams_url": "https://api.github.com/repos/SimonLab/github_app/teams",
151 | "hooks_url": "https://api.github.com/repos/SimonLab/github_app/hooks",
152 | "issue_events_url": "https://api.github.com/repos/SimonLab/github_app/issues/events{/number}",
153 | "events_url": "https://api.github.com/repos/SimonLab/github_app/events",
154 | "assignees_url": "https://api.github.com/repos/SimonLab/github_app/assignees{/user}",
155 | "branches_url": "https://api.github.com/repos/SimonLab/github_app/branches{/branch}",
156 | "tags_url": "https://api.github.com/repos/SimonLab/github_app/tags",
157 | "blobs_url": "https://api.github.com/repos/SimonLab/github_app/git/blobs{/sha}",
158 | "git_tags_url": "https://api.github.com/repos/SimonLab/github_app/git/tags{/sha}",
159 | "git_refs_url": "https://api.github.com/repos/SimonLab/github_app/git/refs{/sha}",
160 | "trees_url": "https://api.github.com/repos/SimonLab/github_app/git/trees{/sha}",
161 | "statuses_url": "https://api.github.com/repos/SimonLab/github_app/statuses/{sha}",
162 | "languages_url": "https://api.github.com/repos/SimonLab/github_app/languages",
163 | "stargazers_url": "https://api.github.com/repos/SimonLab/github_app/stargazers",
164 | "contributors_url": "https://api.github.com/repos/SimonLab/github_app/contributors",
165 | "subscribers_url": "https://api.github.com/repos/SimonLab/github_app/subscribers",
166 | "subscription_url": "https://api.github.com/repos/SimonLab/github_app/subscription",
167 | "commits_url": "https://api.github.com/repos/SimonLab/github_app/commits{/sha}",
168 | "git_commits_url": "https://api.github.com/repos/SimonLab/github_app/git/commits{/sha}",
169 | "comments_url": "https://api.github.com/repos/SimonLab/github_app/comments{/number}",
170 | "issue_comment_url": "https://api.github.com/repos/SimonLab/github_app/issues/comments{/number}",
171 | "contents_url": "https://api.github.com/repos/SimonLab/github_app/contents/{+path}",
172 | "compare_url": "https://api.github.com/repos/SimonLab/github_app/compare/{base}...{head}",
173 | "merges_url": "https://api.github.com/repos/SimonLab/github_app/merges",
174 | "archive_url": "https://api.github.com/repos/SimonLab/github_app/{archive_format}{/ref}",
175 | "downloads_url": "https://api.github.com/repos/SimonLab/github_app/downloads",
176 | "issues_url": "https://api.github.com/repos/SimonLab/github_app/issues{/number}",
177 | "pulls_url": "https://api.github.com/repos/SimonLab/github_app/pulls{/number}",
178 | "milestones_url": "https://api.github.com/repos/SimonLab/github_app/milestones{/number}",
179 | "notifications_url": "https://api.github.com/repos/SimonLab/github_app/notifications{?since,all,participating}",
180 | "labels_url": "https://api.github.com/repos/SimonLab/github_app/labels{/name}",
181 | "releases_url": "https://api.github.com/repos/SimonLab/github_app/releases{/id}",
182 | "deployments_url": "https://api.github.com/repos/SimonLab/github_app/deployments",
183 | "created_at": "2017-05-30T15:26:26Z",
184 | "updated_at": "2017-05-30T15:36:26Z",
185 | "pushed_at": "2017-06-26T14:03:32Z",
186 | "git_url": "git://github.com/SimonLab/github_app.git",
187 | "ssh_url": "git@github.com:SimonLab/github_app.git",
188 | "clone_url": "https://github.com/SimonLab/github_app.git",
189 | "svn_url": "https://github.com/SimonLab/github_app",
190 | "homepage": null,
191 | "size": 63,
192 | "stargazers_count": 0,
193 | "watchers_count": 0,
194 | "language": "Elixir",
195 | "has_issues": true,
196 | "has_projects": true,
197 | "has_downloads": true,
198 | "has_wiki": true,
199 | "has_pages": false,
200 | "forks_count": 0,
201 | "mirror_url": null,
202 | "archived": false,
203 | "open_issues_count": 14,
204 | "license": null,
205 | "forks": 0,
206 | "open_issues": 14,
207 | "watchers": 0,
208 | "default_branch": "b1"
209 | },
210 | "sender": {
211 | "login": "SimonLab",
212 | "id": 6057298,
213 | "avatar_url": "https://avatars2.githubusercontent.com/u/6057298?v=4",
214 | "gravatar_id": "",
215 | "url": "https://api.github.com/users/SimonLab",
216 | "html_url": "https://github.com/SimonLab",
217 | "followers_url": "https://api.github.com/users/SimonLab/followers",
218 | "following_url": "https://api.github.com/users/SimonLab/following{/other_user}",
219 | "gists_url": "https://api.github.com/users/SimonLab/gists{/gist_id}",
220 | "starred_url": "https://api.github.com/users/SimonLab/starred{/owner}{/repo}",
221 | "subscriptions_url": "https://api.github.com/users/SimonLab/subscriptions",
222 | "organizations_url": "https://api.github.com/users/SimonLab/orgs",
223 | "repos_url": "https://api.github.com/users/SimonLab/repos",
224 | "events_url": "https://api.github.com/users/SimonLab/events{/privacy}",
225 | "received_events_url": "https://api.github.com/users/SimonLab/received_events",
226 | "type": "User",
227 | "site_admin": false
228 | },
229 | "installation": {
230 | "id": 92693
231 | }
232 | }
233 |
--------------------------------------------------------------------------------
30 | <%= version.author %> 31 | commented <%= format_date version.inserted_at %> 32 |
33 | 34 | <%= display_markdown(@comments_text["#{version.id}"]) %> 35 | 36 |