bullet_train-api 1.2.4 → 1.2.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a6ade62a135239a02645844a31778eb34be494d8d131510318af2d9fa9c2b8b
4
- data.tar.gz: 915b02cc7c7fac7f0cc3ef27440be6a8799d22da2fe08ae55175b3d3747b0191
3
+ metadata.gz: f59f190e9824ebdddbfd66d07c2cd778024d9e4bd22ad5737325105b61548e06
4
+ data.tar.gz: a7be7d343a2eb2b774b70491a6a6380a1d9525e0643495488b1f7d68efa4743e
5
5
  SHA512:
6
- metadata.gz: e5642cd39f8e915e71d178ee09395025b70c80483a2807de87b88bbc542e39bb58ad6e3b33f0ad739f3d1f1d00802a660b78dd6c367384da9a74c9e4966e56db
7
- data.tar.gz: 37d861c517b06e93cf67249ac91047b30eaa9c5e19a509a7a211abe4cf89a1ed31170f3c800dad20402c840ccfa80727bdf83cf2eb72e4b9b4b8c48cba9739ef
6
+ metadata.gz: 570d41d45f2b17e401c2ece3c93acd45ba8463e84332739c033d6eb5cac787b63ef820b016dbbebb944fb737788a704e72b7aec57919375e8077ed2c835794d7
7
+ data.tar.gz: 6132f0a3d6d3438591b36beaf6451527936046bcccf7fef96175511eda1df95daa34e37f7c962b975564603388541d54eb12805cad0f1cfad2b931b8f2bbc87a
@@ -30,6 +30,8 @@ module Api::V1::Users::ControllerBase
30
30
  member_actions: (defined?(MEMBER_ACTIONS) ? MEMBER_ACTIONS : []),
31
31
  collection_actions: (defined?(COLLECTION_ACTIONS) ? COLLECTION_ACTIONS : [])
32
32
 
33
+ prepend_before_action :resolve_me
34
+
33
35
  private
34
36
 
35
37
  include StrongParameters
@@ -39,6 +41,12 @@ module Api::V1::Users::ControllerBase
39
41
  def index
40
42
  end
41
43
 
44
+ def resolve_me
45
+ if current_user && params[:id]&.downcase == "me"
46
+ params[:id] = current_user.id
47
+ end
48
+ end
49
+
42
50
  # GET /api/v1/users/:id
43
51
  def show
44
52
  end
@@ -28,5 +28,18 @@ class Platform::AccessToken < ApplicationRecord
28
28
  def label_string
29
29
  description
30
30
  end
31
+
32
+ def system_level?
33
+ return false unless application
34
+ !application.team_id
35
+ end
36
+
37
+ def description
38
+ if system_level?
39
+ application.name
40
+ else
41
+ super
42
+ end
43
+ end
31
44
  # 🚅 add methods above.
32
45
  end
@@ -4,7 +4,7 @@ class Platform::Application < ApplicationRecord
4
4
  include Doorkeeper::Orm::ActiveRecord::Mixins::Application
5
5
  # 🚅 add concerns above.
6
6
 
7
- belongs_to :team
7
+ belongs_to :team, optional: true
8
8
  # 🚅 add belongs_to associations above.
9
9
 
10
10
  # 🚅 add has_many associations above.
@@ -2,5 +2,6 @@
2
2
  <% p.content_for :title, t('.section') %>
3
3
  <% p.content_for :body do %>
4
4
  <%= render 'index', applications: @applications %>
5
+ <%= render 'account/platform/access_tokens/index', access_tokens: @team.platform_agent_access_tokens, context: @team %>
5
6
  <% end %>
6
7
  <% end %>
