ruby_event_store-browser 2.4.1 → 2.5.0

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: 7098204142d74b946bd45666e171992bbe6d804954afaffa21b418c7e694c75a
4
- data.tar.gz: '0997bcfc91dfccc4160eae1af9c57b971aa5168e3f552672b199da893691c15d'
3
+ metadata.gz: 2593c678fcd6240bb75582f44cdcec7b3f34b67a0c64340677a24eff75ecdc37
4
+ data.tar.gz: 34cc3a6f626035b142b240e55df3671e10ef019b2df5ce5da1e5523fa149acbc
5
5
  SHA512:
6
- metadata.gz: b0c88c6ef6ce0689450a223b2a24ddd36475f4f77d030a0e2851a4208001efa6f7da45c61c4d02151d51be7faed956bc4d3c0f24202bfa1b12422d9e153cef96
7
- data.tar.gz: 385e5a83a3e3b01d0ebe6f391236efab941da5f75efa20c2690efeef38262bbc6a7c17c95adbfa126c69450d513898723cbc50eba0408ade383aafa1374dbdd9
6
+ metadata.gz: 8d59bf1dcdc540583ce18115ef0ae1f0089f5f56c8542235d01f91311938d731c631e099ce0035ff22edf8d6bac14c481562566a4b04b408e1b7f5c4db495a99
7
+ data.tar.gz: 1b3181199b695e282c8505dbda4e442377a54858e4ca061bcd9431564dc9615455caab804754aa77bbc628d157b5ac69c38cabb48b94fba47e5518b7baea89d0
@@ -1,109 +1,144 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative "../browser"
4
- require "sinatra/base"
4
+ require "rack"
5
+ require "erb"
6
+ require "json"
5
7
 
6
8
  module RubyEventStore
7
9
  module Browser
8
- class App < Sinatra::Base
10
+ class App
9
11
  def self.for(
10
12
  event_store_locator:,
11
13
  host: nil,
12
14
  path: nil,
13
15
  api_url: nil,
14
- environment: :production,
16
+ environment: nil,
15
17
  related_streams_query: DEFAULT_RELATED_STREAMS_QUERY
16
18
  )
17
- self.tap do |app|
18
- app.settings.instance_exec do
19
- set :event_store_locator, event_store_locator
20
- set :related_streams_query, -> { related_streams_query }
21
- set :host, host
22
- set :root_path, path
23
- set :api_url, api_url
24
- set :environment, environment
25
- set :public_folder, "#{__dir__}/../../../public"
26
- end
19
+ warn(<<~WARN) if environment
20
+ Passing :environment to RubyEventStore::Browser::App.for is deprecated.
21
+
22
+ This option is no-op, has no effect and will be removed in next major release.
23
+ WARN
24
+ warn(<<~WARN) if host
25
+ Passing :host to RubyEventStore::Browser::App.for is deprecated.
26
+
27
+ This option will be removed in next major release.
28
+
29
+ Host and mount points are correctly recognized from Rack environment
30
+ and this option is redundant.
31
+ WARN
32
+ warn(<<~WARN) if path
33
+ Passing :path to RubyEventStore::Browser::App.for is deprecated.
34
+
35
+ This option will be removed in next major release.
36
+
37
+ Host and mount points are correctly recognized from Rack environment
38
+ and this option is redundant.
39
+ WARN
40
+
41
+ Rack::Builder.new do
42
+ use Rack::Static,
43
+ urls: {
44
+ "/ruby_event_store_browser.js" => "ruby_event_store_browser.js",
45
+ "/ruby_event_store_browser.css" => "ruby_event_store_browser.css",
46
+ "/bootstrap.js" => "bootstrap.js"
47
+ },
48
+ root: "#{__dir__}/../../../public"
49
+ run App.new(
50
+ event_store_locator: event_store_locator,
51
+ related_streams_query: related_streams_query,
52
+ host: host,
53
+ root_path: path,
54
+ api_url: api_url
55
+ )
27
56
  end
28
57
  end
29
58
 
30
- configure do
31
- set :host, nil
32
- set :root_path, nil
33
- set :api_url, nil
34
- set :event_store_locator, -> { }
35
- set :related_streams_query, nil
36
- set :protection, except: :path_traversal
37
-
38
- mime_type :json, "application/vnd.api+json"
59
+ def initialize(event_store_locator:, related_streams_query:, host:, root_path:, api_url:)
60
+ @event_store_locator = event_store_locator
61
+ @related_streams_query = related_streams_query
62
+ @routing = Urls.from_configuration(host, root_path, api_url)
39
63
  end
