Skip to content

Commit

Permalink
Add GitHubAppInstallation
Browse files Browse the repository at this point in the history
- model, state transition, and tests
- views and tests
- controller, policies, tests
- API blueprint docs
  • Loading branch information
joshsmith committed Jun 26, 2017
1 parent 713244b commit 3280849
Show file tree
Hide file tree
Showing 19 changed files with 695 additions and 53 deletions.
152 changes: 152 additions & 0 deletions blueprint/api.apib
Original file line number Diff line number Diff line change
Expand Up @@ -379,6 +379,111 @@ To delete a donation goal the user must be a site admin or the owner of the refe

+ Attributes (Record Not Found Response)

# Group Github App Installations

This endpoint is used for creating, updating, and retrieving information about a GitHub App installation. See the GitHub API documentation for more details about [the installation process of GitHub Apps](https://developer.github.com/apps/building-integrations/setting-up-a-new-integration/about-choosing-an-integration-type/#installation-processes-for-integrations).

To simplify, Code Corps has a GitHub App which is installed to a GitHub user's or GitHub organization's account _on GitHub_. This grants Code Corps read and write access to the GitHub API on behalf of that user or organization.

You'll notice project and user relationships, which are only useful when setting up an installation for the first time. When GitHub redirects the user back to Code Corps after installing the GitHub App, our client only has access to the installation's `installation_id` in a query parameter. Because of this, before we direct the user to GitHub to install our App, we need to create a GithubAppInstallation record that keeps track of the user who requested it, the project it was requested on, and mark the installation as `initiated_on_code_corps`.

Note that this does not mean there's actually an installation yet! The user hasn't been to GitHub. We're just keeping track of that installation for later.

Once the user installs the App on GitHub, GitHub will send an `installation` webhook to our API. The payload includes a `sender` key containing the GitHub account (along with its `id`) that performed the installation. The user is then automatically redirected back to Code Corps.

Once the user is back on Code Corps, we want to redirect the user back to the target project. Luckily, the `installation_id` GitHub provided in the query parameter yields enough information to match it to our authenticated user's GithubAppInstallation record created in the previous step.

We match:
- the `installation_id` in the query parameter
- the `installation` webhook's `installation_id` and `sender`'s account `id`
- our user's GitHub account `id`

These pieces of information are enough to tell us who installed the application. And because we stored the target project for this first installation, we can redirect our user. The API may need to do some processing work to complete the setup, but once the `state` has moved to `processed`, the client can redirect to the project's integrations page using the project relationship.

Keep in mind that an installation could be used across multiple projects. And because an installation can be installed at the _organization_ level on GitHub, that means the user who requested the installation is likely not be the only user with permissions on GitHub to modify or even uninstall the GitHub App. The user relationship specified here is _only_ useful for the first installation of a user on a project, due solely to the complicated redirection flow detailed above.

## Github App Installations [/github-app-installations]

### Create a Github app installation [POST]

+ Request

+ Attributes (Github App Installation Create Request)

+ Headers

Accept: application/vnd.api+json
Authorization: Bearer {token}

+ Response 201 (application/vnd.api+json; charset=utf-8)

+ Attributes (Github App Installation Response)

+ Response 401 (application/vnd.api+json; charset=utf-8)

+ Attributes (JSON Web Token Invalid Response)

+ Response 403 (application/vnd.api+json; charset=utf-8)

+ Attributes (Forbidden Response)

### Filter by id [GET /github-app-installations{?filter[id]}]

+ Request

+ Attributes

+ `filter[id]`: `1,2,3` (string, required) - Comma separated string of `ids` to filter by.

+ Headers

Accept: application/vnd.api+json

+ Response 200 (application/vnd.api+json; charset=utf-8)

+ Attributes (Github App Installations Response)

## Github App Installation [/github-app-installations/{id}]

+ Parameters

+ id (number, required)

### Retrieve a Github app installation [GET]

+ Request

+ Headers

Accept: application/vnd.api+json

+ Response 200 (application/vnd.api+json; charset=utf-8)

+ Attributes (Github App Installation Response)

### Update a Github app installation [PATCH]

+ Request

+ Attributes (Github App Installation Update Request)

+ Headers

Accept: application/vnd.api+json
Authorization: Bearer {token}

+ Response 201 (application/vnd.api+json; charset=utf-8)

+ Attributes (Github App Installation Response)

+ Response 401 (application/vnd.api+json; charset=utf-8)

+ Attributes (JSON Web Token Invalid Response)

+ Response 403 (application/vnd.api+json; charset=utf-8)

+ Attributes (Forbidden Response)

# Group Organizations

This endpoint retrieves Organizations on Code Corps. Organizations usually have one or more Projects.
Expand Down Expand Up @@ -2591,6 +2696,53 @@ The platform stores Stripe customers and cards so they can be reused across diff
+ status: 403 (number) - HTTP status code
+ title: `403 Forbidden` (string)

## Github App Installation Attributes (object)
+ `github-id`: 12345 (number) - The id of the application installation on GitHub
+ `inserted-at`: `2016-07-08T03:03:51.967Z` (string)
+ installed: true (boolean) - Whether the installation is currently installed. The user can potentially uninstall from GitHub, for example.
+ state: `processed` (enum[string])
+ Members
+ `initiated_on_code_corps`
+ `initiated_on_github`
+ `processing`
+ `processed`
+ `unmatched_user`
+ `updated-at`: `2016-07-08T03:03:51.967Z` (string)

## Github App Installation Create Request (object)
+ type: `github-app-installation` (string, required)
+ attributes(Github App Installation Attributes)
+ relationships
+ project
+ data(Project Resource Identifier)
+ user
+ data(User Resource Identifier)

## Github App Installation Resource (object)
+ include Github App Installation Resource Identifier
+ attributes(Github App Installation Attributes)
+ relationships
+ project
+ data(Project Resource Identifier)
+ user
+ data(User Resource Identifier)

## Github App Installation Resource Identifier (object)
+ id: `1` (string, required)
+ type: `github-app-installation` (string, required)

## Github App Installation Response (object)
+ data(Github App Installation Resource)
+ include JSON API Version

## Github App Installations Response (object)
+ data(array[Github App Installation Resource])
+ include JSON API Version

## Github App Installation Update Request (object)
+ include Project User Resource Identifier
+ attributes(Project User Attributes)

## No Content Response (object)

{}
Expand Down
32 changes: 32 additions & 0 deletions lib/code_corps/transition/github_app_installation_state.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
defmodule CodeCorps.Transition.GithubAppInstallationState do
@moduledoc """
Governs the transitioning from one state to the next.
The possible `state` values are:
- `initiated_on_code_corps` - The user clicks a button or link in the Code Corps UI and is redirected to GitHub to install. This process creates the `GithubAppInstallation` record before redirecting.
- `initiated_on_github` - We receive an installation webhook with a matching user, but an installation was never created on Code Corps. This can happen because GitHub does not assume an app will _only_ be installed at a starting point outside GitHub.
- `processing` - When the installation webhook is received, was matched, and is now processing.
- `processed` - The integration process is completed. There is now a working installation attached to the project.
- `unmatched_user` - When we receive a webhook but there is no Code Corps user matching the given GitHub user's GitHub `id`. This is sent in the `sender` key of the `installation` event by GitHub.
It is possible to resolve more problematic states like `unmatched_user` when the user provides more information, e.g. connecting their GitHub account. The transitions below allow for some of these edge cases.
"""

def next(nil, "initiated_on_code_corps"), do: {:ok, "initiated_on_code_corps"}

def next(current_state, nil), do: {:ok, current_state}

def next("initiated_on_code_corps", "processing"), do: {:ok, "processing"}
def next("initiated_on_code_corps", "processed"), do: {:ok, "processed"}
def next("initiated_on_code_corps", "unmatched_user"), do: {:ok, "unmatched_user"}

def next("processing", "processed"), do: {:ok, "processed"}
def next("processing", "unmatched_user"), do: {:ok, "unmatched_user"}

def next("unmatched_user", "processing"), do: {:ok, "processing"}
def next("unmatched_user", "processed"), do: {:ok, "processed"}

def next(current_state, next_state) when current_state == next_state, do: {:ok, next_state}
def next(current_state, next_state), do: {:error, "invalid transition to #{next_state} from #{current_state}"}
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
defmodule CodeCorps.Repo.Migrations.CreateGithubAppInstallation do
use Ecto.Migration

def change do
create table(:github_app_installations) do
add :github_id, :integer
add :installed, :boolean, default: true
add :state, :string

add :project_id, references(:projects, on_delete: :nothing)
add :user_id, references(:users, on_delete: :nothing)

timestamps()
end

create index(:github_app_installations, [:project_id])
create index(:github_app_installations, [:user_id])
end
end
82 changes: 81 additions & 1 deletion priv/repo/structure.sql
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,41 @@ CREATE SEQUENCE donation_goals_id_seq
ALTER SEQUENCE donation_goals_id_seq OWNED BY donation_goals.id;


--
-- Name: github_app_installations; Type: TABLE; Schema: public; Owner: -
--

CREATE TABLE github_app_installations (
id integer NOT NULL,
github_id integer,
installed boolean DEFAULT true,
state character varying(255),
project_id integer,
user_id integer,
inserted_at timestamp without time zone NOT NULL,
updated_at timestamp without time zone NOT NULL
);


--
-- Name: github_app_installations_id_seq; Type: SEQUENCE; Schema: public; Owner: -
--

CREATE SEQUENCE github_app_installations_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;


--
-- Name: github_app_installations_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: -
--

ALTER SEQUENCE github_app_installations_id_seq OWNED BY github_app_installations.id;


--
-- Name: organizations; Type: TABLE; Schema: public; Owner: -
--
Expand Down Expand Up @@ -1379,6 +1414,13 @@ ALTER TABLE ONLY comments ALTER COLUMN id SET DEFAULT nextval('comments_id_seq':
ALTER TABLE ONLY donation_goals ALTER COLUMN id SET DEFAULT nextval('donation_goals_id_seq'::regclass);


--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--

ALTER TABLE ONLY github_app_installations ALTER COLUMN id SET DEFAULT nextval('github_app_installations_id_seq'::regclass);


--
-- Name: id; Type: DEFAULT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -1621,6 +1663,14 @@ ALTER TABLE ONLY donation_goals
ADD CONSTRAINT donation_goals_pkey PRIMARY KEY (id);


--
-- Name: github_app_installations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY github_app_installations
ADD CONSTRAINT github_app_installations_pkey PRIMARY KEY (id);


--
-- Name: organizations_pkey; Type: CONSTRAINT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -1856,6 +1906,20 @@ CREATE UNIQUE INDEX donation_goals_current_unique_to_project ON donation_goals U
CREATE INDEX donation_goals_project_id_index ON donation_goals USING btree (project_id);


--
-- Name: github_app_installations_project_id_index; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX github_app_installations_project_id_index ON github_app_installations USING btree (project_id);


--
-- Name: github_app_installations_user_id_index; Type: INDEX; Schema: public; Owner: -
--

CREATE INDEX github_app_installations_user_id_index ON github_app_installations USING btree (user_id);


--
-- Name: index_categories_on_slug; Type: INDEX; Schema: public; Owner: -
--
Expand Down Expand Up @@ -2238,6 +2302,22 @@ ALTER TABLE ONLY donation_goals
ADD CONSTRAINT donation_goals_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id);


--
-- Name: github_app_installations_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY github_app_installations
ADD CONSTRAINT github_app_installations_project_id_fkey FOREIGN KEY (project_id) REFERENCES projects(id);


--
-- Name: github_app_installations_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--

ALTER TABLE ONLY github_app_installations
ADD CONSTRAINT github_app_installations_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id);


