ruby_event_store-browser 2.4.0 → 2.5.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: df8e9b50395fcb93bc771370a2d27c6cb668753f8f0d9d7784f4ef56a4b17545
4
- data.tar.gz: 318dca02e6b96941d0375a3748385d5b48e494318b0e12a50c7dd70ee4ee4445
3
+ metadata.gz: 6d8751953fbf8d113ab312cf8bb35f847d5ac5d71dc1ad868263b900b6659801
4
+ data.tar.gz: 3ac9153d0e7f0b6f1310e8aa2bed3e25d81361a53bc043b21ebf48615ec69dbe
5
5
  SHA512:
6
- metadata.gz: 963db3c296db6dfdb4d24630a9060dc7379f6fc8f93dfb7858b903568de7f7e71bcca47e817b45eecab75c6cb870181d02cb4f66adbfad4814cc67c650e5fe20
7
- data.tar.gz: 782d3ccf1aee4a51ba1a1864ad6bfd94b8c2adbce01818ebba5f8df808224ed7ecadd111dd7696726431ab1c14dc7b13c38a8a2ec8273b122965f2ae074ccaa0
6
+ metadata.gz: 2d18795af4c24ad82c42b9214964387884e1a5c525509a8f559b11679e5d222cfb60ac75daab7d17dbc0fb840f63edea3a9cc5d6c17be0ee1e501b5deae21322
7
+ data.tar.gz: 97b5d401e981d3dfbc17c3ddaf8b80436ec18acd39aafdf679e004460d056da2201599cae84bf19ec3d6fa149f2e44d02a4dca915a20960008337ddc5e0c9528
@@ -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.0"
5
+ VERSION = "2.5.1"
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"