40
64
 
41
- get "/api/events/:id" do
42
- begin
43
- json Event.new(event_store: settings.event_store_locator, params: symbolized_params)
44
- rescue RubyEventStore::EventNotFound
45
- 404
65
+ def call(env)
66
+ router = Router.new(routing)
67
+ router.add_route("GET", "/api/events/:event_id") do |params|
68
+ json GetEvent.new(event_store: event_store, event_id: params.fetch("event_id"))
69
+ end
70
+ router.add_route("GET", "/api/streams/:stream_name") do |params, urls|
71
+ json GetStream.new(
72
+ stream_name: params.fetch("stream_name"),
73
+ routing: urls,
74
+ related_streams_query: related_streams_query
75
+ )
76
+ end
77
+ router.add_route("GET", "/api/streams/:stream_name/relationships/events") do |params, urls|
78
+ json GetEventsFromStream.new(
79
+ event_store: event_store,
80
+ routing: urls,
81
+ stream_name: params.fetch("stream_name"),
82
+ page: params["page"]
83
+ )
84
+ end
85
+ %w[/ /events/:event_id /streams/:stream_name].each do |starting_route|
86
+ router.add_route("GET", starting_route) do |_, urls|
87
+ erb bootstrap_html,
88
+ browser_js_src: urls.browser_js_url,
89
+ browser_css_src: urls.browser_css_url,
90
+ bootstrap_js_src: urls.bootstrap_js_url,
91
+ initial_data: {
92
+ rootUrl: urls.app_url,
93
+ apiUrl: urls.api_url,
94
+ resVersion: res_version
95
+ }
96
+ end
46
97
  end
98
+ router.handle(Rack::Request.new(env))
99
+ rescue EventNotFound, Router::NoMatch
100
+ not_found
47
101
  end
48
102
 
49
- get "/api/streams/:id" do
50
- json GetStream.new(
51
- stream_name: params[:id],
52
- routing: routing,
53
- related_streams_query: settings.related_streams_query
54
- )
55
- end
103
+ private
104
+
105
+ attr_reader :event_store_locator, :related_streams_query, :routing
56
106
 
57
- get "/api/streams/:id/relationships/events" do
58
- json GetEventsFromStream.new(
59
- event_store: settings.event_store_locator,
60
- params: symbolized_params,
61
- routing: routing
62
- )
107
+ def event_store
108
+ event_store_locator.call
63
109
  end
64
110
 
65
- get %r{/(events/.*|streams/.*)?} do
66
- erb "
67
- <!DOCTYPE html>
68
- <html>
69
- <head>
70
- <title>RubyEventStore::Browser</title>
71
- <meta name=\"ruby-event-store-browser-settings\" content='<%= browser_settings %>'>
72
- </head>
73
- <body>
74
- <script type=\"text/javascript\" src=\"<%= path %>/ruby_event_store_browser.js\"></script>
75
- <script type=\"text/javascript\" src=\"<%= path %>/bootstrap.js\"></script>
76
- </body>
77
- </html>
78
- ",
79
- locals: {
80
- path: settings.root_path || request.script_name
81
- }
111
+ def bootstrap_html
112
+ <<~HTML
113
+ <!DOCTYPE html>
114
+ <html>
115
+ <head>
116
+ <title>RubyEventStore::Browser</title>
117
+ <link type="text/css" rel="stylesheet" href="<%= browser_css_src %>">
118
+ <meta name="ruby-event-store-browser-settings" content="<%= Rack::Utils.escape_html(JSON.dump(initial_data)) %>">
119
+ </head>
120
+ <body>
121
+ <script type="text/javascript" src="<%= browser_js_src %>"></script>
122
+ <script type="text/javascript" src="<%= bootstrap_js_src %>"></script>
123
+ </body>
124
+ </html>
125
+ HTML
82
126
  end
83
127
 
84
- helpers do
85
- def symbolized_params
86
- params.each_with_object({}) { |(k, v), h| v.nil? ? next : h[k.to_sym] = v }
87
- end
128
+ def not_found
129
+ [404, {}, []]
130
+ end
88
131
 
