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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5856f0724650869732af67c5b45eee354e68a606b97d86153dffa3271adc9ee1
4
- data.tar.gz: 01504dc413db4e2dce833908d1687ba0492f157cea3a3bca58853d9615d597d5
3
+ metadata.gz: 4dd0d753bd8dc039e3f55e66d80d3d9d4904fcc0d442ea62860fbab4a0b05d5e
4
+ data.tar.gz: 0ecfa53ed1607d4d3785d47ffef3f817033b18ec29c6cbbf06e4f466c90a2638
5
5
  SHA512:
6
- metadata.gz: 9c6322150c0dad7b568a77c8dc8b4af4a48c40e6358d18b16cf57e50f3f9515bae3e38a1875f3eb48e420192dbe978a5f7631b5b35f7c1e532e58e52d3f2043a
7
- data.tar.gz: 3ff1847a6e2e59fba1d8b3713b7069d58ea7e89be250ab79d5d87b795a5141c6a2c5dd1cc653257191d07906c66c4700fd12408b03200bdc89d87b312451cf96
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
- *Generic HTTP handlers for operation results.*
3
+ *Endpoints handle authentication, authorization, calling the business logic and response rendering.*
4
4
 
5
- Decouple finding out *what happened* from *what to do*.
5
+ ## Overview
6
6
 
7
- t test/controllers/songs_controller_test.rb --backtrace
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
- ## TODO
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
- * make travis build run `cd test/rails-app/ && rake`
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.
@@ -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)) # invoke: Trailblazer::Developer.method(:wtf?)
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
- includer.extend(Controller)
25
- includer.include(InstanceMethods)
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 and !application_controller
39
+ elsif dsl
30
40
  Module.new do
41
+ @application_controller = application_controller
31
42
  def self.included(includer)
32
- # includer.extend Trailblazer::Endpoint::Controller
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
- puts "~~~~~~~~~~~~~~~config"
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 {DSL#endpoint}
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 {DSL#endpoint}
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|
@@ -1,5 +1,5 @@
1
1
  module Trailblazer
2
2
  class Endpoint
3
- VERSION = "0.0.5"
3
+ VERSION = "0.0.6"
4
4
  end
5
5
  end
@@ -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
 
@@ -17,3 +17,7 @@ gem "trailblazer-operation"
17
17
  gem "trailblazer-endpoint", path: "../../."
18
18
  gem "jwt"
19
19
  gem "multi_json"
20
+ gem "minitest-line"
21
+ gem "trailblazer-cells"
22
+ gem "cells-erb"
23
+ gem "cells-rails"
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../..
3
3
  specs:
4
- trailblazer-endpoint (0.0.4)
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
 
@@ -0,0 +1,4 @@
1
+ module Song::Cell
2
+ class Create < Trailblazer::Cell
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Song::Cell
2
+ class New < Trailblazer::Cell
3
+ end
4
+ end
@@ -1,6 +1,6 @@
1
1
  module Song::Operation
2
2
  class Create < Trailblazer::Operation
3
- # include Trailblazer::Activity::Testing.def_steps(:model, :validate, :save)
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.to_s, domain_activity: Song::Operation::Create
6
- endpoint Song::Operation::Show.to_s, domain_activity: Song::Operation::Show do {Output(:not_found) => Track(:not_found)} end
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.to_s, representer_class: Song::Representer
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.to_s, representer_class: Song::Representer
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.to_s, representer_class: Song::Representer, protocol_failure_block: ->(ctx, endpoint_ctx:, **) { head endpoint_ctx[:status] + 1 }
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.to_s, domain_activity: Song::Operation::Show do {Output(:not_found) => Track(:not_found)} end
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.to_s, representer_class: Song::Representer,
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
- extend Trailblazer::Endpoint::Controller
3
- include Trailblazer::Endpoint::Controller::InstanceMethods::DSL
4
- include Trailblazer::Endpoint::Controller::Rails
5
- extend Trailblazer::Endpoint::Controller::Rails::DefaultBlocks
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
- # directive :options_for_endpoint, method(:options_for_endpoint), method(:request_options)
10
- # directive :options_for_flow_options, method(:options_for_flow_options)
11
- # directive :options_for_block_options, method(:options_for_block_options)
11
+ def self.options_for_endpoint(ctx, controller:, **)
12
+ {
13
+ session: controller.session,
14
+ }
15
+ end
12
16
 
13
- Protocol = Class.new(Trailblazer::Endpoint::Protocol) do
14
- # no {:seq} dependency
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
- puts "TODO: should we always inject params into the endpoint_ctx?"
19
- domain_ctx[:params][:authenticate] == "false" ? false : true
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[:params][:policy] == "false" ? false : true
31
+ Policy.(domain_ctx)
24
32
  end
25
33
  end
26
-
27
- endpoint protocol: Protocol, adapter: Trailblazer::Endpoint::Adapter::Web do
28
- {Output(:not_found) => Track(:not_found)}
29
- end
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
@@ -0,0 +1,5 @@
1
+ class HomeController < ApplicationController
2
+ def dashboard
3
+ render html: "Yo!"
4
+ end
5
+ end
@@ -1,24 +1,84 @@
1
+ #:endpoint
2
+ #:or
3
+ #:create
1
4
  class SongsController < ApplicationController::Web
2
- endpoint("Create", domain_activity: Song::Operation::Create) do {} end # FIXME: we still need to provide an empty hash here if we want to override the not_found behavior.
5
+ endpoint Song::Operation::Create
3
6
 
4
- # directive :options_for_domain_ctx, ->(ctx, **) { {seq: []} }
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 "Create"
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 create_with_options
11
- endpoint "Create", process_model: "yay!" do |ctx, model:, endpoint_ctx:, **|
12
- # TODO test process_model
13
- render json: [model, endpoint_ctx[:process_model]]
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
- def create_with_or
18
- endpoint "Create" do |ctx, model:, **|
19
- render json: {or: model}
20
- end.Or do |ctx, model:, endpoint_ctx:, **|
21
- render json: model, status: 422
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 = true
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
- resources :songs
4
- post "/songs/create_with_options", to: "songs#create_with_options"
5
- post "/songs/create_with_or", to: "songs#create_with_or"
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
- post_json "/v1/songs", {authenticate: false}
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 "create 200" do
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 "[\"1\",\"yay!\"]", response.body
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 "{\"or\":\"1\"}", response.body
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
- # default {failure} block doesn't do anything
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 "null", response.body
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.5
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-09-09 00:00:00.000000000 Z
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