@@ -0,0 +1,42 @@
1
+ <%= render 'account/shared/workflow/box' do |p| %>
2
+ <% p.content_for :title, t('.header', application_name: @application.name) %>
3
+ <% p.content_for :body do %>
4
+ <ul class="space-y" data-turbo="false">
5
+ <% @teams.each do |team| %>
6
+ <li class="bg-white border overflow-hidden sm:rounded-md dark:bg-sealBlue-400">
7
+ <% body = capture do %>
8
+ <div class="px-4 py-4 flex items-center sm:pl-8 sm:pr-6">
9
+ <div class="min-w-0 flex-1 sm:flex sm:items-center sm:justify-between">
10
+ <div class="flex text-xl font-semibold text-blue uppercase group-hover:text-blue-dark tracking-widest dark:text-white">
11
+ <%= team.name %>
12
+ </div>
13
+ <% unless can? :connect, team %>
14
+ <div class="ml-5 flex-shrink-0 text-gray-400">
15
+ <%= t(".not_allowed") %>
16
+ </div>
17
+ <% end %>
18
+ </div>
19
+ <% if can? :connect, team %>
20
+ <div class="ml-5 flex-shrink-0">
21
+ <svg class="h-5 w-5 text-gray-400" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
22
+ <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
23
+ </svg>
24
+ </div>
25
+ <% end %>
26
+ </div>
27
+ <% end %>
28
+
29
+ <% if can? :connect, team %>
30
+ <%= link_to request.url + "&team_id=#{team.id}", class: "group block hover:bg-gray-50 dark:hover:bg-sealBlue-400 dark:text-sealBlue-800" do %>
31
+ <%= body %>
32
+ <% end %>
33
+ <% else %>
34
+ <div class="block dark:text-sealBlue-800">
35
+ <%= body %>
36
+ </div>
37
+ <% end %>
38
+ </li>
39
+ <% end %>
40
+ </ul>
41
+ <% end %>
42
+ <% end %>
@@ -67,6 +67,9 @@ en:
67
67
  platform/application:
68
68
  header: API Access Tokens
69
69
  description: You can use Access Tokens to allow %{application_name} to make requests to the API.
70
+ team:
71
+ header: Platform Connections
72
+ description: The following Platform Applications are connected to your account. The Access Tokens issued to these Platform Applications are available below in case you need to debug these integrations.
70
73
  fields: *fields
71
74
  buttons: *buttons
72
75
  show:
@@ -0,0 +1,7 @@
1
+ en:
2
+ account:
3
+ platform:
4
+ connections:
5
+ new:
6
+ header: "Connect %{application_name} to Team"
7
+ not_allowed: Not Allowed
@@ -1,5 +1,5 @@
1
1
  module BulletTrain
2
2
  module Api
3
- VERSION = "1.2.4"
3
+ VERSION = "1.2.6"
4
4
  end
5
5
  end
@@ -1,6 +1,7 @@
1
1
  require "bullet_train/api/version"
2
2
  require "bullet_train/api/engine"
3
3
  require "bullet_train/api/strong_parameters_reporter"
4
+ require "bullet_train/platform/connection_workflow"
4
5
 
5
6
  # require "wine_bouncer"
6
7
  require "pagy"
@@ -0,0 +1,64 @@
1
+ require "bullet_train/platform"
2
+
3
+ class BulletTrain::Platform::ConnectionWorkflow
4
+ def to_proc
5
+ proc do
6
+ # Load the platform application in question.
7
+ # TODO Do we need to check the client secret or does Doorkeeper do that for us?
8
+ @application = Platform::Application.find_by(uid: params[:client_id])
9
+
10
+ # If the user is current signed in.
11
+ if current_user
12
+ # If the client application is opting into a team-level connection instead of a user-level connection, they have
13
+ # to select a team.
14
+ if params[:new_installation]
15
+ # If they selected a team on the team selection page.
16
+ if params[:team_id]
17
+ # Load the selected team.
18
+ team = Team.find(params[:team_id])
19
+
20
+ # Throw an error if they aren't allowed to create connections on this team.
21
+ authorize! :connect, team
22
+
23
+ # Create a faux membership and user that represent this connection.
24
+ # We have to do this because all our permissions are based on users, so team-level connections need a user.
25
+ faux_password = SecureRandom.hex
26
+ faux_user = User.create(
27
+ email: "noreply+#{SecureRandom.hex}@bullettrain.co",
28
+ password: faux_password,
29
+ password_confirmation: faux_password,
30
+ platform_agent_of: @application,
31
+ first_name: @application.name
32
+ )
33
+
34
+ # TODO I think we can get rid of `platform_agent` because we have `platform_agent_of_id`.
35
+ faux_membership = team.memberships.create(
36
+ user: faux_user,
37
+ platform_agent: true,
38
+ platform_agent_of: @application,
39
+ added_by: team.memberships.find_by(user: current_user)
40
+ )
41
+
42
+ faux_membership.roles << Role.admin
43
+
44
+ # We're done! Return the user, it'll be associated with the access grant and subsequent access token.
45
+ faux_user
46
+ else
47
+ # Show them a list of all their teams.
48
+ # We'll disable the teams they can't create connections for in the view.
49
+ @teams = current_user.teams
50
+
51
+ render "account/platform/connections/new"
52
+ end
53
+ else
54
+ # If the client application isn't specifically opting into a team-level installation, just connect on behalf of the user.
55
+ current_user
56
+ end
57
+ else
58
+ # If they're not signed in, redirect them to the sign in page and set a return URL via params.
59
+ # This is a crazy workaround for the fact that Safari doesn't let us create a session at the same time we redirect.
60
+ redirect_to new_user_session_path(return_url: request.url)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,2 @@
1
+ module BulletTrain::Platform
2
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bullet_train-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.4
4
+ version: 1.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Culver
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-12-17 00:00:00.000000000 Z
11
+ date: 2022-12-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: standard
@@ -162,6 +162,7 @@ files:
162
162
  - app/views/account/platform/applications/new.html.erb