89
- def routing
90
- Routing.new(settings.host || request.base_url, settings.root_path || request.script_name)
91
- end
132
+ def json(body)
133
+ [200, { "Content-Type" => "application/vnd.api+json" }, [JSON.dump(body.to_h)]]
134
+ end
92
135
 
93
- def browser_settings
94
- JSON.dump(
95
- {
96
- rootUrl: routing.root_url,
97
- apiUrl: settings.api_url || routing.api_url,
98
- resVersion: RubyEventStore::VERSION
99
- }
100
- )
101
- end
136
+ def erb(template, **locals)
137
+ [200, { "Content-Type" => "text/html;charset=utf-8" }, [ERB.new(template).result_with_hash(locals)]]
138
+ end
102
139
 
103
- def json(data)
104
- content_type :json
105
- JSON.dump data.as_json
106
- end
140
+ def res_version
141
+ RubyEventStore::VERSION
107
142
  end
108
143
  end
109
144
  end
@@ -2,18 +2,20 @@
2
2
 
3
3
  module RubyEventStore
4
4
  module Browser
5
- class Event
6
- attr_reader :event_store, :params
7
-
8
- def initialize(event_store:, params:)
5
+ class GetEvent
6
+ def initialize(event_store:, event_id:)
9
7
  @event_store = event_store
10
- @params = params
8
+ @event_id = event_id
11
9
  end
12
10
 
13
- def as_json
11
+ def to_h
14
12
  { data: JsonApiEvent.new(event, parent_event_id).to_h }
15
13
  end
16
14
 
15
+ private
16
+
17
+ attr_reader :event_store, :event_id
18
+
17
19
  def event
18
20
  @event ||= event_store.read.event!(event_id)
19
21
  end
@@ -21,10 +23,6 @@ module RubyEventStore
21
23
  def parent_event_id
22
24
  event_store.read.event(event.metadata.fetch(:causation_id))&.event_id if event.metadata.has_key?(:causation_id)
23
25
  end
24
-
25
- def event_id
26
- params.fetch(:id)
27
- end
28
26
  end
29
27
  end
30
28
  end
@@ -5,18 +5,21 @@ module RubyEventStore
5
5
  class GetEventsFromStream
6
6
  HEAD = Object.new
7
7
 
8
- attr_reader :event_store, :params, :routing
9
-
10
- def initialize(event_store:, params:, routing:)
8
+ def initialize(event_store:, routing:, stream_name:, page:)
11
9
  @event_store = event_store
12
- @params = params
13
10
  @routing = routing
11
+ @stream_name = stream_name
12
+ @page = page || {}
14
13
  end
15
14
 
16
- def as_json
15
+ def to_h
17
16
  { data: events.map { |e| JsonApiEvent.new(e, nil).to_h }, links: links }
18
17
  end
19
18
 
19
+ private
20
+
21
+ attr_reader :event_store, :routing, :stream_name, :page
22
+
20
23
  def events
21
24
  @events ||=
22
25
  case direction
@@ -88,11 +91,11 @@ module RubyEventStore
88
91
  end
89
92
 
90
93
  def count
91
- Integer(pagination_param[:count] || PAGE_SIZE)
94
+ Integer(page["count"] || PAGE_SIZE)
92
95
  end
93
96
 
94
97
  def direction
95
- case pagination_param[:direction]
98
+ case page["direction"]
96
99
  when "forward"
97
100
  :forward
98
101
  else
@@ -101,21 +104,13 @@ module RubyEventStore
101
104
  end
102
105
 
103
106
  def position
104
- case pagination_param[:position]
107
+ case page["position"]
105
108
  when "head", nil
106
109
  HEAD
107
110
  else
108
- pagination_param.fetch(:position)
111
+ page.fetch("position")
109
112
  end
110
113
  end
111
-
112
- def stream_name
113
- params.fetch(:id)
114
- end
115
-
116
- def pagination_param
117
- params[:page] || {}
118
- end
119
114
  end
120
115
  end
121
116
  end
@@ -7,7 +7,7 @@ module RubyEventStore
7
7
  @related_streams_query = related_streams_query
8
8
  end
9
9
 
