pg_eventstore 1.0.4 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +5 -0
  3. data/README.md +2 -1
  4. data/db/migrations/6_add_commands_data.sql +2 -0
  5. data/docs/configuration.md +0 -5
  6. data/lib/pg_eventstore/queries.rb +0 -4
  7. data/lib/pg_eventstore/stream.rb +2 -0
  8. data/lib/pg_eventstore/subscriptions/basic_runner.rb +10 -0
  9. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_feeder_commands.rb +2 -27
  10. data/lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb +4 -37
  11. data/lib/pg_eventstore/subscriptions/commands_handler.rb +1 -1
  12. data/lib/pg_eventstore/subscriptions/events_processor.rb +11 -3
  13. data/lib/pg_eventstore/subscriptions/extensions/base_command_extension.rb +47 -0
  14. data/lib/pg_eventstore/subscriptions/extensions/command_class_lookup_extension.rb +15 -0
  15. data/lib/pg_eventstore/{queries → subscriptions/queries}/subscription_command_queries.rb +19 -13
  16. data/lib/pg_eventstore/{queries → subscriptions/queries}/subscriptions_set_command_queries.rb +18 -12
  17. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands/base.rb +33 -0
  18. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands/restore.rb +14 -0
  19. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands/start_all.rb +14 -0
  20. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands/stop.rb +14 -0
  21. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands/stop_all.rb +14 -0
  22. data/lib/pg_eventstore/subscriptions/subscription_feeder_commands.rb +14 -0
  23. data/lib/pg_eventstore/subscriptions/subscription_runner.rb +2 -1
  24. data/lib/pg_eventstore/subscriptions/subscription_runner_commands/base.rb +36 -0
  25. data/lib/pg_eventstore/subscriptions/subscription_runner_commands/reset_position.rb +25 -0
  26. data/lib/pg_eventstore/subscriptions/subscription_runner_commands/restore.rb +14 -0
  27. data/lib/pg_eventstore/subscriptions/subscription_runner_commands/start.rb +14 -0
  28. data/lib/pg_eventstore/subscriptions/subscription_runner_commands/stop.rb +14 -0
  29. data/lib/pg_eventstore/subscriptions/subscription_runner_commands.rb +14 -0
  30. data/lib/pg_eventstore/subscriptions/subscriptions_manager.rb +9 -1
  31. data/lib/pg_eventstore/version.rb +1 -1
  32. data/lib/pg_eventstore/web/application.rb +9 -3
  33. data/lib/pg_eventstore/web/public/javascripts/pg_eventstore.js +18 -0
  34. data/lib/pg_eventstore/web/subscriptions/helpers.rb +28 -6
  35. data/lib/pg_eventstore/web/views/subscriptions/index.erb +39 -7
  36. metadata +21 -6
  37. /data/lib/pg_eventstore/{queries → subscriptions/queries}/subscription_queries.rb +0 -0
  38. /data/lib/pg_eventstore/{queries → subscriptions/queries}/subscriptions_set_queries.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d5a69951bc18bd74869eb843b9e017a1c1f238bfc17750400e30b99f7d7b9f2d
4
- data.tar.gz: 56ce94941096227944e8d5f11b9c7f9ca2f1fd816ad477cb02abe58604945c47
3
+ metadata.gz: b824856ac1174e2aa2431388e12cd2997256ad4a5638c319d22ee1863c24f4ea
4
+ data.tar.gz: 64f73d163abf7a80e9384483857ab1d2cfd38da6d98f2d8f7552a24ac8a7fb3c
5
5
  SHA512:
6
- metadata.gz: f2f682990d1450b874e3d1ad9cf68e3e1cf7dcd546c78bb101982c5a03c5f355f31101442b2dd9a2c59531535e02eff01b12b119c79beca331f7262e927ebe5c
7
- data.tar.gz: 2b2624a388eba5772c6a3bdd601ffe6fbcdb3c08c0d3c4585c1dfcf03862cf94f19e39db453950294f1a5a01a35e5ee33c975632c7ba6a2c966fcb85ff6dbb5d
6
+ metadata.gz: c37a3572d224ed2706bf31d416da5bdfb91f37323225fd1f60f060eb76cd96ff33907055f32924cc6d155aa8abae44056e1f982eff560dcca27b70f0c43afa3e
7
+ data.tar.gz: 891b7a1e7697b31f53964aeacfe9ae516294421c63e9a7d7095b0bd9489b5cf56ef9101639317cd5894d2f4e7f72b51be6ea14c9b7e5c238aaee2e25379d7a7b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.1.0]
4
+ - Add "Reset position" button on Subscriptions Admin UI page
5
+
6
+ **Note** This release includes a migration to support new functional. Please don't forget to run `rake pg_eventstore:migrate` to apply latest db changes.
7
+
3
8
  ## [1.0.4]
4
9
  - Fix bug which caused slow Subscriptions to stop processing new events
5
10
  - Optimize Subscriptions update queries
data/README.md CHANGED
@@ -5,7 +5,8 @@ Implements database and API to store and read events in event sourced systems.
5
5
  ## Requirements
6
6
 
