tramway-api 1.0.2.1 → 1.1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +272 -11
- data/app/controllers/tramway/api/application_controller.rb +14 -6
- data/app/controllers/tramway/api/v1/records_controller.rb +1 -0
- data/app/controllers/tramway/api/v1/users_controller.rb +8 -1
- data/lib/tramway/api.rb +11 -3
- data/lib/tramway/api/version.rb +1 -1
- metadata +3 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9e7b86a5c04f4c4eb43219e86a6317ca0eac3b8b47088dc25b9e0d4ea9de0703
|
4
|
+
data.tar.gz: 745e03b27aa436a26467efa95f2e2f2afa6db7e8483ed7e824078462f1b29a77
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9868cc378cdea23161d5cc078e8542e6a9bf98e4cc7a6b3d8395242cdeb350746a3ec6c56cb1794e50f6ca219e77a2c0d891d0f69c8922bd329c11d673b0c47d
|
7
|
+
data.tar.gz: d05ae85c794ee056694d0fd810a41f5f9c41672e433645b3bac485f222d12bd2585ecbd3d3729be028a4b755b06115e9ac438f8031c564040f36ac6c7ce23905
|
data/README.md
CHANGED
@@ -1,26 +1,287 @@
|
|
1
1
|
# Tramway::Api
|
2
|
-
|
2
|
+
|
3
|
+
## English Readme
|
4
|
+
|
5
|
+
coming soon...
|
6
|
+
|
7
|
+
## Russian Readme
|
8
|
+
|
9
|
+
Простой в использовании, легко устанавливаемый и плохо кастомизируемый Rails-engine с готовым CRUD через API.
|
10
|
+
|
11
|
+
Принцип работы. В приложении заранее указывается для каких моделей создаётся API CRUD. Идея проекта - возможность быстрой выкатки API, с возможностью в последствии избавиться от Tramway API, когда ваш проект становится сложнее.
|
12
|
+
|
13
|
+
Гем НЕ манкипатчит стандартные классы и поведение Rails! Именно по этой причине было решено реализовать как Rails-engine, который в последствии можно просто и легко удалить из проекта.
|
14
|
+
|
15
|
+
Фичи:
|
16
|
+
|
17
|
+
* готовый CRUD для определённых разработчиком моделей
|
18
|
+
* сохранение истории изменений записей (используется гем `audited`)
|
19
|
+
* поддержка загрузки файлов (используется `carrierwave`)
|
20
|
+
* аутентификация пользователя через JWT (используется `knock`)
|
21
|
+
* поддержка по умолчанию JSON API спецификации (через гем `active_model_serializers`)
|
22
|
+
* мягкое удаление записей по умолчанию
|
23
|
+
* поддержка коммуникации по уникальному uid объектов, чтобы не публиковать ID в базе
|
24
|
+
|
25
|
+
Ограничения:
|
26
|
+
|
27
|
+
* только с ActiveRecord
|
28
|
+
* только с версией Rails 5.1.* (поддержка 5.2 вскоре будет реализована автором гема, поддержка автором Rails 6 начнётся с версии 6.1. По религиозным автор не использует Rails версий *.0.*
|
29
|
+
* Ruby >= 2.3
|
30
|
+
* все модели, которые будут использованы гемом должны наследоваться от `Tramway::Core::ApplicationRecord`
|
31
|
+
* все модели, которые будут использованы гемом должны иметь атрибут `state`, типа `string` или `text`. Этот атрибут нужен для реализации мягкого удаления. Полное удаление записей из базы не поддерживается
|
32
|
+
* все модели, которые будут использованы гемом должны иметь атрибут
|
33
|
+
|
34
|
+
Недостатки, которые будут вскоре ликвидированы:
|
35
|
+
|
36
|
+
* ядро `tramway-core` подгружает в себя ненужных для API гемов (недостаток не имеет смысла в случае использования вместе с этим решением гема `tramway-admin`):
|
37
|
+
* bootstrap
|
38
|
+
* font_awesome5_rails
|
39
|
+
* haml
|
40
|
+
* требуется ручное добавление требуемых для работы гемов
|
41
|
+
```ruby
|
42
|
+
gem 'active_model_serializers', '0.10.5'
|
43
|
+
gem 'tramway-core'
|
44
|
+
gem 'state_machine', github: 'seuros/state_machine'
|
45
|
+
gem 'knock'
|
46
|
+
```
|
3
47
|
|
4
48
|
## Usage
|
5
|
-
How to use my plugin.
|
6
49
|
|
7
|
-
|
8
|
-
|
50
|
+
```
|
51
|
+
rails new tramway_api_sample
|
52
|
+
```
|
53
|
+
|
54
|
+
*Gemfile*
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
gem 'tramway-api', '>= 1.1.0.1'
|
58
|
+
gem 'active_model_serializers', '0.10.5'
|
59
|
+
gem 'tramway-core'
|
60
|
+
gem 'state_machine', github: 'seuros/state_machine'
|
61
|
+
gem 'knock'
|
62
|
+
```
|
63
|
+
|
64
|
+
Run `bundle install`
|
65
|
+
|
66
|
+
Then generate User (you use another name, it's just an example) model
|
67
|
+
|
68
|
+
```
|
69
|
+
rails g model user email:text password_digest:text username:text state:text uid:text
|
70
|
+
```
|
71
|
+
|
72
|
+
Add generating uid by default
|
73
|
+
|
74
|
+
*db/migrate/create_users_*.rb
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
t.uuid :uid, default: 'uuid_generate_v4()'
|
78
|
+
```
|
79
|
+
|
80
|
+
*app/models/user.rb*
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
class User < Tramway::Core::ApplicationRecord
|
84
|
+
has_secure_password
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
Create file `config/initializers/tramway.rb`
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
::Tramway::Api.auth_config = { user_model: User, auth_attributes: %i[email username] }
|
92
|
+
::Tramway::Api.set_available_models user: %i[create update]
|
93
|
+
```
|
94
|
+
|
95
|
+
Run `rails g tramway:core:install`
|
96
|
+
|
97
|
+
Run `rails db:create db:migrate`
|
98
|
+
|
99
|
+
*config/routes.rb*
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
Rails.application.routes.draw do
|
103
|
+
mount Tramway::Api::Engine, at: '/api'
|
104
|
+
end
|
105
|
+
```
|
106
|
+
|
107
|
+
Create file *app/forms/user_sign_up_form.rb*
|
108
|
+
|
109
|
+
```ruby
|
110
|
+
class UserSignUpForm < Tramway::Core::ApplicationForm
|
111
|
+
properties :username, :email, :password
|
112
|
+
end
|
113
|
+
```
|
114
|
+
|
115
|
+
**DONE!**
|
116
|
+
|
117
|
+
## Testing
|
118
|
+
|
119
|
+
# Preparation (optional)
|
120
|
+
|
121
|
+
Let's write RSpec test to check what we have:
|
122
|
+
|
123
|
+
*Gemfile*
|
9
124
|
|
10
125
|
```ruby
|
11
|
-
|
126
|
+
group :test do
|
127
|
+
gem 'rspec-rails', '~> 3.5'
|
128
|
+
gem 'rspec-json_expectations', '2.2.0'
|
129
|
+
gem 'factory_bot_rails', '~> 4.0'
|
130
|
+
gem 'json_matchers'
|
131
|
+
gem 'json_api_test_helpers', '1.1.1'
|
132
|
+
end
|
12
133
|
```
|
13
134
|
|
14
|
-
|
15
|
-
|
16
|
-
|
135
|
+
Run `bundle install`
|
136
|
+
|
137
|
+
Run `RAILS_ENV=test rails db:create db:migrate`
|
138
|
+
|
139
|
+
Run `mkdir spec`
|
140
|
+
|
141
|
+
Create file `spec/spec_helper.rb` with:
|
142
|
+
|
143
|
+
```ruby
|
144
|
+
ENV['RAILS_ENV'] ||= 'test'
|
145
|
+
require File.expand_path('../config/environment', __dir__)
|
146
|
+
require 'rspec/rails'
|
147
|
+
require 'rspec/autorun'
|
148
|
+
require 'rspec/expectations'
|
149
|
+
require 'rspec/json_expectations'
|
150
|
+
|
151
|
+
RSpec.configure do |config|
|
152
|
+
config.expect_with :rspec do |expectations|
|
153
|
+
expectations.include_chain_clauses_in_custom_matcher_descriptions = true
|
154
|
+
end
|
155
|
+
config.mock_with :rspec do |mocks|
|
156
|
+
mocks.verify_partial_doubles = true
|
157
|
+
end
|
158
|
+
end
|
17
159
|
```
|
18
160
|
|
19
|
-
|
20
|
-
|
21
|
-
|
161
|
+
Create file `spec/rails_helper.rb` with:
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
require 'spec_helper'
|
165
|
+
require 'factory_bot'
|
166
|
+
require 'rspec/rails'
|
167
|
+
require 'rspec/json_expectations'
|
168
|
+
require 'json_matchers/rspec'
|
169
|
+
require 'json_api_test_helpers'
|
170
|
+
require 'rake'
|
171
|
+
|
172
|
+
RSpec.configure do |config|
|
173
|
+
config.include FactoryBot::Syntax::Methods
|
174
|
+
config.include RSpec::Rails::RequestExampleGroup, type: :feature
|
175
|
+
config.include JsonApiTestHelpers
|
176
|
+
end
|
22
177
|
```
|
23
178
|
|
179
|
+
# SignUp user
|
180
|
+
|
181
|
+
Create file `spec/tramway_api_spec.rb` with:
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
require 'rails_helper'
|
185
|
+
|
186
|
+
RSpec.describe 'Post creating user', type: :feature do
|
187
|
+
describe 'POST /api/v1/user with model User' do
|
188
|
+
let(:attributes) { attributes_for :user }
|
189
|
+
|
190
|
+
it 'returns created status' do
|
191
|
+
post '/api/v1/user', params: { user: attributes }
|
192
|
+
expect(response.status).to eq 201
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'returns no errors' do
|
196
|
+
post '/api/v1/user', params: { user: attributes }
|
197
|
+
|
198
|
+
expect(json_response[:response]). to be_nil
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
```
|
203
|
+
|
204
|
+
# SignIn User
|
205
|
+
|
206
|
+
```ruby
|
207
|
+
require 'rails_helper'
|
208
|
+
|
209
|
+
RSpec.describe 'Post generate token', type: :feature do
|
210
|
+
describe 'POST /api/v1/user_token' do
|
211
|
+
let(:user) { create :user, password: '123456789' }
|
212
|
+
|
213
|
+
it 'returns created status' do
|
214
|
+
post '/api/v1/user_token', params: { auth: { login: user.email, password: '123456789' } }
|
215
|
+
|
216
|
+
expect(response.status).to eq 201
|
217
|
+
end
|
218
|
+
|
219
|
+
it 'returns token' do
|
220
|
+
post '/api/v1/user_token', params: { auth: { login: user.email, password: '123456789' } }
|
221
|
+
|
222
|
+
expect(json_response[:auth_token].present?).to be_truthy
|
223
|
+
expect(json_response[:user]).to include_json({ email: user.email, uid: user.uid })
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
```
|
230
|
+
|
231
|
+
Run `rspec` to test
|
232
|
+
|
233
|
+
We have route `user`, which create new authenticable user.
|
234
|
+
|
235
|
+
For other models we have route `records`.
|
236
|
+
|
237
|
+
```
|
238
|
+
~: rails routes
|
239
|
+
Prefix Verb URI Pattern Controller#Action
|
240
|
+
tramway_api /api Tramway::Api::Engine
|
241
|
+
|
242
|
+
Routes for Tramway::Api::Engine:
|
243
|
+
v1_user_token POST /v1/user_token(.:format) tramway/api/v1/user_tokens#create
|
244
|
+
v1_user GET /v1/user(.:format) tramway/api/v1/users#show
|
245
|
+
POST /v1/user(.:format) tramway/api/v1/users#create
|
246
|
+
v1_records GET /v1/records(.:format) tramway/api/v1/records#index
|
247
|
+
POST /v1/records(.:format) tramway/api/v1/records#create
|
248
|
+
new_v1_record GET /v1/records/new(.:format) tramway/api/v1/records#new
|
249
|
+
edit_v1_record GET /v1/records/:id/edit(.:format) tramway/api/v1/records#edit
|
250
|
+
v1_record GET /v1/records/:id(.:format) tramway/api/v1/records#show
|
251
|
+
PATCH /v1/records/:id(.:format) tramway/api/v1/records#update
|
252
|
+
PUT /v1/records/:id(.:format) tramway/api/v1/records#update
|
253
|
+
DELETE /v1/records/:id(.:format) tramway/api/v1/records#destroy
|
254
|
+
|
255
|
+
```
|
256
|
+
|
257
|
+
## Methods
|
258
|
+
|
259
|
+
### Initializer methods
|
260
|
+
|
261
|
+
#### auth_config
|
262
|
+
|
263
|
+
Sets default ActiveRecord model, which used as main user to be authenticated with JWT.
|
264
|
+
|
265
|
+
`user_model` - model name
|
266
|
+
`auth_attributes` - array of available attributes used as login.
|
267
|
+
|
268
|
+
this model must have field `password_digest`, because we use `bcrypt` gem for authentication (providing other name of password attribute instead of `password` is coming soon)
|
269
|
+
|
270
|
+
### set_available_models
|
271
|
+
|
272
|
+
Sets ActiveRecord models which will be used in API
|
273
|
+
|
274
|
+
Argument is a hash. Keys are underscored models names, values are arrays of available methods for every model.
|
275
|
+
|
276
|
+
Enabled methods:
|
277
|
+
|
278
|
+
* create
|
279
|
+
* update
|
280
|
+
* show
|
281
|
+
* index
|
282
|
+
* destroy
|
283
|
+
|
284
|
+
|
24
285
|
## Contributing
|
25
286
|
Contribution directions go here.
|
26
287
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Tramway
|
2
2
|
module Api
|
3
3
|
class ApplicationController < ::Tramway::Core::ApplicationController
|
4
|
-
include Knock::Authenticable
|
4
|
+
include ::Knock::Authenticable
|
5
5
|
protect_from_forgery with: :null_session, if: proc { |c| c.request.format == 'application/json' }
|
6
6
|
rescue_from ActiveRecord::RecordNotFound, with: :not_found
|
7
7
|
|
@@ -23,23 +23,31 @@ module Tramway
|
|
23
23
|
|
24
24
|
def auth_token
|
25
25
|
if entity.respond_to? :to_token_payload
|
26
|
-
Knock::AuthToken.new payload: entity.to_token_payload
|
26
|
+
::Knock::AuthToken.new payload: entity.to_token_payload
|
27
27
|
else
|
28
|
-
Knock::AuthToken.new payload: { sub: entity.id }
|
28
|
+
::Knock::AuthToken.new payload: { sub: entity.id }
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
32
|
def entity
|
33
33
|
@entity ||=
|
34
34
|
if Tramway::Api.user_based_model.respond_to? :from_token_request
|
35
|
-
Tramway::Api.user_based_model.from_token_request request
|
35
|
+
Tramway::Api.user_based_model.active.from_token_request request
|
36
36
|
else
|
37
|
-
params[:auth] &&
|
37
|
+
params[:auth] && find_user_by_auth_attributes
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
|
+
def find_user_by_auth_attributes
|
42
|
+
Tramway::Api.auth_attributes.each do |attribute|
|
43
|
+
object = Tramway::Api.user_based_model.active.where.not(attribute => nil).find_by(attribute => auth_params[:login])
|
44
|
+
return object if object
|
45
|
+
end
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
|
41
49
|
def auth_params
|
42
|
-
params[:auth]&.permit
|
50
|
+
params[:auth]&.permit(:login, :password)
|
43
51
|
end
|
44
52
|
end
|
45
53
|
end
|
@@ -2,6 +2,7 @@ module Tramway::Api::V1
|
|
2
2
|
class RecordsController < ::Tramway::Api::V1::ApplicationController
|
3
3
|
before_action :check_available_model_class
|
4
4
|
before_action :check_available_model_action
|
5
|
+
before_action :authenticate_user
|
5
6
|
|
6
7
|
def index
|
7
8
|
records = model_class.active.order(id: :desc).send params[:scope] || :all
|
@@ -5,7 +5,8 @@ class Tramway::Api::V1::UsersController < ::Tramway::Api::V1::ApplicationControl
|
|
5
5
|
include Tramway::ClassNameHelpers
|
6
6
|
|
7
7
|
def create
|
8
|
-
user_form =
|
8
|
+
user_form = sign_up_form_class_name(Tramway::Api.user_based_model).new Tramway::Api.user_based_model.new
|
9
|
+
# Implement JSON API spec here
|
9
10
|
if user_form.submit params[Tramway::Api.user_based_model.name.underscore]
|
10
11
|
token = ::Knock::AuthToken.new(payload: { sub: user_form.model.id }).token
|
11
12
|
# FIXME refactor this bullshit
|
@@ -24,4 +25,10 @@ class Tramway::Api::V1::UsersController < ::Tramway::Api::V1::ApplicationControl
|
|
24
25
|
def show
|
25
26
|
render json: current_user, status: :ok
|
26
27
|
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def sign_up_form_class_name(model_class)
|
32
|
+
form_class_name "#{model_class}SignUp"
|
33
|
+
end
|
27
34
|
end
|
data/lib/tramway/api.rb
CHANGED
@@ -3,12 +3,20 @@ require 'tramway/api/engine'
|
|
3
3
|
module Tramway
|
4
4
|
module Api
|
5
5
|
class << self
|
6
|
+
def auth_config
|
7
|
+
@@auth_config ||= { user_model: ::Tramway::User::User, auth_attributes: :email }
|
8
|
+
end
|
9
|
+
|
10
|
+
def auth_config=(**params)
|
11
|
+
@@auth_config = params
|
12
|
+
end
|
13
|
+
|
6
14
|
def user_based_model
|
7
|
-
@@
|
15
|
+
@@auth_config[:user_model]
|
8
16
|
end
|
9
17
|
|
10
|
-
def
|
11
|
-
@@
|
18
|
+
def auth_attributes
|
19
|
+
@@auth_config[:auth_attributes]
|
12
20
|
end
|
13
21
|
|
14
22
|
def set_available_models(**models)
|
data/lib/tramway/api/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tramway-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.2
|
4
|
+
version: 1.1.0.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pavel Kalashnikov
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2019-
|
11
|
+
date: 2019-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: knock
|
@@ -99,8 +99,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
99
99
|
- !ruby/object:Gem::Version
|
100
100
|
version: '0'
|
101
101
|
requirements: []
|
102
|
-
|
103
|
-
rubygems_version: 2.7.7
|
102
|
+
rubygems_version: 3.0.3
|
104
103
|
signing_key:
|
105
104
|
specification_version: 4
|
106
105
|
summary: Engine for api
|