10
- def as_json
10
+ def to_h
11
11
  {
12
12
  data: {
13
13
  id: stream_name,
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyEventStore
4
+ module Browser
5
+ class Router
6
+ NoMatch = Class.new(StandardError)
7
+
8
+ class Route
9
+ NAMED_SEGMENTS_PATTERN = %r{\/([^\/]*):([^:$\/]+)}.freeze
10
+ private_constant :NAMED_SEGMENTS_PATTERN
11
+
12
+ def initialize(request_method, pattern, &block)
13
+ @request_method = request_method
14
+ @pattern = pattern
15
+ @handler = block
16
+ end
17
+
18
+ def match(request)
19
+ return unless request.request_method.eql?(request_method)
20
+
21
+ match_data = regexp.match(File.join("/", request.path_info))
22
+ match_data.named_captures.transform_values { |v| Rack::Utils.unescape(v) } if match_data
23
+ end
24
+
25
+ def call(params, urls)
26
+ handler[params, urls]
27
+ end
28
+
29
+ private
30
+
31
+ def regexp
32
+ /\A#{pattern.gsub(NAMED_SEGMENTS_PATTERN, '/\1(?<\2>[^$/]+)')}\Z/
33
+ end
34
+
35
+ attr_reader :request_method, :pattern, :handler
36
+ end
37
+
38
+ def initialize(urls = Urls.initial)
39
+ @routes = Array.new
40
+ @urls = urls
41
+ end
42
+
43
+ def add_route(request_method, pattern, &block)
44
+ routes << Route.new(request_method, pattern, &block)
45
+ end
46
+
47
+ def handle(request)
48
+ routes.each do |route|
49
+ route_params = route.match(request)
50
+ return route.call(request.params.merge(route_params), urls.with_request(request)) if route_params
51
+ end
52
+ raise NoMatch
53
+ end
54
+
55
+ private
56
+
57
+ attr_reader :routes, :urls
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,64 @@
1
+ module RubyEventStore
2
+ module Browser
3
+ class Urls
4
+ def self.from_configuration(host, root_path, api_url = nil)
5
+ new(host, root_path, api_url)
6
+ end
7
+
8
+ def self.initial
9
+ new(nil, nil, nil)
10
+ end
11
+
12
+ def with_request(request)
13
+ Urls.new(host || request.base_url, root_path || request.script_name, api_url)
14
+ end
15
+
16
+ attr_reader :app_url, :api_url, :host, :root_path
17
+
18
+ def initialize(host, root_path, api_url)
19
+ @host = host
20
+ @root_path = root_path
21
+ @app_url = [host, root_path].compact.reduce(:+)
22
+ @api_url = api_url || ("#{app_url}/api" if app_url)
23
+ end
24
+
25
+ def events_url
26
+ "#{api_url}/events"
27
+ end
28
+
29
+ def streams_url
30
+ "#{api_url}/streams"
31
+ end
32
+
33
+ def paginated_events_from_stream_url(id:, position: nil, direction: nil, count: nil)
34
+ stream_name = Rack::Utils.escape(id)
35
+ query_string =
36
+ URI.encode_www_form(
37
+ { "page[position]" => position, "page[direction]" => direction, "page[count]" => count }.compact
38
+ )
39
+
40
+ if query_string.empty?
41
+ "#{api_url}/streams/#{stream_name}/relationships/events"
42
+ else
43
+ "#{api_url}/streams/#{stream_name}/relationships/events?#{query_string}"
44
+ end
45
+ end
46
+
47
+ def browser_js_url
48
+ "#{app_url}/ruby_event_store_browser.js"
49
+ end
50
+
51
+ def browser_css_url
52
+ "#{app_url}/ruby_event_store_browser.css"
53
+ end
54
+
55
+ def bootstrap_js_url
56
+ "#{app_url}/bootstrap.js"
57
+ end
58
+
59
+ def ==(o)
60
+ self.class.eql?(o.class) && app_url.eql?(o.app_url) && api_url.eql?(o.api_url)
61
+ end
62
+ end
63
+ end
64
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RubyEventStore
4
4
  module Browser
5
- VERSION = "2.4.1"
5
+ VERSION = "2.5.0"
6
6
  end
7
7
  end
@@ -8,8 +8,9 @@ module RubyEventStore
8
8
  end
9
9
  end
10
10
 
11
- require_relative "browser/event"
11
+ require_relative "browser/get_event"
12
12
  require_relative "browser/json_api_event"
13
13
  require_relative "browser/get_events_from_stream"
14
14
  require_relative "browser/get_stream"
15
- require_relative "browser/routing"
15
+ require_relative "browser/urls"
16
+ require_relative "browser/router"