7
7
  - `pg_eventstore` requires a PostgreSQL database with jsonb data type support (which means you need to have v9.2+). However it is recommended to use a non [EOL](https://www.postgresql.org/support/versioning/) PostgreSQL version, because the development of this gem is targeted at current PostgreSQL versions.
8
- - It is recommend you to have the default value set for `default_transaction_isolation` PostgreSQL config setting(`"read committed"`) as the implementation relies on it. All other transaction isolation levels(`"repeatable read"` and `"serializable"`) may cause unexpected serialization errors which you will have to handle by yourself(e.g. by always wrapping your code using [`#multiple`](docs/multiple_commands.md)).
8
+ - It is recommended you to have the default value set for `default_transaction_isolation` PostgreSQL config setting(`"read committed"`) as the implementation relies on it. All other transaction isolation levels(`"repeatable read"` and `"serializable"`) may cause unexpected serialization errors.
9
+ - It is recommended to use a connection pooler (for example [PgBouncer](https://www.pgbouncer.org/)) in `transaction` pool mode to lower the load on a database.
9
10
  - `pg_eventstore` requires ruby v3+. The development of this gem is targeted at [current](https://endoflife.date/ruby) ruby versions.
10
11
 
11
12
  ## Installation
@@ -0,0 +1,2 @@
1
+ ALTER TABLE subscription_commands ADD COLUMN data jsonb DEFAULT '{}'::jsonb;
2
+ ALTER TABLE subscriptions_set_commands ADD COLUMN data jsonb DEFAULT '{}'::jsonb;
@@ -116,8 +116,3 @@ end
116
116
 
117
117
  Taking this into account you may want to increase `connection_pool_size` up to the number of your application's threads(
118
118
  or subscriptions).
119
-
120
- ### Usage of external connection pooler
121
-
122
- `pg_eventstore` does not use any session-specific features of PostgreSQL. You can use any PostgreSQL connection pooler
123
- you like, such as [PgBouncer](https://www.pgbouncer.org/) for example.
@@ -5,10 +5,6 @@ require_relative 'query_builders/events_filtering_query'
5
5
  require_relative 'queries/transaction_queries'
6
6
  require_relative 'queries/event_queries'
7
7
  require_relative 'queries/partition_queries'
8
- require_relative 'queries/subscription_queries'
9
- require_relative 'queries/subscriptions_set_queries'
10
- require_relative 'queries/subscription_command_queries'
11
- require_relative 'queries/subscriptions_set_command_queries'
12
8
  require_relative 'queries/links_resolver'
13
9
 
14
10
  module PgEventstore
@@ -72,6 +72,8 @@ module PgEventstore
72
72
  hash == another.hash
73
73
  end
74
74
 
75
+ # @param other_stream [Stream]
76
+ # @return [Boolean]
75
77
  def ==(other_stream)
76
78
  return false unless other_stream.is_a?(Stream)
77
79
 
@@ -182,6 +182,16 @@ module PgEventstore
182
182
  @state.to_s
183
183
  end
184
184
 
185
+ # @param state [Symbol]
186
+ # @return [Object] a result of evaluating of passed block
187
+ def within_state(state, &blk)
188
+ synchronize do
189
+ return unless @state.public_send("#{RunnerState::STATES.fetch(state)}?")
190
+
191
+ yield
192
+ end
193
+ end
194
+
185
195
  private
186
196
 
187
197
  def synchronize
@@ -3,8 +3,6 @@
3
3
  module PgEventstore
4
4
  module CommandHandlers
5
5
  class SubscriptionFeederCommands
6
- AVAILABLE_COMMANDS = %i[StopAll StartAll Restore Stop].to_h { [_1, _1.to_s] }.freeze
7
-
8
6
  # @param config_name [Symbol]
9
7
  # @param subscription_feeder [PgEventstore::SubscriptionFeeder]
10
8
  def initialize(config_name, subscription_feeder)
@@ -16,15 +14,8 @@ module PgEventstore
16
14
  # @return [void]
17
15
  def process
18
16
  queries.find_commands(@subscription_feeder.id).each do |command|
19
- unless AVAILABLE_COMMANDS.values.include?(command[:name])
20
- PgEventstore.logger&.warn(
21
- "#{self.class.name}: Don't know how to handle #{command[:name].inspect}. Details: #{command.inspect}."
22
- )
23
- next
24
- end
25
- send(Utils.underscore_str(command[:name]))
26
- ensure
27
- queries.delete(command[:id])
17
+ command.exec_cmd(@subscription_feeder)
18
+ queries.delete(command.id)
28
19
  end
29
20
  end
30
21
 
@@ -39,22 +30,6 @@ module PgEventstore
39
30
  def connection
40
31
  PgEventstore.connection(@config_name)
41
32
  end
42
-
43
- def stop_all
44
- @subscription_feeder.stop_all
45
- end
46
-
47
- def start_all
48
- @subscription_feeder.start_all
49
- end
50
-
51
- def restore
52
- @subscription_feeder.restore
53
- end
54
-
55
- def stop
56
- @subscription_feeder.stop
57
- end
58
33
  end
59
34
  end
60
35
  end
@@ -3,30 +3,21 @@
3
3
  module PgEventstore
4
4
  module CommandHandlers
5
5
  class SubscriptionRunnersCommands
6
- AVAILABLE_COMMANDS = %i[Start Stop Restore].to_h { [_1, _1.to_s] }.freeze
7
-
8
6
  # @param config_name [Symbol]
9
7
  # @param runners [Array<PgEventstore::SubscriptionRunner>]
10
8
  # @param subscriptions_set_id [Integer]
11
9
  def initialize(config_name, runners, subscriptions_set_id)
12
10
  @config_name = config_name
13
- @runners = runners
11
+ @runners = runners.to_h { |runner| [runner.id, runner] }
14
12
  @subscriptions_set_id = subscriptions_set_id
15
13
  end
16
14
 
17
15
  # Look up commands for all given SubscriptionRunner-s and execute them
18
16
  # @return [void]
19
17
  def process
20
- queries.find_commands(@runners.map(&:id), subscriptions_set_id: @subscriptions_set_id).each do |command|
21
- unless AVAILABLE_COMMANDS.values.include?(command[:name])
22
- PgEventstore.logger&.warn(
23
- "#{self.class.name}: Don't know how to handle #{command[:name].inspect}. Details: #{command.inspect}."
24
- )
25
- next
26
- end
27
- send(Utils.underscore_str(command[:name]), command[:subscription_id])
28
- ensure
29
- queries.delete(command[:id])
18
+ queries.find_commands(@runners.keys, subscriptions_set_id: @subscriptions_set_id).each do |command|
19
+ command.exec_cmd(@runners[command.subscription_id]) if @runners[command.subscription_id]
20
+ queries.delete(command.id)
30
21
  end
31
22
  end
32
23
 
@@ -41,30 +32,6 @@ module PgEventstore
41
32
  def connection
42
33
  PgEventstore.connection(@config_name)
43
34
  end
44
-
45
- # @param subscription_id [Integer]
46
- # @return [PgEventstore::SubscriptionRunner, nil]
47
- def find_subscription_runner(subscription_id)
48
- @runners.find { |runner| runner.id == subscription_id }
49
- end
50
-
51
- # @param subscription_id [Integer]
52
- # @return [void]
53
- def start(subscription_id)
54
- find_subscription_runner(subscription_id)&.start
55
- end
56
-
57
- # @param subscription_id [Integer]
58
- # @return [void]
59
- def restore(subscription_id)
60
- find_subscription_runner(subscription_id)&.restore
61
- end
62
-
63
- # @param subscription_id [Integer]
64
- # @return [void]
65
- def stop(subscription_id)
66
- find_subscription_runner(subscription_id)&.stop_async
67
- end
68
35
  end
69
36
  end
70
37
  end
@@ -11,7 +11,7 @@ module PgEventstore
11
11
  extend Forwardable
12
12
 
13
13
  RESTART_DELAY = 5 # seconds
14
- PULL_INTERVAL = 1
14
+ PULL_INTERVAL = 1 # seconds
15
15
 
16
16
  def_delegators :@basic_runner, :start, :stop, :state, :stop_async, :wait_for_finish
17
17
 
@@ -7,7 +7,8 @@ module PgEventstore
7
7
  include Extensions::CallbacksExtension
8
8
  extend Forwardable
9
9
 
10
- def_delegators :@basic_runner, :state, :start, :stop, :wait_for_finish, :stop_async, :restore, :running?
10
+ def_delegators :@basic_runner, :state, :start, :stop, :wait_for_finish, :stop_async, :restore, :running?,
11
+ :within_state
11
12
 
12
13
  # @param handler [#call]
13
14
  def initialize(handler)
@@ -22,8 +23,10 @@ module PgEventstore
22
23
  def feed(raw_events)
23
24
  raise EmptyChunkFedError.new("Empty chunk was fed!") if raw_events.empty?
24
25
 
25
- callbacks.run_callbacks(:feed, global_position(raw_events.last))
26
- @raw_events.push(*raw_events)
26
+ within_state(:running) do
27
+ callbacks.run_callbacks(:feed, global_position(raw_events.last))
28
+ @raw_events.push(*raw_events)
29
+ end
27
30
  end
28
31
 
29
32
  # Number of unprocessed events which are currently in a queue
@@ -32,6 +35,11 @@ module PgEventstore
32
35
  @raw_events.size
33
36
  end
34
37
 
38
+ # @return [void]
39
+ def clear_chunk
40
+ @raw_events.clear
41
+ end
42
+
35
43
  private
36
44
 
37
45
  # @param raw_event [Hash]
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module Extensions
5
+ module BaseCommandExtension
6
+ def self.included(klass)
7
+ super
8
+ klass.extend(ClassMethods)
9
+
10
+ # Detects whether command is a known command. Known commands are commands, inherited from Base class.
11
+ # @return [Boolean]
12
+ klass.define_singleton_method(:known_command?) do
13
+ self < klass
14
+ end
15
+ end
16
+
17
+ module ClassMethods
18
+ # @param data [Hash]
19
+ # @return [Hash]
20
+ def parse_data(data)
21
+ {}
22
+ end
23
+ end
24
+
25
+ # @return [Integer]
26
+ def hash
27
+ options_hash.hash
28
+ end
29
+
30
+ # @param another [Object]
31
+ # @return [Boolean]
32
+ def eql?(another)
33
+ return false unless another.is_a?(self.class)
34
+
35
+ hash == another.hash
36
+ end
37
+
38
+ # @param another [Object]
39
+ # @return [Boolean]
40
+ def ==(another)
41
+ return false unless another.is_a?(self.class)
42
+
43
+ options_hash == another.options_hash
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module Extensions
5
+ module CommandClassLookupExtension
6
+ # @param cmd_name [String, Symbol]
7
+ # @return [Class]
8
+ def command_class(cmd_name)
9
+ const_get(cmd_name, false)
10
+ rescue NameError
11
+ self::Base
12
+ end
13
+ end
14
+ end
15
+ end
@@ -11,18 +11,22 @@ module PgEventstore
11
11
  @connection = connection
12
12
  end
13
13
 
14
- # @see #find_by or #create for available arguments
15
- # @return [Hash]
16
- def find_or_create_by(...)
14
+ # @param subscription_id [Integer]
15
+ # @param subscriptions_set_id [Integer]
16
+ # @param command_name [String]
17
+ # @param data [Hash]
18
+ # @return [PgEventstore::SubscriptionRunnerCommands::Abstract]
19
+ def find_or_create_by(subscription_id:, subscriptions_set_id:, command_name:, data:)
17
20
  transaction_queries.transaction do
18
- find_by(...) || create(...)
21
+ find_by(subscription_id: subscription_id, subscriptions_set_id: subscriptions_set_id, command_name: command_name) ||
22
+ create(subscription_id: subscription_id, subscriptions_set_id: subscriptions_set_id, command_name: command_name, data: data)
19
23
  end
20
24
  end
21
25
 
22
26
  # @param subscription_id [Integer]
23
27
  # @param subscriptions_set_id [Integer]
24
28
  # @param command_name [String]
25
- # @return [Hash, nil]
29
+ # @return [PgEventstore::SubscriptionRunnerCommands::Abstract, nil]
26
30
  def find_by(subscription_id:, subscriptions_set_id:, command_name:)
27
31
  sql_builder =
28
32
  SQLBuilder.new.
@@ -43,22 +47,23 @@ module PgEventstore
43
47
  # @param subscription_id [Integer]
44
48
  # @param subscriptions_set_id [Integer]
45
49
  # @param command_name [String]
46
- # @return [Hash]
47
- def create(subscription_id:, subscriptions_set_id:, command_name:)
50
+ # @param data [Hash]
51
+ # @return [PgEventstore::SubscriptionRunnerCommands::Abstract]
52
+ def create(subscription_id:, subscriptions_set_id:, command_name:, data:)
48
53
  sql = <<~SQL
49
- INSERT INTO subscription_commands (name, subscription_id, subscriptions_set_id)
50
- VALUES ($1, $2, $3)
54
+ INSERT INTO subscription_commands (name, subscription_id, subscriptions_set_id, data)
55
+ VALUES ($1, $2, $3, $4)
51
56
  RETURNING *
52
57
  SQL
53
58
  pg_result = connection.with do |conn|
54
- conn.exec_params(sql, [command_name, subscription_id, subscriptions_set_id])
59
+ conn.exec_params(sql, [command_name, subscription_id, subscriptions_set_id, data])
55
60
  end
56
61
  deserialize(pg_result.to_a.first)
57
62
  end
58
63
 
59
64
  # @param subscription_ids [Array<Integer>]
60
65
  # @param subscriptions_set_id [Integer]
61
- # @return [Array<Hash>]
66
+ # @return [Array<PgEventstore::SubscriptionRunnerCommands::Abstract>]
62
67
  def find_commands(subscription_ids, subscriptions_set_id:)
63
68
  return [] if subscription_ids.empty?
64
69
 
@@ -93,9 +98,10 @@ module PgEventstore
93
98
  end
94
99
 
95
100
  # @param hash [Hash]
96
- # @return [Hash]
101
+ # @return [PgEventstore::SubscriptionRunnerCommands::Base]
97
102
  def deserialize(hash)
98
- hash.transform_keys(&:to_sym)
103
+ attrs = hash.transform_keys(&:to_sym)
104
+ SubscriptionRunnerCommands.command_class(attrs[:name]).new(**attrs)
99
105
  end
100
106
  end
101
107
  end
@@ -11,16 +11,20 @@ module PgEventstore
11
11
  @connection = connection
12
12
  end
13
13
 
14
- # @return [Hash]
15
- def find_or_create_by(...)
14
+ # @param subscriptions_set_id [Integer]
15
+ # @param command_name [String]
16
+ # @param data [Hash]
17
+ # @return [PgEventstore::SubscriptionFeederCommands::Abstract]
18
+ def find_or_create_by(subscriptions_set_id:, command_name:, data:)
16
19
  transaction_queries.transaction do
17
- find_by(...) || create(...)
20
+ find_by(subscriptions_set_id: subscriptions_set_id, command_name: command_name) ||
21
+ create(subscriptions_set_id: subscriptions_set_id, command_name: command_name, data: data)
18
22
  end
19
23
  end
20
24
 
21
25
  # @param subscriptions_set_id [Integer]
22
26
  # @param command_name [String]
23
- # @return [Hash, nil]
27
+ # @return [PgEventstore::SubscriptionFeederCommands::Abstract, nil]
24
28
  def find_by(subscriptions_set_id:, command_name:)
25
29
  sql_builder =
26
30
  SQLBuilder.new.
@@ -37,21 +41,22 @@ module PgEventstore
37
41
 
38
42
  # @param subscriptions_set_id [Integer]
39
43
  # @param command_name [String]
40
- # @return [Hash]
41
- def create(subscriptions_set_id:, command_name:)
44
+ # @param data [Hash]
45
+ # @return [PgEventstore::SubscriptionFeederCommands::Abstract]
46
+ def create(subscriptions_set_id:, command_name:, data:)
42
47
  sql = <<~SQL
43
- INSERT INTO subscriptions_set_commands (name, subscriptions_set_id)
44
- VALUES ($1, $2)
48
+ INSERT INTO subscriptions_set_commands (name, subscriptions_set_id, data)
49
+ VALUES ($1, $2, $3)
45
50
  RETURNING *
46
51
  SQL
47
52
  pg_result = connection.with do |conn|
48
- conn.exec_params(sql, [command_name, subscriptions_set_id])
53
+ conn.exec_params(sql, [command_name, subscriptions_set_id, data])
49
54
  end
50
55
  deserialize(pg_result.to_a.first)
51
56
  end
52
57
 
53
58
  # @param subscriptions_set_id [Integer]
54
- # @return [Array<Hash>]
59
+ # @return [Array<PgEventstore::SubscriptionFeederCommands::Abstract>]
55
60
  def find_commands(subscriptions_set_id)
56
61
  sql_builder =
57
62
  SQLBuilder.new.select('*').
@@ -75,9 +80,10 @@ module PgEventstore
75
80
  private
76
81
 
77
82
  # @param hash [Hash]
78
- # @return [Hash]
83
+ # @return [PgEventstore::SubscriptionFeederCommands::Base]
79
84
  def deserialize(hash)
80
- hash.transform_keys(&:to_sym)
85
+ attrs = hash.transform_keys(&:to_sym)
86
+ SubscriptionFeederCommands.command_class(attrs[:name]).new(**attrs)
81
87
  end
82
88
 
83
89
  # @return [PgEventstore::TransactionQueries]
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionFeederCommands
5
+ # @!visibility private
6
+ class Base
7
+ include Extensions::OptionsExtension
8
+ include Extensions::BaseCommandExtension
9
+
10
+ # @!attribute id
11
+ # @return [Integer]
12
+ attribute(:id)
13
+ # @!attribute name
14
+ # @return [String]
15
+ attribute(:name) { self.class.name.split('::').last }
16
+ # @!attribute subscriptions_set_id
17
+ # @return [Integer]
18
+ attribute(:subscriptions_set_id)
19
+ # @!attribute data
20
+ # @return [Hash]
21
+ attribute(:data) { {} }
22
+ # @!attribute created_at
23
+ # @return [Time]
24
+ attribute(:created_at)
25
+
26
+ # @param subscription_feeder [PgEventstore::SubscriptionFeeder]
27
+ # @return [void]
28
+ def exec_cmd(subscription_feeder)
29
+ # Implement it in the subclass
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionFeederCommands
5
+ # @!visibility private
6
+ class Restore < Base
7
+ # @param subscription_feeder [PgEventstore::SubscriptionFeeder]
8
+ # @return [void]
9
+ def exec_cmd(subscription_feeder)
10
+ subscription_feeder.restore
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionFeederCommands
5
+ # @!visibility private
6
+ class StartAll < Base
7
+ # @param subscription_feeder [PgEventstore::SubscriptionFeeder]
8
+ # @return [void]
9
+ def exec_cmd(subscription_feeder)
10
+ subscription_feeder.start_all
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionFeederCommands
5
+ # @!visibility private
6
+ class Stop < Base
7
+ # @param subscription_feeder [PgEventstore::SubscriptionFeeder]
8
+ # @return [void]
9
+ def exec_cmd(subscription_feeder)
10
+ subscription_feeder.stop
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionFeederCommands
5
+ # @!visibility private
6
+ class StopAll < Base
7
+ # @param subscription_feeder [PgEventstore::SubscriptionFeeder]
8
+ # @return [void]
9
+ def exec_cmd(subscription_feeder)
10
+ subscription_feeder.stop_all
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'subscription_feeder_commands/base'
4
+ require_relative 'subscription_feeder_commands/restore'
5
+ require_relative 'subscription_feeder_commands/start_all'
6
+ require_relative 'subscription_feeder_commands/stop'
7
+ require_relative 'subscription_feeder_commands/stop_all'
8
+
9
+ module PgEventstore
10
+ # @!visibility private
11
+ module SubscriptionFeederCommands
12
+ extend Extensions::CommandClassLookupExtension
13
+ end
14
+ end
@@ -16,7 +16,8 @@ module PgEventstore
16
16
 
17
17
  attr_reader :subscription
18
18
 
19
- def_delegators :@events_processor, :start, :stop, :stop_async, :feed, :wait_for_finish, :restore, :state, :running?
19
+ def_delegators :@events_processor, :start, :stop, :stop_async, :feed, :wait_for_finish, :restore, :state, :running?,
20
+ :clear_chunk, :within_state
20
21
  def_delegators :@subscription, :lock!, :id
21
22
 
22
23
  # @param stats [PgEventstore::SubscriptionHandlerPerformance]
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionRunnerCommands
5
+ # @!visibility private
6
+ class Base
7
+ include Extensions::OptionsExtension
8
+ include Extensions::BaseCommandExtension
9
+
10
+ # @!attribute id
11
+ # @return [Integer]
12
+ attribute(:id)
13
+ # @!attribute name
14
+ # @return [String]
15
+ attribute(:name) { self.class.name.split('::').last }
16
+ # @!attribute subscription_id
17
+ # @return [Integer]
18
+ attribute(:subscription_id)
19
+ # @!attribute subscriptions_set_id
20
+ # @return [Integer]
21
+ attribute(:subscriptions_set_id)
22
+ # @!attribute data
23
+ # @return [Hash]
24
+ attribute(:data) { {} }
25
+ # @!attribute created_at
26
+ # @return [Time]
27
+ attribute(:created_at)
28
+
29
+ # @param subscription_runner [PgEventstore::SubscriptionRunner]
30
+ # @return [void]
31
+ def exec_cmd(subscription_runner)
32
+ # Implement it in the subclass
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionRunnerCommands
5
+ # @!visibility private
6
+ class ResetPosition < Base
7
+ class << self
8
+ # @param data [Hash]
9
+ # @return [Hash]
10
+ def parse_data(data)
11
+ { 'position' => Integer(data['position']) }
12
+ end
13
+ end
14
+
15
+ # @param subscription_runner [PgEventstore::SubscriptionRunner]
16
+ # @return [void]
17
+ def exec_cmd(subscription_runner)
18
+ subscription_runner.within_state(:stopped) do
19
+ subscription_runner.clear_chunk
20
+ subscription_runner.subscription.update(last_chunk_greatest_position: nil, current_position: data['position'])
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionRunnerCommands
5
+ # @!visibility private
6
+ class Restore < Base
7
+ # @param subscription_runner [PgEventstore::SubscriptionRunner]
8
+ # @return [void]
9
+ def exec_cmd(subscription_runner)
10
+ subscription_runner.restore
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionRunnerCommands
5
+ # @!visibility private
6
+ class Start < Base
7
+ # @param subscription_runner [PgEventstore::SubscriptionRunner]
8
+ # @return [void]
9
+ def exec_cmd(subscription_runner)
10
+ subscription_runner.start
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PgEventstore
4
+ module SubscriptionRunnerCommands
5
+ # @!visibility private
6
+ class Stop < Base
7
+ # @param subscription_runner [PgEventstore::SubscriptionRunner]
8
+ # @return [void]
9
+ def exec_cmd(subscription_runner)
10
+ subscription_runner.stop_async
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'subscription_runner_commands/base'
4
+ require_relative 'subscription_runner_commands/reset_position'
5
+ require_relative 'subscription_runner_commands/restore'
6
+ require_relative 'subscription_runner_commands/start'
7
+ require_relative 'subscription_runner_commands/stop'
8
+
9
+ module PgEventstore
10
+ # @!visibility private
11
+ module SubscriptionRunnerCommands
12
+ extend Extensions::CommandClassLookupExtension
13
+ end
14
+ end
@@ -1,15 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'forwardable'
4
+ require_relative 'runner_state'
4
5
  require_relative 'basic_runner'
5
6
  require_relative 'subscription'
6
7
  require_relative 'events_processor'
7
8
  require_relative 'subscription_handler_performance'
8
9
  require_relative 'subscription_runner'
9
- require_relative 'runner_state'
10
10
  require_relative 'subscriptions_set'
11
11
  require_relative 'subscription_runners_feeder'
12
12
  require_relative 'subscription_feeder'
13
+ require_relative 'extensions/command_class_lookup_extension'
14
+ require_relative 'extensions/base_command_extension'
15
+ require_relative 'subscription_feeder_commands'
16
+ require_relative 'subscription_runner_commands'
17
+ require_relative 'queries/subscription_command_queries'
18
+ require_relative 'queries/subscription_queries'
19
+ require_relative 'queries/subscriptions_set_command_queries'
20
+ require_relative 'queries/subscriptions_set_queries'
13
21
  require_relative 'commands_handler'
14
22
 
15
23
  module PgEventstore
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PgEventstore
4
- VERSION = "1.0.4"
4
+ VERSION = "1.1.0"
5
5
  end
@@ -145,19 +145,25 @@ module PgEventstore
145
145
  end
146
146
 
147
147
  post '/subscription_cmd/:set_id/:id/:cmd' do
148
- puts SubscriptionCommandQueries.new(connection).find_or_create_by(
148
+ validate_subscription_cmd(params[:cmd])
149
+ cmd_class = SubscriptionRunnerCommands.command_class(params[:cmd])
150
+ SubscriptionCommandQueries.new(connection).find_or_create_by(
149
151
  subscriptions_set_id: params[:set_id],
150
152
  subscription_id: params[:id],
151
- command_name: CommandHandlers::SubscriptionRunnersCommands::AVAILABLE_COMMANDS.fetch(params[:cmd].to_sym)
153
+ command_name: cmd_class.new.name,
154
+ data: cmd_class.parse_data(Hash(params[:data]))
152
155
  )
153
156
 
154
157
  redirect redirect_back_url(fallback_url: url('/subscriptions'))
155
158
  end
156
159
 
157
160
  post '/subscriptions_set_cmd/:id/:cmd' do
161
+ validate_subscriptions_set_cmd(params[:cmd])
162
+ cmd_class = SubscriptionFeederCommands.command_class(params[:cmd])
158
163
  SubscriptionsSetCommandQueries.new(connection).find_or_create_by(
159
164
  subscriptions_set_id: params[:id],
160
- command_name: CommandHandlers::SubscriptionFeederCommands::AVAILABLE_COMMANDS.fetch(params[:cmd].to_sym)
165
+ command_name: cmd_class.new.name,
166
+ data: cmd_class.parse_data(Hash(params[:data]))
161
167
  )
162
168
 
163
169
  redirect redirect_back_url(fallback_url: url('/subscriptions'))
@@ -197,3 +197,21 @@ $(function(){
197
197
  handleMethod(e.target);
198
198
  });
199
199
  });
200
+
201
+ // Reset position action handling
202
+ $(function(){
203
+ "use strict";
204
+
205
+ let $resetPositionModal = $('#reset-position-modal');
206
+
207
+ $resetPositionModal.on('hide.bs.modal', function(){
208
+ $(this).find('.subscription-name').html('');
209
+ $(this).find('form').removeAttr('action');
210
+ });
211
+
212
+ $resetPositionModal.on('show.bs.modal', function(e){
213
+ let $clickedLink = $(e.relatedTarget);
214
+ $(this).find('.subscription-name').html($clickedLink.data('subscription-name'));
215
+ $(this).find('form').attr('action', $clickedLink.data('url'));
216
+ });
217
+ });
@@ -28,14 +28,36 @@ module PgEventstore
28
28
  url("/subscriptions_set_cmd/#{id}/#{cmd}")
29
29
  end
30
30
 
31
- # @return [Hash{Symbol => String}]
32
- def subscriptions_set_cmds
33
- CommandHandlers::SubscriptionFeederCommands::AVAILABLE_COMMANDS
31
+ # @param cmd_name [String] command name
32
+ # @return [String] command name
33
+ def subscriptions_set_cmd(cmd_name)
34
+ validate_subscriptions_set_cmd(cmd_name)
35
+
36
+ cmd_name
37
+ end
38
+
39
+ # @param cmd_name [String]
40
+ # @return [void]
41
+ # @raise [RuntimeError] in case if command class is not found
42
+ def validate_subscriptions_set_cmd(cmd_name)
43
+ cmd_class = SubscriptionFeederCommands.command_class(cmd_name)
44
+ raise "SubscriptionsSet command #{cmd_name.inspect} does not exist" unless cmd_class.known_command?
45
+ end
46
+
47
+ # @param cmd_name [String] command name
48
+ # @return [String] command name
49
+ def subscription_cmd(cmd_name)
50
+ validate_subscription_cmd(cmd_name)
51
+
52
+ cmd_name
34
53
  end
35
54
 
36
- # @return [Hash{Symbol => String}]
37
- def subscriptions_cmds
38
- CommandHandlers::SubscriptionRunnersCommands::AVAILABLE_COMMANDS
55
+ # @param cmd_name [String]
56
+ # @return [void]
57
+ # @raise [RuntimeError] in case if command class is not found
58
+ def validate_subscription_cmd(cmd_name)
59
+ cmd_class = SubscriptionRunnerCommands.command_class(cmd_name)
60
+ raise "Subscription command #{cmd_name.inspect} does not exist" unless cmd_class.known_command?
39
61
  end
40
62
 
41
63
  # @param state [String]
@@ -69,12 +69,12 @@
69
69
  <td><%= subscriptions_set.last_error_occurred_at %></td>
70
70
  <td>
71
71
  <% if subscriptions_set.state == PgEventstore::RunnerState::STATES[:dead] %>
72
- <a class="btn btn-success" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmds[:Restore]) %>">
72
+ <a class="btn btn-success" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmd('Restore')) %>">
73
73
  Restore
74
74
  </a>
75
75
  <% end %>
76
76
  <% if PgEventstore::RunnerState::STATES.values_at(:running, :dead).include?(subscriptions_set.state) %>
77
- <a class="btn btn-warning" data-confirm="You are about to stop SubscriptionsSet#<%= subscriptions_set.id %>. This will also delete it and will result in stopping all related Subscriptions. Continue?" data-confirm-title="Stop SubscriptionsSet" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmds[:Stop]) %>" data-toggle="tooltip" title="This action will delete Subscriptions Set and release all related Subscriptions.">
77
+ <a class="btn btn-warning" data-confirm="You are about to stop SubscriptionsSet#<%= subscriptions_set.id %>. This will also delete it and will result in stopping all related Subscriptions. Continue?" data-confirm-title="Stop SubscriptionsSet" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmd('Stop')) %>" data-toggle="tooltip" title="This action will delete Subscriptions Set and release all related Subscriptions.">
78
78
  Stop