--
-- Name: organizations_owner_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: -
--
Expand Down Expand Up @@ -2602,5 +2682,5 @@ ALTER TABLE ONLY user_tasks
-- PostgreSQL database dump complete
--

INSERT INTO "schema_migrations" (version) VALUES (20160723215749), (20160804000000), (20160804001111), (20160805132301), (20160805203929), (20160808143454), (20160809214736), (20160810124357), (20160815125009), (20160815143002), (20160816020347), (20160816034021), (20160817220118), (20160818000944), (20160818132546), (20160820113856), (20160820164905), (20160822002438), (20160822004056), (20160822011624), (20160822020401), (20160822044612), (20160830081224), (20160830224802), (20160911233738), (20160912002705), (20160912145957), (20160918003206), (20160928232404), (20161003185918), (20161019090945), (20161019110737), (20161020144622), (20161021131026), (20161031001615), (20161121005339), (20161121014050), (20161121043941), (20161121045709), (20161122015942), (20161123081114), (20161123150943), (20161124085742), (20161125200620), (20161126045705), (20161127054559), (20161205024856), (20161207112519), (20161209192504), (20161212005641), (20161214005935), (20161215052051), (20161216051447), (20161218005913), (20161219160401), (20161219163909), (20161220141753), (20161221085759), (20161226213600), (20161231063614), (20170102130055), (20170102181053), (20170104113708), (20170104212623), (20170104235423), (20170106013143), (20170115035159), (20170115230549), (20170121014100), (20170131234029), (20170201014901), (20170201025454), (20170201035458), (20170201183258), (20170220032224), (20170224233516), (20170226050552), (20170228085250), (20170308214128), (20170308220713), (20170308222552), (20170313130611), (20170318032449), (20170318082740), (20170324194827), (20170424215355), (20170501225441), (20170526095401), (20170602000208);
INSERT INTO "schema_migrations" (version) VALUES (20160723215749), (20160804000000), (20160804001111), (20160805132301), (20160805203929), (20160808143454), (20160809214736), (20160810124357), (20160815125009), (20160815143002), (20160816020347), (20160816034021), (20160817220118), (20160818000944), (20160818132546), (20160820113856), (20160820164905), (20160822002438), (20160822004056), (20160822011624), (20160822020401), (20160822044612), (20160830081224), (20160830224802), (20160911233738), (20160912002705), (20160912145957), (20160918003206), (20160928232404), (20161003185918), (20161019090945), (20161019110737), (20161020144622), (20161021131026), (20161031001615), (20161121005339), (20161121014050), (20161121043941), (20161121045709), (20161122015942), (20161123081114), (20161123150943), (20161124085742), (20161125200620), (20161126045705), (20161127054559), (20161205024856), (20161207112519), (20161209192504), (20161212005641), (20161214005935), (20161215052051), (20161216051447), (20161218005913), (20161219160401), (20161219163909), (20161220141753), (20161221085759), (20161226213600), (20161231063614), (20170102130055), (20170102181053), (20170104113708), (20170104212623), (20170104235423), (20170106013143), (20170115035159), (20170115230549), (20170121014100), (20170131234029), (20170201014901), (20170201025454), (20170201035458), (20170201183258), (20170220032224), (20170224233516), (20170226050552), (20170228085250), (20170308214128), (20170308220713), (20170308222552), (20170313130611), (20170318032449), (20170318082740), (20170324194827), (20170424215355), (20170501225441), (20170526095401), (20170602000208), (20170622205732);

0 comments on commit 3280849

Please sign in to comment.