tap-vend 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 7dfef7b8aa2a9ba8b9402a3a4bd4bf0f53054d64
4
+ data.tar.gz: 6f050959e21002cfee3e29934661cc1a1fa77fa3
5
+ SHA512:
6
+ metadata.gz: e84f324d907ec2cc9f97c4b0768885a3aec48d80a2696128946d3df774c89165ef4b3fb524a25b6f05fb195501e8754d09b904e33fcd069a05f74a34055ea941
7
+ data.tar.gz: a8ccdac5a0a1e32ddb6add6b5624e01c365a9fa126a218550143042262c0971371a87c6567e9dde854f6724d645b17c685878210354f61461f8aa5e0ec359617
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 follain
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ tap-vend
2
+ ========
3
+
4
+ ## A [singer.io](http://singer.io) tap to extract data from [Vend POS](http://vendhq.com) and load into any Singer target, like Stitch or CSV
5
+
6
+ # Configuration
7
+
8
+ {
9
+ "store":"your-vend-store",
10
+ "token":"your-vend-personal-token",
11
+ "requests_per_second": 2
12
+ }
13
+
14
+ # Usage (with [Stitch target](https://github.com/singer-io/target-stitch))
15
+
16
+ > bundle exec tap-vend
17
+ Usage: tap-vend [options]
18
+ -c, --config config_file Set config file (json)
19
+ -s, --state state_file Set state file (json)
20
+ -h, --help Displays help
21
+ -v, --verbose Enables verbose logging to STDERR
22
+
23
+ > pip install target-stitch
24
+ > gem install tap-vend
25
+ > bundle exec tap-vend -c config.vend.json -s state.json | target-stitch --config config.stitch.json | tail -1 > state.new.json
data/bin/tap-vend ADDED
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler'
4
+
5
+ Bundler.require
6
+
7
+ require 'concurrent'
8
+ require 'faraday'
9
+ require 'faraday_middleware'
10
+ require 'optparse'
11
+ require 'peach'
12
+
13
+ require './lib/client'
14
+ require './lib/schema'
15
+ require './lib/models/base'
16
+
17
+ require './lib/models/consignment'
18
+ require './lib/models/customer'
19
+ require './lib/models/customer_group'
20
+ require './lib/models/inventory'
21
+ require './lib/models/outlet'
22
+ require './lib/models/outlet_attribute'
23
+ require './lib/models/outlet_tax'
24
+ require './lib/models/payment_type'
25
+ require './lib/models/price_book'
26
+ require './lib/models/price_book_product'
27
+ require './lib/models/product'
28
+ require './lib/models/product_type'
29
+ require './lib/models/register'
30
+ require './lib/models/sale'
31
+ require './lib/models/supplier'
32
+ require './lib/models/tax'
33
+ require './lib/models/user'
34
+
35
+ config_file = nil
36
+ state_file = nil
37
+ verbose = false
38
+
39
+ parser = OptionParser.new do |opts|
40
+ opts.banner = "Usage: #{$0} [options]"
41
+ opts.on('-c', '--config config_file', 'Set config file (json)') do |config|
42
+ config_file = config
43
+ end
44
+
45
+ opts.on('-s', '--state state_file', 'Set state file (json)') do |state|
46
+ state_file = state
47
+ end
48
+
49
+ opts.on('-h', '--help', 'Displays help') do
50
+ puts opts
51
+ exit
52
+ end
53
+
54
+ opts.on('-v', '--verbose', 'Enables verbose logging to STDERR') do
55
+ verbose = true
56
+ end
57
+ end
58
+
59
+ parser.parse!
60
+
61
+ if config_file.nil?
62
+ puts parser
63
+ exit
64
+ end
65
+
66
+ config = JSON.parse(File.read(config_file))
67
+
68
+ state = {}
69
+ if state_file
70
+ state = JSON.parse(File.read(state_file))
71
+ end
72
+
73
+
74
+ Client.requests_per_second = config['requests_per_second']
75
+
76
+ client = Client.new(
77
+ config['store'],
78
+ config['token'],
79
+ verbose,
80
+ state
81
+ )
82
+
83
+ Models::Base.subclasses.each do |model|
84
+ client.output model.schema
85
+ end
86
+
87
+ Models::Base.subclasses.peach(50) do |model|
88
+ client.process model
89
+ end
data/lib/client.rb ADDED
@@ -0,0 +1,72 @@
1
+ require 'json'
2
+ require 'logger'
3
+ require 'thread'
4
+ require 'glutton_ratelimit'
5
+
6
+ Client = Struct.new(:store, :token, :verbose, :state) do
7
+ extend GluttonRatelimit
8
+ MUTEX = Mutex.new
9
+ OUTPUT = Mutex.new
10
+
11
+ def self.requests_per_second=(rps)
12
+ rps = rps.to_r
13
+ if rps > 0
14
+ rate_limit :get, rps.numerator, rps.denominator
15
+ end
16
+ end
17
+
18
+ def process(model, after = 0)
19
+ MUTEX.synchronize do
20
+ self.state ||= Concurrent::Hash.new()
21
+ end
22
+
23
+ state['value'] ||= Concurrent::Hash.new()
24
+
25
+ return unless model.path
26
+ after = state['value'][model.stream].to_i
27
+
28
+ records = get(model.path, after: after)
29
+
30
+ if Array(records['data']).flatten.compact.any?
31
+ records['data'].peach(50) do |record|
32
+ model.new(record, self).records.flatten.each do |model_record|
33
+ output model_record
34
+ end
35
+ end
36
+ state['value'][model.stream] = records['version']['max']
37
+ end
38
+
39
+ output(type: :STATE, value: state)
40
+ end
41
+
42
+ def output(object)
43
+ OUTPUT.synchronize do
44
+ puts JSON.generate(object)
45
+ end
46
+ end
47
+
48
+ def get(path, after: 0, page_size: 5000)
49
+ connection.get("/api/2.0/#{path}", after: after, page_size: page_size).body
50
+ end
51
+
52
+ private
53
+
54
+ def log(string)
55
+ OUTPUT.synchronize do
56
+ (@logger ||= ::Logger.new(STDERR)).info string
57
+ end
58
+ end
59
+
60
+ def connection
61
+ MUTEX.synchronize do
62
+ @connection ||= Faraday::Connection.new do |conn|
63
+ conn.authorization :Bearer, token
64
+ conn.response :json
65
+ conn.url_prefix = "https://#{store}.vendhq.com"
66
+ conn.response :logger, ::Logger.new(STDERR) if verbose
67
+ conn.adapter Faraday.default_adapter
68
+ end
69
+ end
70
+ @connection
71
+ end
72
+ end
@@ -0,0 +1,43 @@
1
+ require 'active_support/inflector'
2
+
3
+ module Models
4
+ Base = Struct.new(:data, :client) do
5
+ def self.subclasses
6
+ ObjectSpace.each_object(Class).select { |klass| klass < self }
7
+ end
8
+
9
+ def self.path
10
+ name.demodulize.tableize
11
+ end
12
+
13
+ def self.stream
14
+ name.demodulize.tableize
15
+ end
16
+
17
+ def self.schema(&block)
18
+ @schema ||= Schema.new(stream)
19
+ @schema.instance_eval &block if block_given?
20
+ @schema.to_hash
21
+ end
22
+
23
+ def transform
24
+ data.dup
25
+ end
26
+
27
+ def base_record
28
+ {
29
+ type: "RECORD",
30
+ stream: self.class.stream,
31
+ record: transform
32
+ }
33
+ end
34
+
35
+ def extra_records
36
+ []
37
+ end
38
+
39
+ def records
40
+ [ base_record ] + extra_records.map(&:records)
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,38 @@
1
+
2
+ module Models
3
+ class ConsignmentProduct < Base
4
+ def self.path
5
+ nil
6
+ end
7
+
8
+ schema do
9
+ string :id
10
+ end
11
+
12
+ def transform
13
+ super.tap do |hash|
14
+ hash.merge!(
15
+ 'id' =>
16
+ hash.slice('consignment_id', 'product_id').values.join('.')
17
+ )
18
+ end
19
+ end
20
+ end
21
+
22
+ class Consignment < Base
23
+ schema do
24
+ string :id
25
+ end
26
+
27
+ def extra_records
28
+ return [] unless data['deleted_at'].nil?
29
+ products = client.get("consignments/#{data['id']}/products")['data']
30
+ Array(products).flatten.map do |product|
31
+ ConsignmentProduct.new(
32
+ product.merge('consignment_id' => data['id']),
33
+ client
34
+ )
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,8 @@
1
+
2
+ module Models
3
+ class Customer < Base
4
+ schema do
5
+ string :id
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+
2
+ module Models
3
+ class CustomerGroup < Base
4
+ schema do
5
+ string :id
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+
2
+ module Models
3
+ class Inventory < Base
4
+ def self.path
5
+ 'inventory'
6
+ end
7
+
8
+ schema do
9
+ string :id
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,31 @@
1
+
2
+ module Models
3
+ class Outlet < Base
4
+ schema do
5
+ string :id
6
+ string :name
7
+ string :physical_address_1
8
+ string :physical_address_2
9
+ string :physical_suburb
10
+ string :physical_city
11
+ string :physical_postcode
12
+ string :physical_state
13
+ string :physical_country_id
14
+ end
15
+
16
+ def transform
17
+ super.tap do |hash|
18
+ hash.delete 'attributes'
19
+ end
20
+ end
21
+
22
+ def extra_records
23
+ data['attributes'].map do |attribute|
24
+ OutletAttribute.new(
25
+ attribute.merge('outlet_id' => data['id']),
26
+ client
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,21 @@
1
+
2
+ module Models
3
+ class OutletAttribute < Base
4
+ def self.path
5
+ nil
6
+ end
7
+
8
+ schema do
9
+ string :id
10
+ string :outlet_id
11
+ string :key
12
+ string :value
13
+ end
14
+
15
+ def transform
16
+ super.tap do |hash|
17
+ hash.merge! 'id' => hash.slice('outlet_id', 'key').values.join('.')
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+
2
+ module Models
3
+ class OutletTax < Base
4
+ schema do
5
+ string :id
6
+ end
7
+
8
+ def transform
9
+ super.tap do |hash|
10
+ hash.merge!(
11
+ 'id' =>
12
+ hash.slice('outlet_id', 'product_id', 'tax_id').values.join('.')
13
+ )
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,8 @@
1
+
2
+ module Models
3
+ class PaymentType < Base
4
+ schema do
5
+ string :id
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+
2
+ module Models
3
+ class PriceBook < Base
4
+ schema do
5
+ string :id
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+
2
+ module Models
3
+ class PriceBookProduct < Base
4
+ schema do
5
+ string :id
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+
2
+ module Models
3
+ class Product < Base
4
+ schema do
5
+ string :id
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+
2
+ module Models
3
+ class ProductType < Base
4
+ schema do
5
+ string :id
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+
2
+ module Models
3
+ class Register < Base
4
+ schema do
5
+ string :id
6
+ string :name
7
+ string :outlet_id
8
+ string :invoice_prefix
9
+ string :invoice_suffix
10
+ end
11
+
12
+ def transform
13
+ super.tap do |hash|
14
+ hash.delete 'attributes'
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,8 @@
1
+
2
+ module Models
3
+ class Sale < Base
4
+ schema do
5
+ string :id
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+
2
+ module Models
3
+ class Supplier < Base
4
+ schema do
5
+ string :id
6
+ end
7
+ end
8
+ end
data/lib/models/tax.rb ADDED
@@ -0,0 +1,8 @@
1
+
2
+ module Models
3
+ class Tax < Base
4
+ schema do
5
+ string :id
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,28 @@
1
+
2
+ module Models
3
+ class User < Base
4
+ schema do
5
+ string :id
6
+ string :username
7
+ string :displayname
8
+ string :email, nil: true
9
+ string :email_verified_at, nil: true
10
+ string :account_type
11
+ string :created_at
12
+ string :updated_at
13
+ string :deleted_at, nil: true
14
+ end
15
+
16
+ def transform
17
+ super.slice 'id',
18
+ 'username',
19
+ 'email',
20
+ 'account_type',
21
+ 'email_verified_at',
22
+ 'created_at',
23
+ 'updated_at',
24
+ 'deleted_at',
25
+ 'seen_at'
26
+ end
27
+ end
28
+ end
data/lib/schema.rb ADDED
@@ -0,0 +1,25 @@
1
+
2
+ Schema = Struct.new(:stream) do
3
+ def string(name, options={})
4
+ if options[:nil]
5
+ properties[name.to_sym] = { type: [:string, :null] }
6
+ else
7
+ properties[name.to_sym] = { type: :string }
8
+ end
9
+ end
10
+
11
+ def properties
12
+ @properties ||= {}
13
+ end
14
+
15
+ def to_hash
16
+ {
17
+ type: :SCHEMA,
18
+ stream: stream,
19
+ key_properties: [:id],
20
+ schema: {
21
+ properties: properties
22
+ }
23
+ }
24
+ end
25
+ end
@@ -0,0 +1,3 @@
1
+ module TapVend
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,188 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tap-vend
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Joe Lind
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-06-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.1'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 5.1.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '5.1'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 5.1.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: concurrent-ruby
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.0'
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: 1.0.2
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: '1.0'
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: 1.0.2
53
+ - !ruby/object:Gem::Dependency
54
+ name: faraday
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '0.12'
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: 0.12.1
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: '0.12'
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: 0.12.1
73
+ - !ruby/object:Gem::Dependency
74
+ name: faraday_middleware
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: '0.11'
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 0.11.0.1
83
+ type: :runtime
84
+ prerelease: false
85
+ version_requirements: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.11'
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: 0.11.0.1
93
+ - !ruby/object:Gem::Dependency
94
+ name: glutton_ratelimit
95
+ requirement: !ruby/object:Gem::Requirement
96
+ requirements:
97
+ - - "~>"
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: 0.2.0
103
+ type: :runtime
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ version: 0.2.0
113
+ - !ruby/object:Gem::Dependency
114
+ name: peach
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - "~>"
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: 0.5.1
123
+ type: :runtime
124
+ prerelease: false
125
+ version_requirements: !ruby/object:Gem::Requirement
126
+ requirements:
127
+ - - "~>"
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: 0.5.1
133
+ description: Stream Vend POS records to a Singer target, such as Stitch
134
+ email: joelind@gmail.com
135
+ executables:
136
+ - tap-vend
137
+ extensions: []
138
+ extra_rdoc_files: []
139
+ files:
140
+ - LICENSE
141
+ - README.md
142
+ - bin/tap-vend
143
+ - lib/client.rb
144
+ - lib/models/base.rb
145
+ - lib/models/consignment.rb
146
+ - lib/models/customer.rb
147
+ - lib/models/customer_group.rb
148
+ - lib/models/inventory.rb
149
+ - lib/models/outlet.rb
150
+ - lib/models/outlet_attribute.rb
151
+ - lib/models/outlet_tax.rb
152
+ - lib/models/payment_type.rb
153
+ - lib/models/price_book.rb
154
+ - lib/models/price_book_product.rb
155
+ - lib/models/product.rb
156
+ - lib/models/product_type.rb
157
+ - lib/models/register.rb
158
+ - lib/models/sale.rb
159
+ - lib/models/supplier.rb
160
+ - lib/models/tax.rb
161
+ - lib/models/user.rb
162
+ - lib/schema.rb
163
+ - lib/tap_vend/version.rb
164
+ homepage: https://github.com/Follain/tap-vend
165
+ licenses:
166
+ - MIT
167
+ metadata: {}
168
+ post_install_message:
169
+ rdoc_options: []
170
+ require_paths:
171
+ - lib
172
+ required_ruby_version: !ruby/object:Gem::Requirement
173
+ requirements:
174
+ - - ">="
175
+ - !ruby/object:Gem::Version
176
+ version: '0'
177
+ required_rubygems_version: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ requirements: []
183
+ rubyforge_project:
184
+ rubygems_version: 2.5.2
185
+ signing_key:
186
+ specification_version: 4
187
+ summary: Singer.io tap for Vend POS
188
+ test_files: []