logux_rails 0.1.0
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 +7 -0
- data/.gitignore +20 -0
- data/.pryrc +8 -0
- data/.rspec +3 -0
- data/.rubocop.yml +33 -0
- data/.rubocop_todo.yml +17 -0
- data/.travis.yml +11 -0
- data/Appraisals +13 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +68 -0
- data/Rakefile +10 -0
- data/app/controllers/logux_controller.rb +41 -0
- data/app/helpers/logux_helper.rb +4 -0
- data/app/logux/actions.rb +3 -0
- data/app/logux/policies.rb +3 -0
- data/bin/console +15 -0
- data/bin/setup +8 -0
- data/config/routes.rb +5 -0
- data/docker-compose.yml +29 -0
- data/lib/generators/logux/model/USAGE +11 -0
- data/lib/generators/logux/model/model_generator.rb +28 -0
- data/lib/generators/logux/model/templates/migration.rb.erb +14 -0
- data/lib/logux.rb +107 -0
- data/lib/logux/action_caller.rb +42 -0
- data/lib/logux/action_controller.rb +6 -0
- data/lib/logux/actions.rb +29 -0
- data/lib/logux/add.rb +37 -0
- data/lib/logux/auth.rb +6 -0
- data/lib/logux/base_controller.rb +37 -0
- data/lib/logux/channel_controller.rb +24 -0
- data/lib/logux/class_finder.rb +61 -0
- data/lib/logux/client.rb +21 -0
- data/lib/logux/engine.rb +6 -0
- data/lib/logux/error_renderer.rb +40 -0
- data/lib/logux/meta.rb +36 -0
- data/lib/logux/model.rb +39 -0
- data/lib/logux/model/dsl.rb +15 -0
- data/lib/logux/model/proxy.rb +24 -0
- data/lib/logux/model/updater.rb +39 -0
- data/lib/logux/model/updates_deprecator.rb +54 -0
- data/lib/logux/node.rb +37 -0
- data/lib/logux/policy.rb +14 -0
- data/lib/logux/policy_caller.rb +34 -0
- data/lib/logux/process.rb +9 -0
- data/lib/logux/process/action.rb +60 -0
- data/lib/logux/process/auth.rb +27 -0
- data/lib/logux/process/batch.rb +59 -0
- data/lib/logux/response.rb +18 -0
- data/lib/logux/stream.rb +25 -0
- data/lib/logux/test.rb +35 -0
- data/lib/logux/test/helpers.rb +75 -0
- data/lib/logux/test/matchers.rb +10 -0
- data/lib/logux/test/matchers/base.rb +25 -0
- data/lib/logux/test/matchers/response_chunks.rb +48 -0
- data/lib/logux/test/matchers/send_to_logux.rb +51 -0
- data/lib/logux/test/store.rb +21 -0
- data/lib/logux/version.rb +5 -0
- data/lib/logux_rails.rb +3 -0
- data/lib/tasks/logux_tasks.rake +46 -0
- data/logux_rails.gemspec +46 -0
- metadata +398 -0
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
class ActionCaller
|
5
|
+
attr_reader :action, :meta
|
6
|
+
|
7
|
+
delegate :logger, to: :Logux
|
8
|
+
|
9
|
+
def initialize(action:, meta:)
|
10
|
+
@action = action
|
11
|
+
@meta = meta
|
12
|
+
end
|
13
|
+
|
14
|
+
def call!
|
15
|
+
Logux::Model::UpdatesDeprecator.watch(level: :error) do
|
16
|
+
logger.debug(
|
17
|
+
"Searching action for Logux action: #{action}, meta: #{meta}"
|
18
|
+
)
|
19
|
+
format(action_controller.public_send(action.action_type))
|
20
|
+
end
|
21
|
+
rescue Logux::UnknownActionError, Logux::UnknownChannelError => e
|
22
|
+
logger.warn(e)
|
23
|
+
format(nil)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def format(response)
|
29
|
+
return response if response.is_a?(Logux::Response)
|
30
|
+
|
31
|
+
Logux::Response.new(:processed, action: action, meta: meta)
|
32
|
+
end
|
33
|
+
|
34
|
+
def class_finder
|
35
|
+
@class_finder ||= Logux::ClassFinder.new(action: action, meta: meta)
|
36
|
+
end
|
37
|
+
|
38
|
+
def action_controller
|
39
|
+
class_finder.find_action_class.new(action: action, meta: meta)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
class Actions < ::ActionController::Parameters
|
5
|
+
def action_name
|
6
|
+
type&.split('/')&.dig(0)
|
7
|
+
end
|
8
|
+
|
9
|
+
def action_type
|
10
|
+
type&.split('/')&.last
|
11
|
+
end
|
12
|
+
|
13
|
+
def channel_name
|
14
|
+
channel&.split('/')&.dig(0)
|
15
|
+
end
|
16
|
+
|
17
|
+
def channel_id
|
18
|
+
channel&.split('/')&.last
|
19
|
+
end
|
20
|
+
|
21
|
+
def type
|
22
|
+
require(:type)
|
23
|
+
end
|
24
|
+
|
25
|
+
def channel
|
26
|
+
require(:channel)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/logux/add.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
class Add
|
5
|
+
attr_reader :client, :version, :password
|
6
|
+
|
7
|
+
def initialize(client: Logux::Client.new,
|
8
|
+
version: Logux::PROTOCOL_VERSION,
|
9
|
+
password: Logux.configuration.password)
|
10
|
+
@client = client
|
11
|
+
@version = version
|
12
|
+
@password = password
|
13
|
+
end
|
14
|
+
|
15
|
+
def call(commands)
|
16
|
+
return if commands.empty?
|
17
|
+
|
18
|
+
prepared_data = prepare_data(commands)
|
19
|
+
Logux.logger.debug("Logux add: #{prepared_data}")
|
20
|
+
client.post(prepared_data)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def prepare_data(commands)
|
26
|
+
{
|
27
|
+
version: PROTOCOL_VERSION,
|
28
|
+
password: password,
|
29
|
+
commands: commands.map do |command|
|
30
|
+
action = command.first
|
31
|
+
meta = command[1]
|
32
|
+
['action', action, meta || Meta.new]
|
33
|
+
end
|
34
|
+
}
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/logux/auth.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
class BaseController
|
5
|
+
class << self
|
6
|
+
def verify_authorized!
|
7
|
+
Logux.configuration.verify_authorized = true
|
8
|
+
end
|
9
|
+
|
10
|
+
def unverify_authorized!
|
11
|
+
Logux.configuration.verify_authorized = false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :action, :meta
|
16
|
+
|
17
|
+
def initialize(action:, meta: {})
|
18
|
+
@action = action
|
19
|
+
@meta = meta
|
20
|
+
end
|
21
|
+
|
22
|
+
def respond(status, action: @action, meta: @meta, custom_data: nil)
|
23
|
+
Logux::Response.new(status,
|
24
|
+
action: action,
|
25
|
+
meta: meta,
|
26
|
+
custom_data: custom_data)
|
27
|
+
end
|
28
|
+
|
29
|
+
def user_id
|
30
|
+
@user_id ||= meta.user_id
|
31
|
+
end
|
32
|
+
|
33
|
+
def node_id
|
34
|
+
@node_id ||= meta.node_id
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
class ChannelController < BaseController
|
5
|
+
def subscribe
|
6
|
+
Logux.add_batch(initial_data.map { |d| [d, initial_meta] })
|
7
|
+
end
|
8
|
+
|
9
|
+
def initial_data
|
10
|
+
[]
|
11
|
+
end
|
12
|
+
|
13
|
+
def initial_meta
|
14
|
+
{ clients: [meta.client_id] }
|
15
|
+
end
|
16
|
+
|
17
|
+
def since_time
|
18
|
+
@since_time ||= begin
|
19
|
+
since = action['since'].try(:[], 'time')
|
20
|
+
Time.at(since).to_datetime if since
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
class ClassFinder
|
5
|
+
attr_reader :action, :meta
|
6
|
+
|
7
|
+
def initialize(action:, meta:)
|
8
|
+
@action = action
|
9
|
+
@meta = meta
|
10
|
+
end
|
11
|
+
|
12
|
+
def find_action_class
|
13
|
+
"#{class_namespace}::#{class_name}".constantize
|
14
|
+
rescue NameError
|
15
|
+
message =
|
16
|
+
"Unable to find action #{class_name.camelize}.\n" \
|
17
|
+
"Should be in app/logux/#{class_namespace.downcase}/#{class_path}.rb"
|
18
|
+
raise_error_for_failed_find(message)
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_policy_class
|
22
|
+
"Policies::#{class_namespace}::#{class_name}".constantize
|
23
|
+
rescue NameError
|
24
|
+
message =
|
25
|
+
"Unable to find action policy #{class_name.camelize}.\n" \
|
26
|
+
"Should be in app/logux/#{class_namespace.downcase}/#{class_path}.rb"
|
27
|
+
raise_error_for_failed_find(message)
|
28
|
+
end
|
29
|
+
|
30
|
+
def class_name
|
31
|
+
if subscribe?
|
32
|
+
action.channel_name.camelize
|
33
|
+
else
|
34
|
+
action.type.split('/')[0..-2].map(&:camelize).join('::')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def class_namespace
|
41
|
+
subscribe? ? 'Channels' : 'Actions'
|
42
|
+
end
|
43
|
+
|
44
|
+
def subscribe?
|
45
|
+
action.type == 'logux/subscribe'
|
46
|
+
end
|
47
|
+
|
48
|
+
def action?
|
49
|
+
!subscribe?
|
50
|
+
end
|
51
|
+
|
52
|
+
def class_path
|
53
|
+
"#{class_namespace}::#{class_name}".underscore
|
54
|
+
end
|
55
|
+
|
56
|
+
def raise_error_for_failed_find(message)
|
57
|
+
exception_class = action? ? UnknownActionError : UnknownChannelError
|
58
|
+
raise exception_class.new(message, meta: meta)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
data/lib/logux/client.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
class Client
|
5
|
+
attr_reader :logux_host
|
6
|
+
|
7
|
+
def initialize(logux_host: Logux.configuration.logux_host)
|
8
|
+
@logux_host = logux_host
|
9
|
+
end
|
10
|
+
|
11
|
+
def post(params)
|
12
|
+
client.post(params.to_json,
|
13
|
+
content_type: :json,
|
14
|
+
accept: :json)
|
15
|
+
end
|
16
|
+
|
17
|
+
def client
|
18
|
+
@client ||= RestClient::Resource.new(logux_host, verify_ssl: false)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
data/lib/logux/engine.rb
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
class ErrorRenderer
|
5
|
+
attr_reader :exception
|
6
|
+
|
7
|
+
def initialize(exception)
|
8
|
+
@exception = exception
|
9
|
+
end
|
10
|
+
|
11
|
+
def message
|
12
|
+
case exception
|
13
|
+
when Logux::WithMetaError
|
14
|
+
build_message(exception, exception.meta.id)
|
15
|
+
when Logux::UnauthorizedError
|
16
|
+
build_message(exception, exception.message)
|
17
|
+
when StandardError
|
18
|
+
# some runtime error that should be fixed
|
19
|
+
render_stardard_error(exception)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def render_stardard_error(exception)
|
26
|
+
if Logux.configuration.render_backtrace_on_error
|
27
|
+
['error', exception.message + "\n" + exception.backtrace.join("\n")]
|
28
|
+
else
|
29
|
+
['error', 'Please look server logs for more information']
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_message(exception, additional_info)
|
34
|
+
[
|
35
|
+
exception.class.name.demodulize.camelize(:lower).gsub(/Error/, ''),
|
36
|
+
additional_info
|
37
|
+
]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/logux/meta.rb
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
class Meta < Hash
|
5
|
+
def initialize(source_hash = {})
|
6
|
+
merge!(source_hash.stringify_keys)
|
7
|
+
|
8
|
+
self['id'] ||= Logux.generate_action_id
|
9
|
+
self['time'] ||= self['id'].split(' ').first
|
10
|
+
end
|
11
|
+
|
12
|
+
def node_id
|
13
|
+
id.split(' ').second
|
14
|
+
end
|
15
|
+
|
16
|
+
def user_id
|
17
|
+
node_id.split(':').first
|
18
|
+
end
|
19
|
+
|
20
|
+
def client_id
|
21
|
+
node_id.split(':')[0..1].join(':')
|
22
|
+
end
|
23
|
+
|
24
|
+
def logux_order
|
25
|
+
time + ' ' + id.split(' ')[1..-1].join(' ')
|
26
|
+
end
|
27
|
+
|
28
|
+
def time
|
29
|
+
fetch('time')
|
30
|
+
end
|
31
|
+
|
32
|
+
def id
|
33
|
+
fetch('id')
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/logux/model.rb
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'model/updater'
|
4
|
+
require_relative 'model/proxy'
|
5
|
+
require_relative 'model/dsl'
|
6
|
+
require_relative 'model/updates_deprecator'
|
7
|
+
|
8
|
+
module Logux
|
9
|
+
module Model
|
10
|
+
class InsecureUpdateError < StandardError; end
|
11
|
+
|
12
|
+
def self.included(base)
|
13
|
+
base.extend(DSL)
|
14
|
+
|
15
|
+
base.before_update :touch_logux_order_for_changes,
|
16
|
+
unless: -> { changes.key?('logux_fields_updated_at') }
|
17
|
+
end
|
18
|
+
|
19
|
+
def logux
|
20
|
+
Proxy.new(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def touch_logux_order_for_changes
|
26
|
+
attributes = changed.each_with_object({}) do |attr, res|
|
27
|
+
res[attr] = send(attr)
|
28
|
+
end
|
29
|
+
|
30
|
+
updater = Updater.new(model: self, attributes: attributes)
|
31
|
+
self.logux_fields_updated_at = updater.updated_attributes
|
32
|
+
|
33
|
+
ActiveSupport::Notifications.instrument(
|
34
|
+
Logux::Model::UpdatesDeprecator::EVENT,
|
35
|
+
model: self
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
module Model
|
5
|
+
module DSL
|
6
|
+
def logux_crdt_map_attributes(*attributes)
|
7
|
+
@logux_crdt_mapped_attributes = attributes
|
8
|
+
end
|
9
|
+
|
10
|
+
def logux_crdt_mapped_attributes
|
11
|
+
@logux_crdt_mapped_attributes ||= []
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
module Model
|
5
|
+
class Proxy
|
6
|
+
def initialize(model)
|
7
|
+
@model = model
|
8
|
+
end
|
9
|
+
|
10
|
+
def update(meta, attributes)
|
11
|
+
updater = Updater.new(
|
12
|
+
model: @model,
|
13
|
+
logux_order: meta.logux_order,
|
14
|
+
attributes: attributes
|
15
|
+
)
|
16
|
+
@model.update_attributes(updater.updated_attributes)
|
17
|
+
end
|
18
|
+
|
19
|
+
def updated_at(field)
|
20
|
+
@model.logux_fields_updated_at[field.to_s]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Logux
|
4
|
+
module Model
|
5
|
+
class Updater
|
6
|
+
def initialize(model:, attributes:, logux_order: Logux.generate_action_id)
|
7
|
+
@model = model
|
8
|
+
@logux_order = logux_order
|
9
|
+
@attributes = attributes
|
10
|
+
end
|
11
|
+
|
12
|
+
def updated_attributes
|
13
|
+
newer_updates.merge(logux_fields_updated_at: fields_updated_at)
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def fields_updated_at
|
19
|
+
@fields_updated_at ||=
|
20
|
+
newer_updates.slice(*tracked_fields)
|
21
|
+
.keys
|
22
|
+
.reduce(@model.logux_fields_updated_at) do |acc, attr|
|
23
|
+
acc.merge(attr => @logux_order)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def newer_updates
|
28
|
+
@newer_updates ||= @attributes.reject do |attr, _|
|
29
|
+
field_updated_at = @model.logux.updated_at(attr)
|
30
|
+
field_updated_at && field_updated_at > @logux_order
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def tracked_fields
|
35
|
+
@model.class.logux_crdt_mapped_attributes
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|