userlist 0.2.2 → 0.6.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 +5 -5
- data/.github/workflows/test.yml +21 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +5 -4
- data/Gemfile +3 -0
- data/README.md +185 -6
- data/lib/userlist.rb +28 -1
- data/lib/userlist/config.rb +11 -4
- data/lib/userlist/push.rb +38 -35
- data/lib/userlist/push/client.rb +20 -5
- data/lib/userlist/push/company.rb +23 -0
- data/lib/userlist/push/event.rb +26 -0
- data/lib/userlist/push/operations/create.rb +25 -0
- data/lib/userlist/push/operations/delete.rb +23 -0
- data/lib/userlist/push/relation.rb +30 -0
- data/lib/userlist/push/relationship.rb +30 -0
- data/lib/userlist/push/resource.rb +125 -0
- data/lib/userlist/push/resource_collection.rb +32 -0
- data/lib/userlist/push/serializer.rb +66 -0
- data/lib/userlist/push/strategies.rb +20 -6
- data/lib/userlist/push/strategies/sidekiq.rb +37 -0
- data/lib/userlist/push/strategies/sidekiq/worker.rb +18 -0
- data/lib/userlist/push/strategies/threaded.rb +1 -1
- data/lib/userlist/push/strategies/threaded/worker.rb +4 -4
- data/lib/userlist/push/user.rb +19 -0
- data/lib/userlist/token.rb +68 -0
- data/lib/userlist/version.rb +1 -1
- data/userlist.gemspec +6 -5
- metadata +48 -16
- data/.travis.yml +0 -5
data/lib/userlist/push/client.rb
CHANGED
@@ -10,10 +10,25 @@ module Userlist
|
|
10
10
|
|
11
11
|
def initialize(config = {})
|
12
12
|
@config = Userlist.config.merge(config)
|
13
|
+
|
14
|
+
raise Userlist::ConfigurationError, :push_key unless @config.push_key
|
15
|
+
raise Userlist::ConfigurationError, :push_endpoint unless @config.push_endpoint
|
16
|
+
end
|
17
|
+
|
18
|
+
def get(endpoint)
|
19
|
+
request(Net::HTTP::Get, endpoint)
|
20
|
+
end
|
21
|
+
|
22
|
+
def post(endpoint, payload = nil)
|
23
|
+
request(Net::HTTP::Post, endpoint, payload)
|
24
|
+
end
|
25
|
+
|
26
|
+
def put(endpoint, payload = nil)
|
27
|
+
request(Net::HTTP::Put, endpoint, payload)
|
13
28
|
end
|
14
29
|
|
15
|
-
def
|
16
|
-
request(
|
30
|
+
def delete(endpoint)
|
31
|
+
request(Net::HTTP::Delete, endpoint)
|
17
32
|
end
|
18
33
|
|
19
34
|
private
|
@@ -33,12 +48,12 @@ module Userlist
|
|
33
48
|
end
|
34
49
|
end
|
35
50
|
|
36
|
-
def request(path, payload =
|
37
|
-
request =
|
51
|
+
def request(method, path, payload = nil)
|
52
|
+
request = method.new(path)
|
38
53
|
request['Accept'] = 'application/json'
|
39
54
|
request['Authorization'] = "Push #{token}"
|
40
55
|
request['Content-Type'] = 'application/json; charset=UTF-8'
|
41
|
-
request.body = JSON.
|
56
|
+
request.body = JSON.generate(payload) if payload
|
42
57
|
|
43
58
|
logger.debug "Sending #{request.method} to #{URI.join(endpoint, request.path)} with body #{request.body}"
|
44
59
|
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Userlist
|
2
|
+
class Push
|
3
|
+
class Company < Resource
|
4
|
+
include Operations::Create
|
5
|
+
include Operations::Delete
|
6
|
+
|
7
|
+
def self.endpoint
|
8
|
+
'/companies'
|
9
|
+
end
|
10
|
+
|
11
|
+
has_many :relationships, type: 'Userlist::Push::Relationship', inverse: :company
|
12
|
+
has_many :users, type: 'Userlist::Push::User'
|
13
|
+
has_one :user, type: 'Userlist::Push::User'
|
14
|
+
|
15
|
+
def initialize(payload = {}, config = Userlist.config)
|
16
|
+
raise Userlist::ArgumentError, 'Missing required payload hash' unless payload
|
17
|
+
raise Userlist::ArgumentError, 'Missing required parameter :identifier' unless payload[:identifier]
|
18
|
+
|
19
|
+
super
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Userlist
|
2
|
+
class Push
|
3
|
+
class Event < Resource
|
4
|
+
include Operations::Create
|
5
|
+
|
6
|
+
has_one :user, type: 'Userlist::Push::User'
|
7
|
+
has_one :company, type: 'Userlist::Push::Company'
|
8
|
+
|
9
|
+
def initialize(payload = {}, config = Userlist.config)
|
10
|
+
raise Userlist::ArgumentError, 'Missing required payload' unless payload
|
11
|
+
raise Userlist::ArgumentError, 'Missing required parameter :name' unless payload[:name]
|
12
|
+
raise Userlist::ArgumentError, 'Missing required parameter :user or :company' unless payload[:user] || payload[:company]
|
13
|
+
|
14
|
+
super
|
15
|
+
end
|
16
|
+
|
17
|
+
def occured_at
|
18
|
+
payload[:occured_at] || Time.now
|
19
|
+
end
|
20
|
+
|
21
|
+
def push?
|
22
|
+
(user.nil? || user.push?) && (company.nil? || company.push?)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Userlist
|
2
|
+
class Push
|
3
|
+
module Operations
|
4
|
+
module Create
|
5
|
+
module ClassMethods
|
6
|
+
def create(payload = {}, config = self.config)
|
7
|
+
return false unless resource = from_payload(payload, config)
|
8
|
+
|
9
|
+
strategy.call(:post, endpoint, resource) if resource.create?
|
10
|
+
end
|
11
|
+
|
12
|
+
alias push create
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.included(base)
|
16
|
+
base.extend(ClassMethods)
|
17
|
+
end
|
18
|
+
|
19
|
+
def create?
|
20
|
+
push?
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Userlist
|
2
|
+
class Push
|
3
|
+
module Operations
|
4
|
+
module Delete
|
5
|
+
module ClassMethods
|
6
|
+
def delete(payload = {}, config = self.config)
|
7
|
+
return false unless resource = from_payload(payload, config)
|
8
|
+
|
9
|
+
strategy.call(:delete, resource.url) if resource.delete?
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
base.extend(ClassMethods)
|
15
|
+
end
|
16
|
+
|
17
|
+
def delete?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Userlist
|
2
|
+
class Push
|
3
|
+
class Relation
|
4
|
+
def initialize(scope, type, operations = [])
|
5
|
+
@scope = scope
|
6
|
+
@type = type
|
7
|
+
|
8
|
+
operations.each { |operation| singleton_class.send(:include, operation::ClassMethods) }
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :scope, :type
|
12
|
+
|
13
|
+
def from_payload(payload, config = self.config)
|
14
|
+
type.from_payload(payload, config)
|
15
|
+
end
|
16
|
+
|
17
|
+
def endpoint
|
18
|
+
type.endpoint
|
19
|
+
end
|
20
|
+
|
21
|
+
def strategy
|
22
|
+
scope.strategy
|
23
|
+
end
|
24
|
+
|
25
|
+
def config
|
26
|
+
scope.config
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Userlist
|
2
|
+
class Push
|
3
|
+
class Relationship < Resource
|
4
|
+
include Operations::Create
|
5
|
+
include Operations::Delete
|
6
|
+
|
7
|
+
has_one :user, type: 'Userlist::Push::User'
|
8
|
+
has_one :company, type: 'Userlist::Push::Company'
|
9
|
+
|
10
|
+
def initialize(payload = {}, config = Userlist.config)
|
11
|
+
raise Userlist::ArgumentError, 'Missing required payload' unless payload
|
12
|
+
raise Userlist::ArgumentError, 'Missing required parameter :user' unless payload[:user]
|
13
|
+
raise Userlist::ArgumentError, 'Missing required parameter :company' unless payload[:company]
|
14
|
+
|
15
|
+
super
|
16
|
+
end
|
17
|
+
|
18
|
+
def url
|
19
|
+
raise Userlist::Error, "Cannot generate url for #{self.class.name} without a user" unless user
|
20
|
+
raise Userlist::Error, "Cannot generate url for #{self.class.name} without a company" unless company
|
21
|
+
|
22
|
+
"#{self.class.endpoint}/#{user.identifier}/#{company.identifier}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def push?
|
26
|
+
user&.push? && company&.push?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
module Userlist
|
2
|
+
class Push
|
3
|
+
class Resource
|
4
|
+
class << self
|
5
|
+
def resource_name
|
6
|
+
name.split('::')[-1]
|
7
|
+
end
|
8
|
+
|
9
|
+
def endpoint
|
10
|
+
"/#{resource_name.downcase}s"
|
11
|
+
end
|
12
|
+
|
13
|
+
def from_payload(payload, config = Userlist.config)
|
14
|
+
return payload if payload.nil?
|
15
|
+
return payload if payload.is_a?(self)
|
16
|
+
|
17
|
+
payload = { identifier: payload } if payload.is_a?(String) || payload.is_a?(Numeric)
|
18
|
+
|
19
|
+
new(payload, config)
|
20
|
+
end
|
21
|
+
|
22
|
+
def relationship_names
|
23
|
+
@relationship_names ||= relationships.keys
|
24
|
+
end
|
25
|
+
|
26
|
+
def relationships
|
27
|
+
@relationships ||= {}
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
def has_one(name, type:) # rubocop:disable Naming/PredicateName
|
33
|
+
relationships[name.to_sym] = { type: type }
|
34
|
+
|
35
|
+
generated_methods.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
36
|
+
def #{name}
|
37
|
+
#{type}.from_payload(payload[:#{name}], config)
|
38
|
+
end
|
39
|
+
RUBY
|
40
|
+
end
|
41
|
+
|
42
|
+
def has_many(name, **options) # rubocop:disable Naming/PredicateName
|
43
|
+
relationships[name.to_sym] = options
|
44
|
+
|
45
|
+
generated_methods.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
46
|
+
def #{name}
|
47
|
+
relationship = self.class.relationships[:#{name}]
|
48
|
+
|
49
|
+
ResourceCollection.new(payload[:#{name}], relationship, self, config)
|
50
|
+
end
|
51
|
+
RUBY
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def generated_methods
|
57
|
+
@generated_methods ||= Module.new.tap { |mod| include mod }
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
attr_reader :payload, :config
|
62
|
+
|
63
|
+
def initialize(payload = {}, config = Userlist.config)
|
64
|
+
@payload = payload
|
65
|
+
@config = config
|
66
|
+
end
|
67
|
+
|
68
|
+
def respond_to_missing?(method, include_private = false)
|
69
|
+
attribute = method.to_s.sub(/=$/, '')
|
70
|
+
payload.key?(attribute.to_sym) || super
|
71
|
+
end
|
72
|
+
|
73
|
+
def to_hash
|
74
|
+
Serializer.serialize(self)
|
75
|
+
end
|
76
|
+
alias to_h to_hash
|
77
|
+
|
78
|
+
def to_json(*args)
|
79
|
+
to_hash.to_json(*args)
|
80
|
+
end
|
81
|
+
|
82
|
+
def url
|
83
|
+
"#{self.class.endpoint}/#{identifier}"
|
84
|
+
end
|
85
|
+
|
86
|
+
def identifier
|
87
|
+
payload[:identifier]
|
88
|
+
end
|
89
|
+
|
90
|
+
def hash
|
91
|
+
self.class.hash & payload.hash
|
92
|
+
end
|
93
|
+
|
94
|
+
def eql?(other)
|
95
|
+
hash == other.hash
|
96
|
+
end
|
97
|
+
alias == eql?
|
98
|
+
|
99
|
+
def attribute_names
|
100
|
+
payload.keys.map(&:to_sym) - relationship_names
|
101
|
+
end
|
102
|
+
|
103
|
+
def relationship_names
|
104
|
+
self.class.relationship_names.to_a
|
105
|
+
end
|
106
|
+
|
107
|
+
def push?
|
108
|
+
true
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def method_missing(method, *args, &block)
|
114
|
+
if method.to_s =~ /=$/
|
115
|
+
attribute = method.to_s.sub(/=$/, '')
|
116
|
+
payload[attribute.to_sym] = args.first
|
117
|
+
elsif payload.key?(method.to_sym)
|
118
|
+
payload[method.to_sym]
|
119
|
+
else
|
120
|
+
super
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Userlist
|
2
|
+
class Push
|
3
|
+
class ResourceCollection
|
4
|
+
include Enumerable
|
5
|
+
|
6
|
+
attr_reader :collection, :relationship, :owner, :config
|
7
|
+
|
8
|
+
def initialize(collection, relationship, owner, config = Userlist.config)
|
9
|
+
@collection = Array(collection)
|
10
|
+
@relationship = relationship
|
11
|
+
@owner = owner
|
12
|
+
@config = config
|
13
|
+
end
|
14
|
+
|
15
|
+
def each
|
16
|
+
collection.each do |resource|
|
17
|
+
resource[inverse] = owner if inverse && resource.is_a?(Hash)
|
18
|
+
|
19
|
+
yield type.from_payload(resource, config)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def type
|
24
|
+
Object.const_get(relationship[:type])
|
25
|
+
end
|
26
|
+
|
27
|
+
def inverse
|
28
|
+
relationship[:inverse]
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'set'
|
2
|
+
|
3
|
+
module Userlist
|
4
|
+
class Push
|
5
|
+
class Serializer
|
6
|
+
def self.serialize(resource)
|
7
|
+
new.serialize(resource)
|
8
|
+
end
|
9
|
+
|
10
|
+
def serialize(resource)
|
11
|
+
resource = serialize_resource(resource) if resource.is_a?(Userlist::Push::Resource)
|
12
|
+
resource
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def serialize_resource(resource)
|
18
|
+
return resource.identifier if serialized_resources.include?(resource)
|
19
|
+
|
20
|
+
serialized_resources << resource
|
21
|
+
|
22
|
+
return unless resource.push?
|
23
|
+
|
24
|
+
serialized = {}
|
25
|
+
|
26
|
+
resource.attribute_names.each do |name|
|
27
|
+
serialized[name] = resource.send(name)
|
28
|
+
end
|
29
|
+
|
30
|
+
resource.relationship_names.each do |name|
|
31
|
+
next unless result = serialize_relationship(resource.send(name))
|
32
|
+
|
33
|
+
serialized[name] = result
|
34
|
+
end
|
35
|
+
|
36
|
+
serialized
|
37
|
+
end
|
38
|
+
|
39
|
+
def serialize_relationship(relationship)
|
40
|
+
return unless relationship
|
41
|
+
|
42
|
+
case relationship
|
43
|
+
when Userlist::Push::ResourceCollection
|
44
|
+
serialize_collection(relationship)
|
45
|
+
when Userlist::Push::Resource
|
46
|
+
serialize_resource(relationship)
|
47
|
+
else
|
48
|
+
raise "Cannot serialize relationship type: #{relationship.class}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def serialize_collection(collection)
|
53
|
+
serialized = collection
|
54
|
+
.map(&method(:serialize_relationship))
|
55
|
+
.compact
|
56
|
+
.reject(&:empty?)
|
57
|
+
|
58
|
+
serialized unless serialized.empty?
|
59
|
+
end
|
60
|
+
|
61
|
+
def serialized_resources
|
62
|
+
@serialized_resources ||= Set.new
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -1,17 +1,31 @@
|
|
1
|
-
require 'userlist/push/strategies/null'
|
2
|
-
require 'userlist/push/strategies/direct'
|
3
|
-
require 'userlist/push/strategies/threaded'
|
4
|
-
|
5
1
|
module Userlist
|
6
2
|
class Push
|
7
3
|
module Strategies
|
8
4
|
def self.strategy_for(strategy, config = {})
|
9
|
-
strategy =
|
10
|
-
|
5
|
+
strategy = lookup_strategy(strategy)
|
11
6
|
strategy = strategy.new(config) if strategy.respond_to?(:new)
|
12
7
|
|
13
8
|
strategy
|
14
9
|
end
|
10
|
+
|
11
|
+
def self.lookup_strategy(strategy)
|
12
|
+
return strategy unless strategy.is_a?(Symbol) || strategy.is_a?(String)
|
13
|
+
|
14
|
+
require_strategy(strategy)
|
15
|
+
const_get(strategy.to_s.capitalize, false)
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.strategy_defined?(strategy)
|
19
|
+
return true unless strategy.is_a?(Symbol) || strategy.is_a?(String)
|
20
|
+
|
21
|
+
const_defined?(strategy.to_s.capitalize, false)
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.require_strategy(strategy)
|
25
|
+
return unless strategy.is_a?(Symbol) || strategy.is_a?(String)
|
26
|
+
|
27
|
+
require("userlist/push/strategies/#{strategy}") unless strategy_defined?(strategy)
|
28
|
+
end
|
15
29
|
end
|
16
30
|
end
|
17
31
|
end
|