79
79
  </a>
80
80
  <% end %>
@@ -101,10 +101,10 @@
101
101
  </div>
102
102
  <div class="float-right">
103
103
  <% if subscriptions_set.id %>
104
- <a class="btn btn-success" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmds[:StartAll]) %>">
104
+ <a class="btn btn-success" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmd('StartAll')) %>">
105
105
  Start All
106
106
  </a>
107
- <a class="btn btn-danger" data-confirm="You are about to stop all Subscriptions. Continue?" data-confirm-title="Stop all Subscriptions" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmds[:StopAll]) %>">
107
+ <a class="btn btn-danger" data-confirm="You are about to stop all Subscriptions. Continue?" data-confirm-title="Stop all Subscriptions" data-method="post" href="<%= subscriptions_set_cmd_url(subscriptions_set.id, subscriptions_set_cmd('StopAll')) %>">
108
108
  Stop All
109
109
  </a>
110
110
  <% else %>
@@ -170,17 +170,20 @@
170
170
  <td>
171
171
  <% if subscriptions_set.id %>
172
172
  <% if subscription.state == PgEventstore::RunnerState::STATES[:stopped] %>
173
- <a class="btn btn-success" data-method="post" href="<%= subscription_cmd_url(subscriptions_set.id, subscription.id, subscriptions_cmds[:Start]) %>">
173
+ <a class="btn btn-success" data-method="post" href="<%= subscription_cmd_url(subscriptions_set.id, subscription.id, subscription_cmd('Start')) %>">
174
174
  Start