163
163
  - app/views/account/platform/applications/show.html.erb
164
164
  - app/views/account/platform/applications/show.json.jbuilder
165
+ - app/views/account/platform/connections/new.html.erb
165
166
  - app/views/api/v1/open_api/shared/_paths.yaml.erb
166
167
  - app/views/api/v1/open_api/teams/_paths.yaml.erb
167
168
  - app/views/api/v1/open_api/users/_paths.yaml.erb
@@ -186,13 +187,14 @@ files:
186
187
  - config/locales/en/me.en.yml
187
188
  - config/locales/en/platform/access_tokens.en.yml
188
189
  - config/locales/en/platform/applications.en.yml
190
+ - config/locales/en/platform/connections.en.yml
189
191
  - config/routes.rb
190
- - docs/api.md
191
- - docs/api/versioning.md
192
192
  - lib/bullet_train/api.rb
193
193
  - lib/bullet_train/api/engine.rb
194
194
  - lib/bullet_train/api/strong_parameters_reporter.rb
195
195
  - lib/bullet_train/api/version.rb
196
+ - lib/bullet_train/platform.rb
197
+ - lib/bullet_train/platform/connection_workflow.rb
196
198
  - lib/tasks/bullet_train/api_tasks.rake
197
199
  homepage: https://github.com/bullet-train-co/bullet_train-api
198
200
  licenses:
