trailblazer-endpoint 0.0.5 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGES.md +6 -0
- data/README.md +40 -5
- data/lib/trailblazer/endpoint.rb +3 -1
- data/lib/trailblazer/endpoint/controller.rb +29 -15
- data/lib/trailblazer/endpoint/options.rb +0 -23
- data/lib/trailblazer/endpoint/version.rb +1 -1
- data/test/docs/controller_test.rb +59 -0
- data/test/rails-app/Gemfile +4 -0
- data/test/rails-app/Gemfile.lock +26 -1
- data/test/rails-app/app/concepts/song/cell/create.rb +4 -0
- data/test/rails-app/app/concepts/song/cell/new.rb +4 -0
- data/test/rails-app/app/concepts/song/operation/create.rb +5 -1
- data/test/rails-app/app/concepts/song/view/create.erb +1 -0
- data/test/rails-app/app/concepts/song/view/new.erb +1 -0
- data/test/rails-app/app/controllers/api/v1/songs_controller.rb +13 -7
- data/test/rails-app/app/controllers/application_controller.rb +3 -1
- data/test/rails-app/app/controllers/application_controller/api.rb +3 -1
- data/test/rails-app/app/controllers/application_controller/web.rb +34 -20
- data/test/rails-app/app/controllers/auth_controller.rb +44 -0
- data/test/rails-app/app/controllers/home_controller.rb +5 -0
- data/test/rails-app/app/controllers/songs_controller.rb +72 -12
- data/test/rails-app/app/models/user.rb +3 -1
- data/test/rails-app/config/application.rb +1 -1
- data/test/rails-app/config/routes.rb +10 -3
- data/test/rails-app/test/controllers/api_songs_controller_test.rb +6 -2
- data/test/rails-app/test/controllers/songs_controller_test.rb +51 -10
- metadata +14 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4dd0d753bd8dc039e3f55e66d80d3d9d4904fcc0d442ea62860fbab4a0b05d5e
|
4
|
+
data.tar.gz: 0ecfa53ed1607d4d3785d47ffef3f817033b18ec29c6cbbf06e4f466c90a2638
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 7762ada8e3fafbd1a68e5f4045af8e57b03568745ac55236d756da6a4fe681f6818abd4e27b737b162872b9ebd4589f440b1b7ebee01729d2cfd6dd88702d980
|
7
|
+
data.tar.gz: 6f56d293369de1da783002e8d9f602f2847eafbe2f7ad80f5f6a08fc305542f3d432d7c9878376cc421a38de58e19d60eac2d436fe2650191b36f66d149bb722
|
data/CHANGES.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# 0.0.6
|
2
|
+
|
3
|
+
* `Controller::endpoint` short form introduced.
|
4
|
+
* Minor changes for `Controller.module`.
|
5
|
+
* Lots of cleanups.
|
6
|
+
|
1
7
|
# 0.0.5
|
2
8
|
|
3
9
|
* Removed `Protocol::Failure`. Until we have `Railway::End::Failure`, use a normal `Activity::End` everywhere instead of introducing our own.
|
data/README.md
CHANGED
@@ -1,11 +1,46 @@
|
|
1
1
|
# Trailblazer::Endpoint
|
2
2
|
|
3
|
-
*
|
3
|
+
*Endpoints handle authentication, authorization, calling the business logic and response rendering.*
|
4
4
|
|
5
|
-
|
5
|
+
## Overview
|
6
6
|
|
7
|
-
|
7
|
+
An endpoint links your routing with your business code. The idea is that your controllers are pure HTTP routers, calling the respective endpoint for each action. From there, the endpoint takes over, handles authentication, policies, executing the domain code, interpreting the result, and providing hooks to render a response.
|
8
8
|
|
9
|
-
|
9
|
+
Instead of dealing with a mix of `before_filter`s, Rack-middlewares, controller code and callbacks, an endpoint is just another activity and allows to be customized with the well-established Trailblazer mechanics.
|
10
10
|
|
11
|
-
|
11
|
+
|
12
|
+
In a Rails controller, a controller action could look as follows.
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
class DiagramsController < ApplicationController
|
16
|
+
endpoint Diagram::Operation::Create, [:is_logged_in?, :can_add_diagram?]
|
17
|
+
|
18
|
+
def create
|
19
|
+
endpoint Diagram::Operation::Create do |ctx, **|
|
20
|
+
redirect_to diagram_path(ctx[:diagram].id)
|
21
|
+
end.Or do |ctx, **|
|
22
|
+
render :form
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
```
|
27
|
+
|
28
|
+
While routing and redirecting/rendering still happens in Rails, all remaining steps are handled in the endpoint.
|
29
|
+
|
30
|
+
An API controller action, where the rendering is done generically, could look much simpler.
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
class API::V1::DiagramsController < ApplicationController
|
34
|
+
endpoint Diagram::Operation::Create, [:is_logged_in?, :can_add_diagram?]
|
35
|
+
|
36
|
+
def create
|
37
|
+
endpoint Diagram::Operation::Create
|
38
|
+
end
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
Endpoints are easily customized but their main intent is to reduce fuzzy controller code and providing best practices for both HTML-rendering controllers and APIs.
|
43
|
+
|
44
|
+
## Documentation
|
45
|
+
|
46
|
+
Read the [full documentation for endpoint](https://trailblazer.to/2.1/docs/endpoint.html) on our website.
|
data/lib/trailblazer/endpoint.rb
CHANGED
@@ -63,7 +63,9 @@ module Trailblazer
|
|
63
63
|
|
64
64
|
# Runtime
|
65
65
|
# Invokes the endpoint for you and runs one of the three outcome blocks.
|
66
|
-
def self.with_or_etc(activity, args, failure_block:, success_block:, protocol_failure_block:, invoke: Trailblazer::Activity::TaskWrap.method(:invoke))
|
66
|
+
def self.with_or_etc(activity, args, failure_block:, success_block:, protocol_failure_block:, invoke: Trailblazer::Activity::TaskWrap.method(:invoke))
|
67
|
+
# def self.with_or_etc(activity, args, failure_block:, success_block:, protocol_failure_block:, invoke: Trailblazer::Developer.method(:wtf?))
|
68
|
+
|
67
69
|
# args[1] = args[1].merge(focus_on: { variables: [:returned], steps: :invoke_workflow })
|
68
70
|
|
69
71
|
# signal, (endpoint_ctx, _ ) = Trailblazer::Developer.wtf?(activity, args)
|
@@ -2,9 +2,9 @@ module Trailblazer
|
|
2
2
|
class Endpoint
|
3
3
|
module Controller
|
4
4
|
def self.extended(extended)
|
5
|
-
extended.extend Trailblazer::Endpoint::Options::DSL
|
5
|
+
extended.extend Trailblazer::Endpoint::Options::DSL # ::directive
|
6
6
|
extended.extend Trailblazer::Endpoint::Options::DSL::Inherit
|
7
|
-
extended.extend Trailblazer::Endpoint::Options
|
7
|
+
extended.extend Trailblazer::Endpoint::Options # ::options_for
|
8
8
|
extended.extend DSL::Endpoint
|
9
9
|
|
10
10
|
extended.include InstanceMethods # {#endpoint_for}
|
@@ -17,26 +17,41 @@ module Trailblazer
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# @experimental
|
20
|
+
# TODO: test application_controller with and without dsl/api
|
21
|
+
|
20
22
|
def self.module(framework: :rails, api: false, dsl: false, application_controller: false)
|
21
|
-
if api
|
23
|
+
if application_controller && !api && !dsl # FIXME: not tested! this is useful for an actual AppController with block_options or flow_options settings, "globally"
|
24
|
+
Module.new do
|
25
|
+
def self.included(includer)
|
26
|
+
includer.extend(Controller) # only ::directive and friends.
|
27
|
+
end
|
28
|
+
end
|
29
|
+
elsif api
|
22
30
|
Module.new do
|
31
|
+
@application_controller = application_controller
|
23
32
|
def self.included(includer)
|
24
|
-
|
25
|
-
|
33
|
+
if @application_controller
|
34
|
+
includer.extend Controller
|
35
|
+
end
|
26
36
|
includer.include(InstanceMethods::API)
|
27
37
|
end
|
28
38
|
end
|
29
|
-
elsif dsl
|
39
|
+
elsif dsl
|
30
40
|
Module.new do
|
41
|
+
@application_controller = application_controller
|
31
42
|
def self.included(includer)
|
32
|
-
|
43
|
+
if @application_controller
|
44
|
+
includer.extend Controller
|
45
|
+
end
|
33
46
|
includer.include Trailblazer::Endpoint::Controller::InstanceMethods::DSL
|
34
47
|
includer.include Trailblazer::Endpoint::Controller::Rails
|
35
48
|
includer.extend Trailblazer::Endpoint::Controller::Rails::DefaultBlocks
|
36
49
|
includer.extend Trailblazer::Endpoint::Controller::Rails::DefaultParams
|
37
50
|
includer.include Trailblazer::Endpoint::Controller::Rails::Process
|
38
51
|
end
|
39
|
-
end
|
52
|
+
end # Module
|
53
|
+
else
|
54
|
+
raise
|
40
55
|
end
|
41
56
|
end
|
42
57
|
|
@@ -96,13 +111,12 @@ module Trailblazer
|
|
96
111
|
directive :generic_options, method(:generic_options) # FIXME: do we need this?
|
97
112
|
end
|
98
113
|
|
99
|
-
def endpoint_config(name, **options)
|
100
|
-
|
101
|
-
build_options = options_for(:generic_options, {}).merge(options) # DISCUSS: why don't we add this as another directive option/step?
|
114
|
+
def endpoint_config(name, domain_activity: name, **options)
|
115
|
+
build_options = options_for(:generic_options, {}).merge(domain_activity: domain_activity, **options) # DISCUSS: why don't we add this as another directive option/step?
|
102
116
|
|
103
117
|
endpoint = Trailblazer::Endpoint.build(build_options)
|
104
118
|
|
105
|
-
directive :endpoints, ->(*) { {name => endpoint} }
|
119
|
+
directive :endpoints, ->(*) { {name.to_s => endpoint} }
|
106
120
|
end
|
107
121
|
|
108
122
|
end
|
@@ -111,12 +125,12 @@ module Trailblazer
|
|
111
125
|
module InstanceMethods
|
112
126
|
|
113
127
|
def endpoint_for(name, config_source: self.class)
|
114
|
-
config_source.options_for(:endpoints, {}).fetch(name) # TODO: test non-existant endpoint
|
128
|
+
config_source.options_for(:endpoints, {}).fetch(name.to_s) # TODO: test non-existant endpoint
|
115
129
|
end
|
116
130
|
|
117
131
|
module DSL
|
118
132
|
def endpoint(name, **action_options, &block)
|
119
|
-
action_options = {controller: self}.merge(action_options) # FIXME: redundant with {
|
133
|
+
action_options = {controller: self}.merge(action_options) # FIXME: redundant with {API#endpoint}
|
120
134
|
|
121
135
|
endpoint = endpoint_for(name)
|
122
136
|
|
@@ -132,7 +146,7 @@ module Trailblazer
|
|
132
146
|
def endpoint(name, config_source: self.class, **action_options)
|
133
147
|
endpoint = endpoint_for(name, config_source: config_source)
|
134
148
|
|
135
|
-
action_options = {controller: self}.merge(action_options) # FIXME: redundant with {
|
149
|
+
action_options = {controller: self}.merge(action_options) # FIXME: redundant with {InstanceMethods#endpoint}
|
136
150
|
|
137
151
|
block_options = config_source.options_for(:options_for_block_options, **action_options)
|
138
152
|
block_options = Trailblazer::Endpoint::Options.merge_with(action_options, block_options)
|
@@ -67,29 +67,6 @@ module Trailblazer
|
|
67
67
|
target.instance_variable_set(:@config, config)
|
68
68
|
end
|
69
69
|
|
70
|
-
class State < Module
|
71
|
-
def initialize(normalizer, config)
|
72
|
-
@normalizer = normalizer
|
73
|
-
@config = config
|
74
|
-
end
|
75
|
-
|
76
|
-
# called once when extended in {ApplicationController}.
|
77
|
-
def extended(extended)
|
78
|
-
super
|
79
|
-
|
80
|
-
extended.extend(Inherited)
|
81
|
-
Normalizer.add_normalizer!(extended, @normalizer, @config)
|
82
|
-
end
|
83
|
-
|
84
|
-
end
|
85
|
-
module Inherited
|
86
|
-
def inherited(subclass)
|
87
|
-
super
|
88
|
-
|
89
|
-
Normalizer.add_normalizer!(subclass, @normalizer, @config)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
70
|
def self.add(normalizer, directive_name, options)
|
94
71
|
Class.new(normalizer) do
|
95
72
|
options.collect do |callable|
|
@@ -311,4 +311,63 @@ class DocsControllerTest < Minitest::Spec
|
|
311
311
|
end
|
312
312
|
end
|
313
313
|
|
314
|
+
class ControllerEndpointMethodTest < Minitest::Spec
|
315
|
+
# Test {Controller::endpoint}
|
316
|
+
|
317
|
+
class Protocol < Trailblazer::Endpoint::Protocol
|
318
|
+
def policy(*); true; end
|
319
|
+
def authenticate(*); true; end
|
320
|
+
end
|
321
|
+
|
322
|
+
class BasicController
|
323
|
+
include Trailblazer::Endpoint::Controller.module(api: true, application_controller: true)
|
324
|
+
|
325
|
+
directive :options_for_block_options, Trailblazer::Endpoint::Controller.method(:options_for_block_options)
|
326
|
+
|
327
|
+
endpoint protocol: Protocol, adapter: Trailblazer::Endpoint::Adapter::Web
|
328
|
+
|
329
|
+
def head(status)
|
330
|
+
@status = status
|
331
|
+
end
|
332
|
+
|
333
|
+
def self.options_for_block_options(ctx, controller:, **)
|
334
|
+
{
|
335
|
+
success_block: ->(ctx, endpoint_ctx:, **) { controller.head("#{ctx[:op]}") },
|
336
|
+
failure_block: ->(ctx, status:, **) { },
|
337
|
+
protocol_failure_block: ->(ctx, status:, **) { }
|
338
|
+
}
|
339
|
+
end
|
340
|
+
|
341
|
+
directive :options_for_block_options, method(:options_for_block_options)
|
342
|
+
end
|
343
|
+
|
344
|
+
class RodaController < BasicController
|
345
|
+
class Create < Trailblazer::Activity::Railway
|
346
|
+
def save(ctx, **); ctx[:op] = self.class; end;
|
347
|
+
step :save
|
348
|
+
end
|
349
|
+
class Update < Create
|
350
|
+
end
|
351
|
+
|
352
|
+
# {Controller::endpoint}: {:domain_activity} defaults to {name} when not given
|
353
|
+
endpoint Create # class {name}s are ok
|
354
|
+
endpoint :update, domain_activity: Update # symbol {name} is ok
|
355
|
+
|
356
|
+
def show
|
357
|
+
endpoint Create
|
358
|
+
@status
|
359
|
+
end
|
360
|
+
|
361
|
+
def update
|
362
|
+
endpoint :update
|
363
|
+
@status
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
|
368
|
+
it "what" do
|
369
|
+
RodaController.new.show.must_equal %{ControllerEndpointMethodTest::RodaController::Create}
|
370
|
+
RodaController.new.update.must_equal %{ControllerEndpointMethodTest::RodaController::Update}
|
371
|
+
end
|
372
|
+
end
|
314
373
|
|
data/test/rails-app/Gemfile
CHANGED
data/test/rails-app/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../..
|
3
3
|
specs:
|
4
|
-
trailblazer-endpoint (0.0.
|
4
|
+
trailblazer-endpoint (0.0.5)
|
5
5
|
trailblazer-activity-dsl-linear (>= 0.3.0, < 0.4.0)
|
6
6
|
|
7
7
|
GEM
|
@@ -63,10 +63,25 @@ GEM
|
|
63
63
|
tzinfo (~> 1.1)
|
64
64
|
zeitwerk (~> 2.2, >= 2.2.2)
|
65
65
|
builder (3.2.4)
|
66
|
+
cells (4.1.7)
|
67
|
+
declarative-builder (< 0.2.0)
|
68
|
+
declarative-option (< 0.2.0)
|
69
|
+
tilt (>= 1.4, < 3)
|
70
|
+
uber (< 0.2.0)
|
71
|
+
cells-erb (0.1.0)
|
72
|
+
cells (~> 4.0)
|
73
|
+
erbse (>= 0.1.1)
|
74
|
+
cells-rails (0.1.1)
|
75
|
+
actionpack (>= 5.0)
|
76
|
+
cells (>= 4.1.6, < 5.0.0)
|
66
77
|
concurrent-ruby (1.1.7)
|
67
78
|
crass (1.0.6)
|
68
79
|
declarative (0.0.20)
|
80
|
+
declarative-builder (0.1.0)
|
81
|
+
declarative-option (< 0.2.0)
|
69
82
|
declarative-option (0.1.0)
|
83
|
+
erbse (0.1.4)
|
84
|
+
temple
|
70
85
|
erubi (1.9.0)
|
71
86
|
globalid (0.4.2)
|
72
87
|
activesupport (>= 4.2.0)
|
@@ -87,6 +102,8 @@ GEM
|
|
87
102
|
mini_mime (1.0.2)
|
88
103
|
mini_portile2 (2.4.0)
|
89
104
|
minitest (5.14.2)
|
105
|
+
minitest-line (0.6.5)
|
106
|
+
minitest (~> 5.0)
|
90
107
|
multi_json (1.15.0)
|
91
108
|
nio4r (2.5.2)
|
92
109
|
nokogiri (1.10.10)
|
@@ -133,12 +150,16 @@ GEM
|
|
133
150
|
activesupport (>= 4.0)
|
134
151
|
sprockets (>= 3.0.0)
|
135
152
|
sqlite3 (1.4.2)
|
153
|
+
temple (0.8.2)
|
136
154
|
thor (1.0.1)
|
137
155
|
thread_safe (0.3.6)
|
156
|
+
tilt (2.0.10)
|
138
157
|
trailblazer-activity (0.11.3)
|
139
158
|
trailblazer-context (>= 0.3.1, < 0.4.0)
|
140
159
|
trailblazer-activity-dsl-linear (0.3.2)
|
141
160
|
trailblazer-activity (>= 0.11.2, < 1.0.0)
|
161
|
+
trailblazer-cells (0.0.3)
|
162
|
+
cells (>= 4.1.0.rc1, < 5.0.0)
|
142
163
|
trailblazer-context (0.3.1)
|
143
164
|
hashie (~> 4.1)
|
144
165
|
trailblazer-developer (0.0.16)
|
@@ -162,10 +183,14 @@ PLATFORMS
|
|
162
183
|
ruby
|
163
184
|
|
164
185
|
DEPENDENCIES
|
186
|
+
cells-erb
|
187
|
+
cells-rails
|
165
188
|
jwt
|
189
|
+
minitest-line
|
166
190
|
multi_json
|
167
191
|
rails (~> 6.0.3, >= 6.0.3.1)
|
168
192
|
sqlite3 (~> 1.4)
|
193
|
+
trailblazer-cells
|
169
194
|
trailblazer-endpoint!
|
170
195
|
trailblazer-operation
|
171
196
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Song::Operation
|
2
2
|
class Create < Trailblazer::Operation
|
3
|
-
|
3
|
+
step :contract
|
4
4
|
step :model
|
5
5
|
# step :validate
|
6
6
|
# step :save
|
@@ -9,5 +9,9 @@ module Song::Operation
|
|
9
9
|
return unless params[:id]
|
10
10
|
ctx[:model] = Song.new(params[:id])
|
11
11
|
end
|
12
|
+
|
13
|
+
def contract(ctx, **)
|
14
|
+
ctx[:contract] = Struct.new(:errors).new()
|
15
|
+
end
|
12
16
|
end
|
13
17
|
end
|
@@ -0,0 +1 @@
|
|
1
|
+
<div><%= model %><%= options[:current_user] %></div>
|
@@ -0,0 +1 @@
|
|
1
|
+
<div><%= model %></div>
|
@@ -1,34 +1,40 @@
|
|
1
1
|
#:controller
|
2
2
|
module Api
|
3
3
|
module V1
|
4
|
+
#:endpoint
|
4
5
|
class SongsController < ApplicationController::Api
|
5
|
-
endpoint Song::Operation::Create
|
6
|
-
endpoint Song::Operation::Show
|
6
|
+
endpoint Song::Operation::Create
|
7
|
+
endpoint Song::Operation::Show do
|
8
|
+
{Output(:not_found) => Track(:not_found)} # add additional wiring to {domain_activity}
|
9
|
+
end
|
10
|
+
#:endpoint end
|
7
11
|
|
8
12
|
#:create
|
9
13
|
def create
|
10
|
-
endpoint Song::Operation::Create
|
14
|
+
endpoint Song::Operation::Create, representer_class: Song::Representer
|
11
15
|
end
|
12
16
|
#:create end
|
13
17
|
|
18
|
+
#~empty
|
14
19
|
def show
|
15
|
-
endpoint Song::Operation::Show
|
20
|
+
endpoint Song::Operation::Show, representer_class: Song::Representer
|
16
21
|
end
|
17
22
|
|
18
23
|
def show_with_options
|
19
|
-
endpoint Song::Operation::Show
|
24
|
+
endpoint Song::Operation::Show, representer_class: Song::Representer, protocol_failure_block: ->(ctx, endpoint_ctx:, **) { head endpoint_ctx[:status] + 1 }
|
20
25
|
end
|
21
26
|
|
22
27
|
class WithOptionsController < ApplicationController::Api
|
23
|
-
endpoint Song::Operation::Show
|
28
|
+
endpoint Song::Operation::Show do {Output(:not_found) => Track(:not_found)} end
|
24
29
|
|
25
30
|
#:show-options
|
26
31
|
def show
|
27
|
-
endpoint Song::Operation::Show
|
32
|
+
endpoint Song::Operation::Show, representer_class: Song::Representer,
|
28
33
|
protocol_failure_block: ->(ctx, endpoint_ctx:, **) { head endpoint_ctx[:status] + 1 }
|
29
34
|
end
|
30
35
|
#:show-options end
|
31
36
|
end
|
37
|
+
#~empty end
|
32
38
|
end
|
33
39
|
end
|
34
40
|
end
|
@@ -1,7 +1,9 @@
|
|
1
1
|
require "trailblazer/endpoint/controller"
|
2
2
|
|
3
3
|
class ApplicationController < ActionController::Base
|
4
|
-
|
4
|
+
def self.current_user_in_domain_ctx
|
5
|
+
->(_ctx, ((ctx, a), b)) { ctx[:domain_ctx][:current_user] = ctx[:current_user]; [_ctx, [[ctx, a], b]] } # FIXME: extract to lib?
|
6
|
+
end
|
5
7
|
end
|
6
8
|
|
7
9
|
|
@@ -1,6 +1,8 @@
|
|
1
1
|
#:app-controller
|
2
|
+
#:app-include
|
2
3
|
class ApplicationController::Api < ApplicationController
|
3
|
-
include Trailblazer::Endpoint::Controller.module(api: true)
|
4
|
+
include Trailblazer::Endpoint::Controller.module(api: true, application_controller: true)
|
5
|
+
#:app-include end
|
4
6
|
|
5
7
|
def self.options_for_block_options(ctx, controller:, **)
|
6
8
|
response_block = ->(ctx, endpoint_ctx:, **) do
|
@@ -1,30 +1,44 @@
|
|
1
|
+
#:app-include
|
2
|
+
#:options
|
3
|
+
#:protocol
|
4
|
+
#:generic
|
1
5
|
class ApplicationController::Web < ApplicationController
|
2
|
-
|
3
|
-
|
4
|
-
include Trailblazer::Endpoint::Controller
|
5
|
-
|
6
|
-
extend Trailblazer::Endpoint::Controller::Rails::DefaultParams
|
7
|
-
include Trailblazer::Endpoint::Controller::Rails::Process
|
6
|
+
#~pskip
|
7
|
+
#~gskip
|
8
|
+
include Trailblazer::Endpoint::Controller.module(dsl: true, application_controller: true)
|
9
|
+
#:app-include end
|
8
10
|
|
9
|
-
|
10
|
-
|
11
|
-
|
11
|
+
def self.options_for_endpoint(ctx, controller:, **)
|
12
|
+
{
|
13
|
+
session: controller.session,
|
14
|
+
}
|
15
|
+
end
|
12
16
|
|
13
|
-
|
14
|
-
|
15
|
-
def authenticate(ctx, domain_ctx:, **)
|
16
|
-
# puts domain_ctx[:params].inspect
|
17
|
+
directive :options_for_endpoint, method(:options_for_endpoint)
|
18
|
+
#:options end
|
17
19
|
|
18
|
-
|
19
|
-
|
20
|
+
# directive :options_for_flow_options, method(:options_for_flow_options)
|
21
|
+
# directive :options_for_block_options, method(:options_for_block_options)
|
22
|
+
#~pskip end
|
23
|
+
class Protocol < Trailblazer::Endpoint::Protocol
|
24
|
+
# provide method for {step :authenticate}
|
25
|
+
def authenticate(ctx, session:, **)
|
26
|
+
ctx[:current_user] = User.find_by(id: session[:user_id])
|
20
27
|
end
|
21
28
|
|
29
|
+
# provide method for {step :policy}
|
22
30
|
def policy(ctx, domain_ctx:, **)
|
23
|
-
domain_ctx
|
31
|
+
Policy.(domain_ctx)
|
24
32
|
end
|
25
33
|
end
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
34
|
+
#:protocol end
|
35
|
+
Policy = ->(domain_ctx) { domain_ctx[:params][:policy] == "false" ? false : true }
|
36
|
+
#~gskip end
|
37
|
+
endpoint protocol: Protocol, adapter: Trailblazer::Endpoint::Adapter::Web,
|
38
|
+
domain_ctx_filter: ApplicationController.current_user_in_domain_ctx
|
30
39
|
end
|
40
|
+
#:generic end
|
41
|
+
|
42
|
+
# do
|
43
|
+
# {Output(:not_found) => Track(:not_found)}
|
44
|
+
# end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class AuthController < ApplicationController::Web
|
2
|
+
# We could use a fully-fledged operation here, with a contract and whatnot.
|
3
|
+
def self.authenticate(ctx, request:, params:, **)
|
4
|
+
if params[:username] == "yogi@trb.to" && params[:password] == "secret"
|
5
|
+
ctx[:current_user] = User.find_by(email: params[:username])
|
6
|
+
|
7
|
+
return true
|
8
|
+
else
|
9
|
+
return false # let's be extra explicit!
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
endpoint("sign_in", domain_activity: Class.new(Trailblazer::Activity::Railway)) do
|
14
|
+
# step nil, delete: :domain_activity
|
15
|
+
step nil, delete: :policy
|
16
|
+
step AuthController.method(:authenticate), replace: :authenticate, inherit: true, id: :authenticate
|
17
|
+
|
18
|
+
{}
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.options_for_endpoint(ctx, controller:, **)
|
22
|
+
{
|
23
|
+
params: controller.params,
|
24
|
+
request: controller.request,
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
directive :options_for_endpoint, method(:options_for_endpoint)
|
29
|
+
|
30
|
+
def sign_in
|
31
|
+
endpoint "sign_in" do |ctx, current_user:, **|
|
32
|
+
session[:user_id] = current_user.id # Working on {session} is HTTP-specific and done in the controller.
|
33
|
+
|
34
|
+
redirect_to dashboard_path
|
35
|
+
# render html: "Yes!"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def dashboard_path
|
42
|
+
"/"
|
43
|
+
end
|
44
|
+
end
|
@@ -1,24 +1,84 @@
|
|
1
|
+
#:endpoint
|
2
|
+
#:or
|
3
|
+
#:create
|
1
4
|
class SongsController < ApplicationController::Web
|
2
|
-
endpoint
|
5
|
+
endpoint Song::Operation::Create
|
3
6
|
|
4
|
-
|
7
|
+
#:endpoint end
|
8
|
+
def create
|
9
|
+
endpoint Song::Operation::Create do |ctx, current_user:, model:, **|
|
10
|
+
render html: cell(Song::Cell::Create, model, current_user: current_user)
|
11
|
+
end.Or do |ctx, contract:, **| # validation failure
|
12
|
+
render html: cell(Song::Cell::New, contract)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
#:create end
|
5
16
|
|
17
|
+
#~oskip
|
18
|
+
class CreateOrController < SongsController
|
19
|
+
#~oskip end
|
6
20
|
def create
|
7
|
-
endpoint
|
21
|
+
endpoint Song::Operation::Create do |ctx, current_user:, model:, **|
|
22
|
+
render html: cell(Song::Cell::Create, model, current_user: current_user)
|
23
|
+
end.Or do |ctx, contract:, **| # validation failure
|
24
|
+
render json: contract.errors, status: 422
|
25
|
+
end
|
8
26
|
end
|
27
|
+
end
|
28
|
+
#:or end
|
9
29
|
|
10
|
-
def
|
11
|
-
endpoint
|
12
|
-
|
13
|
-
|
30
|
+
def create_without_block
|
31
|
+
endpoint Song::Operation::Create
|
32
|
+
end
|
33
|
+
|
34
|
+
class CreateWithOptionsController < SongsController
|
35
|
+
#:create-options
|
36
|
+
def create
|
37
|
+
endpoint Song::Operation::Create, session: {user_id: 2} do |ctx, current_user:, model:, **|
|
38
|
+
render html: cell(Song::Cell::Create, model, current_user: current_user)
|
39
|
+
end
|
14
40
|
end
|
41
|
+
#:create-options end
|
15
42
|
end
|
16
43
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
44
|
+
|
45
|
+
class CreateWithOptionsForDomainCtxController < SongsController
|
46
|
+
#:domain_ctx
|
47
|
+
def create
|
48
|
+
endpoint Song::Operation::Create, options_for_domain_ctx: {params: {id: 999}} do |ctx, model:, **|
|
49
|
+
render html: cell(Song::Cell::Create, model)
|
50
|
+
end
|
22
51
|
end
|
52
|
+
#:domain_ctx end
|
53
|
+
end
|
54
|
+
|
55
|
+
class CreateEndpointCtxController < SongsController
|
56
|
+
#:endpoint_ctx
|
57
|
+
def create
|
58
|
+
endpoint Song::Operation::Create do |ctx, endpoint_ctx:, **|
|
59
|
+
render html: "Created", status: endpoint_ctx[:status]
|
60
|
+
end.Or do |ctx, **| # validation failure
|
61
|
+
#~empty
|
62
|
+
#~empty end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
#:endpoint_ctx end
|
66
|
+
end
|
67
|
+
|
68
|
+
# end.Or do |ctx, endpoint_ctx:, **| # validation failure
|
69
|
+
# render json: endpoint_ctx.keys, status: 422
|
70
|
+
# end
|
71
|
+
|
72
|
+
|
73
|
+
class CreateWithProtocolFailureController < SongsController
|
74
|
+
#:protocol_failure
|
75
|
+
def create_with_protocol_failure
|
76
|
+
endpoint Song::Operation::Create do |ctx, **|
|
77
|
+
redirect_to dashboard_path
|
78
|
+
end.protocol_failure do |ctx, **|
|
79
|
+
render html: "wrong login, app crashed", status: 500
|
80
|
+
end
|
81
|
+
end
|
82
|
+
#:protocol_failure end
|
23
83
|
end
|
24
84
|
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
class User < Struct.new(:id, :email)
|
2
|
-
def self.find_by(id:)
|
2
|
+
def self.find_by(id: false, email: false)
|
3
|
+
return User.new(1, "yogi@trb.to") if email == "yogi@trb.to"
|
3
4
|
return User.new(id, "yogi@trb.to") if id.to_s == "1"
|
5
|
+
return User.new(id, "seuros@trb.to") if id.to_s == "2"
|
4
6
|
end
|
5
7
|
end
|
@@ -33,6 +33,6 @@ module RailsApp
|
|
33
33
|
# Only loads a smaller set of middleware suitable for API only apps.
|
34
34
|
# Middleware like session, flash, cookies can be added back manually.
|
35
35
|
# Skip views, helpers and assets when generating a new resource.
|
36
|
-
config.api_only =
|
36
|
+
config.api_only = false
|
37
37
|
end
|
38
38
|
end
|
@@ -1,11 +1,18 @@
|
|
1
1
|
Rails.application.routes.draw do
|
2
2
|
# For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
|
3
|
-
|
4
|
-
post "/songs/
|
5
|
-
post "/songs/
|
3
|
+
post "/songs/create_with_options", to: "songs_controller/create_with_options#create"
|
4
|
+
post "/songs/create_or", to: "songs_controller/create_or#create"
|
5
|
+
post "/songs/endpoint_ctx", to: "songs_controller/create_endpoint_ctx#create"
|
6
|
+
post "/songs/create_with_or", to: "songs#create"
|
7
|
+
post "/songs", to: "songs#create_without_block"
|
8
|
+
post "/songs/create_with_protocol_failure", to: "songs_controller/create_with_protocol_failure#create_with_protocol_failure"
|
9
|
+
post "/songs/create_with_options_for_domain_ctx", to: "songs_controller/create_with_options_for_domain_ctx#create"
|
10
|
+
post "/auth/sign_in", to: "auth#sign_in"
|
6
11
|
|
7
12
|
post "/v1/songs", to: "api/v1/songs#create"
|
8
13
|
get "/v1/songs/:id", to: "api/v1/songs#show"
|
9
14
|
|
10
15
|
get "/v1/songs_with_options/:id", to: "api/v1/songs_controller/with_options#show"
|
16
|
+
|
17
|
+
get "/", to: "home#dashboard"
|
11
18
|
end
|
@@ -32,11 +32,13 @@ class ApiSongsControllerTest < ActionDispatch::IntegrationTest
|
|
32
32
|
test "API interface" do
|
33
33
|
yogi_jwt = jwt(1)
|
34
34
|
|
35
|
+
# default {success}
|
36
|
+
#:success
|
35
37
|
post_json "/v1/songs", {id: 1}, yogi_jwt
|
36
38
|
|
37
|
-
# default {success}
|
38
39
|
assert_response 200
|
39
40
|
assert_equal "{\"id\":1}", response.body
|
41
|
+
#:success end
|
40
42
|
|
41
43
|
# no proper input/params
|
42
44
|
post_json "/v1/songs", {}, yogi_jwt
|
@@ -45,9 +47,11 @@ class ApiSongsControllerTest < ActionDispatch::IntegrationTest
|
|
45
47
|
assert_equal "{\"errors\":{\"message\":\"The submitted data is invalid.\"}}", response.body
|
46
48
|
|
47
49
|
# 401
|
48
|
-
|
50
|
+
#:not_authenticated
|
51
|
+
post_json "/v1/songs", {} # no token
|
49
52
|
assert_response 401
|
50
53
|
assert_equal "{\"errors\":{\"message\":\"Authentication credentials were not provided or are invalid.\"}}", response.body
|
54
|
+
#:not_authenticated end
|
51
55
|
|
52
56
|
# 403
|
53
57
|
post_json "/v1/songs", {id: 1, policy: false}, yogi_jwt
|
@@ -1,7 +1,18 @@
|
|
1
1
|
require "test_helper"
|
2
2
|
|
3
3
|
class SongsControllerTest < ActionDispatch::IntegrationTest
|
4
|
-
test "
|
4
|
+
test "all possible outcomes with {Create}" do
|
5
|
+
# 401
|
6
|
+
post "/songs", params: {}
|
7
|
+
assert_response 401
|
8
|
+
assert_equal "", response.body
|
9
|
+
|
10
|
+
# sign in
|
11
|
+
post "/auth/sign_in", params: {username: "yogi@trb.to", password: "secret"}
|
12
|
+
assert_equal 1, session[:user_id]
|
13
|
+
# follow_redirect!
|
14
|
+
assert_equal 1, session[:user_id]
|
15
|
+
|
5
16
|
post "/songs", params: {id: 1}
|
6
17
|
# default {success} block doesn't do anything
|
7
18
|
assert_response 200
|
@@ -12,11 +23,6 @@ class SongsControllerTest < ActionDispatch::IntegrationTest
|
|
12
23
|
assert_response 422
|
13
24
|
assert_equal "", response.body
|
14
25
|
|
15
|
-
# 401
|
16
|
-
post "/songs", params: {authenticate: false}
|
17
|
-
assert_response 401
|
18
|
-
assert_equal "", response.body
|
19
|
-
|
20
26
|
# 403
|
21
27
|
post "/songs", params: {policy: false}
|
22
28
|
assert_response 403
|
@@ -25,7 +31,7 @@ class SongsControllerTest < ActionDispatch::IntegrationTest
|
|
25
31
|
post "/songs/create_with_options", params: {id: 1}
|
26
32
|
# {success} block renders model
|
27
33
|
assert_response 200
|
28
|
-
assert_equal "
|
34
|
+
assert_equal "<div>#<struct Song id=\"1\">#<struct User id=2, email=\"seuros@trb.to\"></div>\n", response.body
|
29
35
|
|
30
36
|
post "/songs/create_with_options", params: {}
|
31
37
|
# default {failure} block doesn't do anything
|
@@ -35,13 +41,48 @@ class SongsControllerTest < ActionDispatch::IntegrationTest
|
|
35
41
|
post "/songs/create_with_or", params: {id: 1}
|
36
42
|
# {success} block renders model
|
37
43
|
assert_response 200
|
38
|
-
assert_equal "
|
44
|
+
assert_equal "<div>#<struct Song id=\"1\">#<struct User id=1, email=\"yogi@trb.to\"></div>\n", response.body
|
39
45
|
|
40
46
|
post "/songs/create_with_or", params: {}
|
41
|
-
|
47
|
+
assert_response 200
|
48
|
+
assert_equal %{<div>#<struct errors=nil></div>\n}, response.body
|
49
|
+
|
50
|
+
# Or { render status: 422 }
|
51
|
+
post "/songs/create_or", params: {}
|
42
52
|
assert_response 422
|
43
|
-
assert_equal
|
53
|
+
assert_equal %{null}, response.body
|
54
|
+
|
55
|
+
# {:endpoint_ctx} is available in blocks
|
56
|
+
post "/songs/endpoint_ctx", params: {id: 1}
|
57
|
+
assert_response 200
|
58
|
+
assert_equal "Created", response.body
|
59
|
+
# assert_equal "[\"domain_ctx\",\"session\",\"controller\",\"config_source\",\"current_user\",\"domain_activity_return_signal\"]", response.body
|
60
|
+
|
61
|
+
# {:options_for_domain_ctx} overrides domain_ctx
|
62
|
+
post "/songs/create_with_options_for_domain_ctx", params: {id: 1} # params get overridden
|
63
|
+
assert_response 200
|
64
|
+
assert_equal "<div>#<struct Song id=999></div>\n", response.body
|
65
|
+
end
|
66
|
+
|
67
|
+
test "override protocol_failure" do
|
68
|
+
post "/songs/create_with_protocol_failure", params: {}
|
69
|
+
assert_response 500
|
70
|
+
assert_equal "wrong login, app crashed", response.body
|
71
|
+
end
|
72
|
+
|
73
|
+
test "sign_in" do
|
74
|
+
# wrong credentials
|
75
|
+
post "/auth/sign_in", params: {}
|
76
|
+
assert_response 401
|
77
|
+
assert_equal "", response.body
|
78
|
+
assert_nil session[:user_id]
|
44
79
|
|
80
|
+
# valid signin
|
81
|
+
post "/auth/sign_in", params: {username: "yogi@trb.to", password: "secret"}
|
82
|
+
assert_response 302
|
83
|
+
# assert_equal "", response.body
|
84
|
+
assert_equal 1, session[:user_id]
|
85
|
+
assert_redirected_to "/"
|
45
86
|
end
|
46
87
|
end
|
47
88
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trailblazer-endpoint
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Nick Sutterer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-11-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: trailblazer-activity-dsl-linear
|
@@ -129,13 +129,19 @@ files:
|
|
129
129
|
- test/rails-app/app/concepts/auth/jwt.rb
|
130
130
|
- test/rails-app/app/concepts/auth/operation/authenticate.rb
|
131
131
|
- test/rails-app/app/concepts/auth/operation/policy.rb
|
132
|
+
- test/rails-app/app/concepts/song/cell/create.rb
|
133
|
+
- test/rails-app/app/concepts/song/cell/new.rb
|
132
134
|
- test/rails-app/app/concepts/song/operation/create.rb
|
133
135
|
- test/rails-app/app/concepts/song/operation/show.rb
|
134
136
|
- test/rails-app/app/concepts/song/representer.rb
|
137
|
+
- test/rails-app/app/concepts/song/view/create.erb
|
138
|
+
- test/rails-app/app/concepts/song/view/new.erb
|
135
139
|
- test/rails-app/app/controllers/api/v1/songs_controller.rb
|
136
140
|
- test/rails-app/app/controllers/application_controller.rb
|
137
141
|
- test/rails-app/app/controllers/application_controller/api.rb
|
138
142
|
- test/rails-app/app/controllers/application_controller/web.rb
|
143
|
+
- test/rails-app/app/controllers/auth_controller.rb
|
144
|
+
- test/rails-app/app/controllers/home_controller.rb
|
139
145
|
- test/rails-app/app/controllers/songs_controller.rb
|
140
146
|
- test/rails-app/app/models/application_record.rb
|
141
147
|
- test/rails-app/app/models/concerns/.keep
|
@@ -222,13 +228,19 @@ test_files:
|
|
222
228
|
- test/rails-app/app/concepts/auth/jwt.rb
|
223
229
|
- test/rails-app/app/concepts/auth/operation/authenticate.rb
|
224
230
|
- test/rails-app/app/concepts/auth/operation/policy.rb
|
231
|
+
- test/rails-app/app/concepts/song/cell/create.rb
|
232
|
+
- test/rails-app/app/concepts/song/cell/new.rb
|
225
233
|
- test/rails-app/app/concepts/song/operation/create.rb
|
226
234
|
- test/rails-app/app/concepts/song/operation/show.rb
|
227
235
|
- test/rails-app/app/concepts/song/representer.rb
|
236
|
+
- test/rails-app/app/concepts/song/view/create.erb
|
237
|
+
- test/rails-app/app/concepts/song/view/new.erb
|
228
238
|
- test/rails-app/app/controllers/api/v1/songs_controller.rb
|
229
239
|
- test/rails-app/app/controllers/application_controller.rb
|
230
240
|
- test/rails-app/app/controllers/application_controller/api.rb
|
231
241
|
- test/rails-app/app/controllers/application_controller/web.rb
|
242
|
+
- test/rails-app/app/controllers/auth_controller.rb
|
243
|
+
- test/rails-app/app/controllers/home_controller.rb
|
232
244
|
- test/rails-app/app/controllers/songs_controller.rb
|
233
245
|
- test/rails-app/app/models/application_record.rb
|
234
246
|
- test/rails-app/app/models/concerns/.keep
|