175
175
  </a>
176
+ <a class="btn btn-warning" href="#" data-toggle="modal" data-target="#reset-position-modal" data-subscription-name="<%= h subscription.name %>" data-url="<%= subscription_cmd_url(subscriptions_set.id, subscription.id, subscription_cmd('ResetPosition')) %>">
177
+ Reset position
178
+ </a>
176
179
  <% end %>
177
180
  <% if PgEventstore::RunnerState::STATES.values_at(:running, :dead).include?(subscription.state) %>
178
- <a class="btn btn-warning" data-method="post" href="<%= subscription_cmd_url(subscriptions_set.id, subscription.id, subscriptions_cmds[:Stop]) %>">
181
+ <a class="btn btn-warning" data-method="post" href="<%= subscription_cmd_url(subscriptions_set.id, subscription.id, subscription_cmd('Stop')) %>">
179
182
  Stop
180
183
  </a>
181
184
  <% end %>
182
185
  <% if subscription.state == PgEventstore::RunnerState::STATES[:dead] %>
183
- <a class="btn btn-success" data-method="post" href="<%= subscription_cmd_url(subscriptions_set.id, subscription.id, subscriptions_cmds[:Restore]) %>">
186
+ <a class="btn btn-success" data-method="post" href="<%= subscription_cmd_url(subscriptions_set.id, subscription.id, subscription_cmd('Restore')) %>">
184
187
  Restore