@@ -1,63 +0,0 @@
1
- # API Versioning
2
- Bullet Train's API layer is designed to help support the need of software developers to evolve their API over time while continuing to maintain support for versions of the API that users have already built against.
3
-
4
- ## What is API versioning?
5
- By default, Bullet Train will build out a "V1" version of your API. The version number is intended to represent a contract with your users that as long as they're hitting `/api/v1` endpoints, the structure of URLs, requests, and responses won't change in a way that will break the integrations they've created.
6
-
7
- If a change to the API would break the established contract, we want to bump the API version number so we can differentiate between developers building against the latest version of the API (e.g. "V2") and developers who wrote code against the earlier version of the API (e.g. "V1"). This allows us the opportunity to ensure that older versions of the API continue to work as previously expected by the earlier developers.
8
-
9
- ## When should you take advantage of API versioning?
10
- You want to bump API versions as sparingly as possible. Even with all the tooling Bullet Train provides, maintaining backwards compatibility of older API versions comes at an ongoing cost. Generally speaking, you should only bump your API version when a customer is already using an API endpoint and you're making changes to the structure of your domain model that are not strictly additive and will break the established contract.
11
-
12
- Importantly, if the changes you're making to your domain model are only additive, you don't need to bump your API version. Users shouldn't care that you're adding new attributes or new endpoints to your API, just as long as the ones they're already using don't change in a way that is breaking for them.
13
-
14
- ## Background
15
- By default, the following components in your API are created in versioned namespaces:
16
-
17
- - API controllers are in `app/controllers/api/v1` and live in an `Api::V1` module.
18
- - JSON views are in `app/controllers/api/v1`.
19
- - Routes are in `config/routes/api/v1.rb`.
20
- - Tests are in `test/controllers/api/v1` and live in an `Api::V1` module.
21
-
22
- > It's also impotant to keep in mind that some dependencies of your API and API tests like models, factories, and permissions are not versioned, but as we'll cover later, this is something our approach helps you work around.
23
-
24
- ## Bumping Your API Version
25
-
26
- ⚠️ You must do this _before_ making the breaking changes to your API.
27
-
28
- If you're in a situation where you know you need to bump your API version to help lock-in a backward compatible version of your API, you can simply run:
29
-
30
- ```
31
- rake bullet_train:api:bump_version
32
- ```
33
-
34
- > TODO This Rake task doesn't exist yet.
35
-
36
- ## What happens when you bump an API version?
37
- When you bump your API version, all of the files and directories that are namespaced with the API version number will be duplicated into a new namespace for the new API version number.
38
-
39
- For example, when bumping from "V1" to "V2":
40
-
41
- - A copy of all the API controllers in `app/controllers/api/v1` are copied into `app/controllers/api/v2`.
42
- - A copy of all the JSON views in `app/views/api/v1` are copied into `app/views/api/v2`.
43
- - A copy of all the routes in `config/routes/api/v1.rb` are copied into `config/routes/api/v2.rb`.
44
- - A copy of all the tests in `test/controllers/api/v1` are copied into `test/controllers/api/v2`.
45
-
46
- We also bump the value of `BulletTrain::Api.current_version` in `config/initializers/api.rb` so tools like Super Scaffolding know which version of your API to update going forward.
47
-
48
- ## How does this help?
49
- As a baseline, keeping a wholesale copy of the versioned API components helps lock in their behavior and protect them from change going forward. It's not a silver bullet, since unversioned dependencies (like your model, factories, and permissions) can still affect the behavior of these versioned API components, but even in that case these copied files give us a place where we can implement the logic that helps older versions of the API continue to operate even as unversioned components like our domain model continue changing.
50
-
51
- ### Versioned API Tests
52
- By versioning our API tests, we lock in a copy of what the assumptions were for older versions of the API. Should unversioned dependencies like our domain model change in ways that break earlier versions of our API, the test suite will let us know and help us figure out when we've implemented the appropriate logic in the older version of the API controller to restore the expected behavior for that version of the API.
53
-
54
- ## Advanced Topics
55
-
56
- ### Object-Oriented Inheritance
57
- In order to reduce the surface area of legacy API controllers that you're maintaining, it might make sense in some cases to have an older versioned API controller simply inherit from a newer version or the current version of the same API controller. For example, this might make sense for endpoints that you know didn't have breaking changes across API versions.
58
-
59
- ### Backporting New Features to Legacy API Versions
60
- Typically we'd recommend you use new feature availability to encourage existing API users to upgrade to the latest version of the API. However, in some situations you may really need to make a newer API feature available to a user who is locked into a legacy version of your API for some other endpoint. This is totally fine if the feature is only additive. For example, if you're just adding a newer API endpoint in a legacy version of the API, you can simply have the new API controller in the legacy version of the API inherit from the API controller in the current version of the API.
61
-
62
- ### Pruning Unused Legacy API Endpoints
63
- Maintaining legacy endpoints has a very real cost, so you may choose to identify which endpoints aren't being used on legacy versions of your API and prune them from that version entirely. This has the effect of requiring existing API users to keep their API usage up-to-date before expanding the surface area of usage, which may or may not be desirable for you.
data/docs/api.md DELETED
@@ -1,106 +0,0 @@
1
- # REST API
2
- We believe every SaaS application should have an API and [webhooks](https://github.com/bullet-train-co/bullet_train-base/blob/main/docs/webhooks/outgoing.md) available to users, so Bullet Train aims to help automate the creation of a production-grade REST API using Rails-native tooling and provides a forward-thinking strategy for its long-term maintenance.
3
-
4
- ## Background
5
- Vanilla Rails scaffolding actually provides simple API functionality out-of-the-box: You can append `.json` to the URL of any scaffold and it will render a JSON representation instead of an HTML view. This functionality continues to work in Bullet Train, but our API implementation also builds on this simple baseline using the same tools with additional organization and some new patterns.
6
-
7
- ## Goals
8
-
9
- ### Zero-Effort API
10
- As with vanilla Rails scaffolding, Super Scaffolding automatically generates your API as you scaffold new models, and unlike vanilla Rails scaffolding, it will automatically keep it up-to-date as you scaffold additional attributes onto your models.
11
-
12
- ### Versioning by Default
13
- By separating out and versioning API controllers, views, routes, and tests, Bullet Train provides [a methodology and tooling](/docs/api/versioning.md) to help ensure that once users have built against your API, changes in the structure of your domain model and API don't unexpectedly break existing integrations. You can [read more about API versioning](/docs/api/versioning.md).
14
-
15
- ### Standard Rails Tooling
16
- APIs are built using standard Rails tools like `ActiveController::API`, [Strong Parameters](https://api.rubyonrails.org/classes/ActionController/StrongParameters.html), `config/routes.rb`, and [Jbuilder](https://github.com/rails/jbuilder). Maintaining API endpoints doesn't require special knowledge and feels like regular Rails development.
17
-
18
- ### Outsourced Authentication
19
- In the same way we've adopted [Devise](https://github.com/heartcombo/devise) for best-of-breed and battle-tested authentication on the browser side, we've adopted [Doorkeeper](https://github.com/doorkeeper-gem/doorkeeper) for best-of-breed and battle-tested authentication on the API side.
20
-
21
- ### DRY Authorization Logic
22
- Because our API endpoints are standard Rails controllers, they're able to leverage the exact same [permissions definitions and authorization logic](https://github.com/bullet-train-co/bullet_train-base/blob/main/docs/permissions.md) as our account controllers.
23
-
24
- ## Structure
25
- Where vanilla Rails uses a single controller in `app/controllers` for both in-browser and API requests, Bullet Train splits these into two separate controllers, one in `app/controllers/account` and another in `app/controllers/api/v1`, although a lot of logic is shared between the two.
26
-
27
- API endpoints are defined in three parts:
28
-
29
- 1. Routes are defined in `config/routes/api/v1.rb`.
30
- 2. Controllers are defined in the `app/controllers/api/v1` directory.
31
- 3. Jbuilder views are defined in the `app/views/api/v1` directory.
32
-
33
- ## "API First" and Supporting Account Controllers
34
- As previously mentioned, there is a lot of shared logic between account and API controllers. Importantly, there are a couple of responsbilities that are implemented "API first" in API controllers and then utilized by account controllers.
35
-
36
- ### Strong Parameters
37
- The primary definition of Strong Parameters for a given resource is defined in the most recent version of the API controller and included from there by the account controller. In account controllers, where you might expect to see a Strong Parameters definition, you'll see the following instead:
38
-
39
- ```ruby
40
- include strong_parameters_from_api
41
- ```
42
-
43
- > This may feel counter-intuitive to some developers and you might wonder why we don't flip this around and have the primary definition in the account controller and have the API controller delegate to it. The answer is a pragmatic one: creating and maintaining the defintion of Strong Paramters in the API controller means it gets automatically frozen in time should you ever need to [bump your API version number](/api/docs/versioning.md). We probably _could_ accomplish this if things were the other way around, but it wouldn't happen automatically.
44
-
45
- If by chance there are additional attributes that should be permitted or specific logic that needs to be run as part of the account controller (or inversely, only in the API controller), you can specify that in the controller like so:
46
-
47
- ```ruby
48
- def permitted_fields
49
- [:some_specific_attribute]
50
- end
51
-
52
- def permitted_arrays
53
- {some_collection: []}
54
- end
55
-
56
- def process_params(strong_params)
57
- assign_checkboxes(strong_params, :some_checkboxes)
58
- strong_params
59
- end
60
- ```
61
-
62
- ### Delegating `.json` View Rendering on Account Controllers
63
-
64
- In Bullet Train, when you append `.json` to an account URL, the account controller doesn't actually have any `.json.jbuilder` templates in its view directory within `app/views/account`. Instead, by default the controller is configured to delegate the JSON rendering to the corresponding Jbuilder templates in the most recent version of the API, like so:
65
-
66
- ```ruby
67
- # GET /account/projects/:id or /account/projects/:id.json
68
- def show
69
- delegate_json_to_api
70
- end
71
- ```
72
-
73
- ## Usage Example
74
- First, provision a platform application in section titled "Your Applications" in the "Developers" menu of the application. When you create a new platform application, an access token that doesn't automatically expire will be automatically provisioned along with it. You can then use the access token to hit the API, as seen in the following Ruby-based example:
75
-
76
- ```ruby
77
- require 'net/http'
78
- require 'uri'
79
-
80
- # Configure an API client.
81
- client = Net::HTTP.new('localhost', 3000)
82
-
83
- headers = {
84
- "Content-Type" => "application/json",
85
- "Authorization" => "Bearer GfNLkDmzOTqAacR1Kqv0VJo7ft2TT-S_p8C6zPDBFhg"
86
- }
87
-
88
- # Fetch the team details.
89
- response = client.get("/api/v1/teams/1", headers)
90
-
91
- # Parse response.
92
- team = JSON.parse(response.body)
93
-
94
- # Update team name.
95
- team["name"] = "Updated Team Name"
96
-
97
- # Push the update to the API.
98
- # Note that the team attributes are nested under a `team` key in the JSON body.
99
- response = client.patch("/api/v1/teams/1", {team: team}.to_json, headers)
100
- ```
101
-
102
- ## Advanced Topics
103
- - [API Versioning](/docs/api/versioning.md)
104
-
105
- ## A Note About Other Serializers and API Frameworks
106
- In early versions of Bullet Train we made the decision to adopt a specific serialization library, [ActiveModelSerializers](https://github.com/rails-api/active_model_serializers) and in subsequent versions we went as far as to adopt an entire third-party framework ([Grape](https://github.com/ruby-grape/grape)) and a third-party API specification ([JSON:API](https://jsonapi.org)). We now consider it out-of-scope to try and make such decisions on behalf of developers. Support for them in Bullet Train applications and in Super Scaffolding could be created by third-parties.