185
188
  </a>
186
189
  <% end %>
@@ -218,3 +221,32 @@
218
221
  </div>
219
222
  </div>
220
223
  </div>
224
+
225
+ <div class="modal fade" id="reset-position-modal" tabindex="-1" role="dialog" aria-labelledby="reset-position-modal" aria-hidden="true">
226
+ <div class="modal-dialog modal-dialog-centered" role="document">
227
+ <div class="modal-content">
228
+ <div class="modal-header">
229
+ <h5 class="modal-title"> Reset Subscription's position</h5>
230
+ <button type="button" class="close" data-dismiss="modal" aria-label="Close">
231
+ <span aria-hidden="true">&times;</span>
232
+ </button>
233
+ </div>
234
+ <div class="modal-body font-weight-bold text-break">
235
+ <span>Reset position of</span><pre class="subscription-name"></pre><span>Subscription?</span>
236
+ <form id="reset-position-form" data-parsley-validate="" class="form-horizontal form-label-left" novalidate="" method="POST">
237
+ <div class="item form-group">
238
+ <label class="col-form-label col-md-3 col-sm-3 label-align" for="subscription-position">Position <span class="required">*</span>
239
+ </label>
240
+ <div class="col-md-6 col-sm-6 ">
241
+ <input type="number" id="subscription-position" name="data[position]" required="required" autocomplete="off" value="0" class="form-control">
242
+ </div>
243
+ </div>
244
+ </form>
245
+ </div>
246
+ <div class="modal-footer">
247
+ <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
248
+ <button type="submit" class="btn btn-primary" form="reset-position-form">Reset</button>
249
+ </div>
250
+ </div>
251
+ </div>
252
+ </div>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_eventstore
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.4
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ivan Dzyzenko
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-06-10 00:00:00.000000000 Z
11
+ date: 2024-07-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pg
@@ -69,6 +69,7 @@ files:
69
69
  - db/migrations/3_create_subscription_commands.sql
70
70
  - db/migrations/4_create_subscriptions_set_commands.sql
71
71
  - db/migrations/5_partitions.sql
72
+ - db/migrations/6_add_commands_data.sql
72
73
  - docs/appending_events.md
73
74
  - docs/configuration.md
74
75
  - docs/events_and_streams.md
@@ -107,10 +108,6 @@ files:
107
108
  - lib/pg_eventstore/queries/event_queries.rb
108
109
  - lib/pg_eventstore/queries/links_resolver.rb
109
110
  - lib/pg_eventstore/queries/partition_queries.rb
110
- - lib/pg_eventstore/queries/subscription_command_queries.rb
111
- - lib/pg_eventstore/queries/subscription_queries.rb
112
- - lib/pg_eventstore/queries/subscriptions_set_command_queries.rb
113
- - lib/pg_eventstore/queries/subscriptions_set_queries.rb
114
111
  - lib/pg_eventstore/queries/transaction_queries.rb
115
112
  - lib/pg_eventstore/query_builders/events_filtering_query.rb
116
113
  - lib/pg_eventstore/rspec/has_option_matcher.rb
@@ -122,11 +119,29 @@ files:
122
119
  - lib/pg_eventstore/subscriptions/command_handlers/subscription_runners_commands.rb
123
120
  - lib/pg_eventstore/subscriptions/commands_handler.rb
124
121
  - lib/pg_eventstore/subscriptions/events_processor.rb
122
+ - lib/pg_eventstore/subscriptions/extensions/base_command_extension.rb
123
+ - lib/pg_eventstore/subscriptions/extensions/command_class_lookup_extension.rb
124
+ - lib/pg_eventstore/subscriptions/queries/subscription_command_queries.rb
125
+ - lib/pg_eventstore/subscriptions/queries/subscription_queries.rb
126
+ - lib/pg_eventstore/subscriptions/queries/subscriptions_set_command_queries.rb
127
+ - lib/pg_eventstore/subscriptions/queries/subscriptions_set_queries.rb
125
128
  - lib/pg_eventstore/subscriptions/runner_state.rb
126
129
  - lib/pg_eventstore/subscriptions/subscription.rb
127
130
  - lib/pg_eventstore/subscriptions/subscription_feeder.rb
131
+ - lib/pg_eventstore/subscriptions/subscription_feeder_commands.rb
132
+ - lib/pg_eventstore/subscriptions/subscription_feeder_commands/base.rb
133
+ - lib/pg_eventstore/subscriptions/subscription_feeder_commands/restore.rb
134
+ - lib/pg_eventstore/subscriptions/subscription_feeder_commands/start_all.rb
135
+ - lib/pg_eventstore/subscriptions/subscription_feeder_commands/stop.rb
136
+ - lib/pg_eventstore/subscriptions/subscription_feeder_commands/stop_all.rb
128
137
  - lib/pg_eventstore/subscriptions/subscription_handler_performance.rb
129
138
  - lib/pg_eventstore/subscriptions/subscription_runner.rb
139
+ - lib/pg_eventstore/subscriptions/subscription_runner_commands.rb
140
+ - lib/pg_eventstore/subscriptions/subscription_runner_commands/base.rb
141
+ - lib/pg_eventstore/subscriptions/subscription_runner_commands/reset_position.rb
142
+ - lib/pg_eventstore/subscriptions/subscription_runner_commands/restore.rb
143
+ - lib/pg_eventstore/subscriptions/subscription_runner_commands/start.rb
144
+ - lib/pg_eventstore/subscriptions/subscription_runner_commands/stop.rb
130
145
  - lib/pg_eventstore/subscriptions/subscription_runners_feeder.rb
131
146
  - lib/pg_eventstore/subscriptions/subscriptions_manager.rb
132
147
  - lib/pg_eventstore/subscriptions/subscriptions_set.rb