straight-server 0.1.2 → 0.2.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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -0
  3. data/Gemfile +3 -1
  4. data/Gemfile.lock +57 -47
  5. data/Gemfile.travis +26 -0
  6. data/README.md +175 -22
  7. data/Rakefile +7 -0
  8. data/VERSION +1 -1
  9. data/benchmark/addons.yml +15 -0
  10. data/benchmark/config.yml +78 -0
  11. data/benchmark/default_last_keychain_id +1 -0
  12. data/benchmark/server_secret +1 -0
  13. data/bin/goliath.log +6 -0
  14. data/bin/goliath.log_stdout.log +51 -0
  15. data/bin/straight-server-benchmark +68 -0
  16. data/db/migrations/003_add_payment_id_to_orders.rb +13 -0
  17. data/db/migrations/004_add_description_to_orders.rb +11 -0
  18. data/db/migrations/005_add_orders_expiration_period_to_gateways.rb +11 -0
  19. data/db/migrations/006_add_check_order_status_in_db_first_to_gateways.rb +11 -0
  20. data/db/migrations/007_add_active_switcher_to_gateways.rb +11 -0
  21. data/db/migrations/008_add_order_counters_to_gateways.rb +11 -0
  22. data/db/migrations/009_add_hashed_id_to_gateways.rb +18 -0
  23. data/examples/client/client.dart +5 -0
  24. data/examples/client/client.html +7 -15
  25. data/examples/client/client.js +15 -0
  26. data/lib/straight-server/config.rb +1 -1
  27. data/lib/straight-server/gateway.rb +241 -59
  28. data/lib/straight-server/initializer.rb +170 -44
  29. data/lib/straight-server/logger.rb +1 -1
  30. data/lib/straight-server/order.rb +74 -9
  31. data/lib/straight-server/orders_controller.rb +23 -6
  32. data/lib/straight-server/random_string.rb +18 -0
  33. data/lib/straight-server/server.rb +44 -17
  34. data/lib/straight-server/utils/hash_string_to_sym_keys.rb +24 -0
  35. data/lib/straight-server.rb +6 -3
  36. data/spec/.straight/config.yml +16 -0
  37. data/spec/.straight/server_secret +1 -0
  38. data/spec/fixtures/addons.yml +19 -0
  39. data/spec/fixtures/test_addon.rb +8 -0
  40. data/spec/lib/gateway_spec.rb +93 -13
  41. data/spec/lib/initializer_spec.rb +104 -0
  42. data/spec/lib/order_spec.rb +59 -0
  43. data/spec/lib/orders_controller_spec.rb +34 -1
  44. data/spec/lib/utils/hash_string_to_sym_keys.rb +18 -0
  45. data/spec/spec_helper.rb +10 -2
  46. data/straight-server.gemspec +36 -8
  47. data/templates/addons.yml +15 -0
  48. data/templates/config.yml +41 -0
  49. metadata +47 -5
@@ -4,19 +4,89 @@ module StraightServer
4
4
  # in one of the classes below.
5
5
  module GatewayModule
6
6
 
7
+ # Temporary fix for straight server benchmarking
8
+ @@redis = StraightServer::Config.redis[:connection] if StraightServer::Config.redis
9
+ @@websockets = {}
10
+
11
+ def fetch_transactions_for(address)
12
+ try_adapters(@blockchain_adapters) { |b| b.fetch_transactions_for(address) }
13
+ end
14
+
7
15
  class InvalidSignature < Exception; end
8
16
  class InvalidOrderId < Exception; end
9
17
  class CallbackUrlBadResponse < Exception; end
10
18
  class WebsocketExists < Exception; end
11
19
  class WebsocketForCompletedOrder < Exception; end
20
+ class GatewayInactive < Exception; end
21
+ class NoBlockchainAdapters < Exception
22
+ def message
23
+ "No blockchain adapters were found! StraightServer cannot query the blockchain.\n" +
24
+ "Check your ~/.straight/config.yml file and make sure valid blockchain adapters\n" +
25
+ "are present."
26
+ end
27
+ end
28
+ class NoWebsocketsForNewGateway < Exception
29
+ def message
30
+ "You're trying to get access to websockets on a Gateway that hasn't been saved yet"
31
+ end
32
+ end
33
+ class OrderCountersDisabled < Exception
34
+ def message
35
+ "Please enable order counting in config file! You can do is using the following option:\n\n" +
36
+ " count_orders: true\n\n" +
37
+ "and don't forget to provide Redis connection info by adding this to the config file as well:\n\n" +
38
+ " redis:\n" +
39
+ " host: localhost\n" +
40
+ " port: 6379\n" +
41
+ " db: null\n"
42
+ end
43
+ end
12
44
 
13
45
  CALLBACK_URL_ATTEMPT_TIMEFRAME = 3600 # seconds
14
46
 
15
- def initialize(*attrs)
16
-
47
+
48
+ ############# Initializers methods ########################################################
49
+ # We have separate methods, because with GatewayOnDB they are called from #after_initialize
50
+ # but in GatewayOnConfig they are called from #initialize intself.
51
+ # #########################################################################################
52
+ #
53
+ def initialize_exchange_rate_adapters
54
+ @exchange_rate_adapters ||= []
55
+ if self.exchange_rate_adapter_names
56
+ self.exchange_rate_adapter_names.each do |adapter|
57
+ begin
58
+ @exchange_rate_adapters << Straight::ExchangeRate.const_get("#{adapter}Adapter").instance
59
+ rescue NameError => e
60
+ puts "WARNING: No exchange rate adapter with the name #{a} was found!"
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ def initialize_blockchain_adapters
67
+ @blockchain_adapters = []
68
+ StraightServer::Config.blockchain_adapters.each do |a|
69
+
70
+ adapter = begin
71
+ Straight::Blockchain.const_get("#{a}Adapter")
72
+ rescue NameError
73
+ begin
74
+ Kernel.const_get(a)
75
+ rescue NameError
76
+ puts "WARNING: No blockchain adapter with the name #{a} was found!"
77
+ nil
78
+ end
79
+ end
80
+
81
+ @blockchain_adapters << adapter.mainnet_adapter if adapter
82
+ end
83
+ raise NoBlockchainAdapters if @blockchain_adapters.empty?
84
+ end
85
+
86
+ def initialize_callbacks
17
87
  # When the status of an order changes, we send an http request to the callback_url
18
88
  # and also notify a websocket client (if present, of course).
19
- @order_callbacks = [
89
+ @order_callbacks = [
20
90
  lambda do |order|
21
91
  StraightServer::Thread.new do
22
92
  send_callback_http_request order
@@ -24,27 +94,25 @@ module StraightServer
24
94
  end
25
95
  end
26
96
  ]
97
+ end
27
98
 
28
- @blockchain_adapters = [
29
- Straight::Blockchain::BlockchainInfoAdapter.mainnet_adapter,
30
- Straight::Blockchain::HelloblockIoAdapter.mainnet_adapter
31
- ]
32
-
33
- @exchange_rate_adapters = []
99
+ def initialize_status_check_schedule
34
100
  @status_check_schedule = Straight::GatewayModule::DEFAULT_STATUS_CHECK_SCHEDULE
35
- @websockets = {}
36
-
37
- super
38
- initialize_exchange_rate_adapters # should always go after super
39
101
  end
102
+ #
103
+ ############# END OF Initializers methods ##################################################
104
+
40
105
 
41
106
  # Creates a new order and saves into the DB. Checks if the MD5 hash
42
107
  # is correct first.
43
108
  def create_order(attrs={})
109
+
110
+ raise GatewayInactive unless self.active
111
+
44
112
  StraightServer.logger.info "Creating new order with attrs: #{attrs}"
45
113
  signature = attrs.delete(:signature)
46
- raise InvalidOrderId if check_signature && (attrs[:id].nil? || attrs[:id].to_i <= 0)
47
114
  if !check_signature || sign_with_secret(attrs[:id]) == signature
115
+ raise InvalidOrderId if check_signature && (attrs[:id].nil? || attrs[:id].to_i <= 0)
48
116
  order = order_for_keychain_id(
49
117
  amount: attrs[:amount],
50
118
  keychain_id: increment_last_keychain_id!,
@@ -75,47 +143,69 @@ module StraightServer
75
143
  end
76
144
 
77
145
  def add_websocket_for_order(ws, order)
78
- raise WebsocketExists unless @websockets[order.id].nil?
146
+ raise WebsocketExists unless websockets[order.id].nil?
79
147
  raise WebsocketForCompletedOrder unless order.status < 2
80
148
  StraightServer.logger.info "Opening ws connection for #{order.id}"
81
149
  ws.on(:close) do |event|
82
- @websockets.delete(order.id)
150
+ websockets.delete(order.id)
83
151
  StraightServer.logger.info "Closing ws connection for #{order.id}"
84
152
  end
85
- @websockets[order.id] = ws
153
+ websockets[order.id] = ws
86
154
  ws
87
155
  end
88
156
 
157
+ def websockets
158
+ raise NoWebsocketsForNewGateway unless self.id
159
+ @@websockets[self.id]
160
+ end
161
+
89
162
  def send_order_to_websocket_client(order)
90
- if ws = @websockets[order.id]
163
+ if ws = websockets[order.id]
91
164
  ws.send(order.to_json)
92
165
  ws.close
93
166
  end
94
167
  end
95
168
 
96
- def initialize_exchange_rate_adapters
97
- if self.exchange_rate_adapter_names
98
- self.exchange_rate_adapter_names.each do |adapter|
99
- begin
100
- @exchange_rate_adapters << Kernel.const_get("Straight::ExchangeRate::#{adapter}Adapter").new
101
- rescue NameError
102
- raise NameError, "No such adapter exists: Straight::ExchangeRate::#{adapter}Adapter"
103
- end
104
- end
169
+ def sign_with_secret(content, level: 1)
170
+ result = content.to_s
171
+ level.times do
172
+ result = OpenSSL::HMAC.digest('sha256', secret, result).unpack("H*").first
105
173
  end
174
+ result
106
175
  end
107
176
 
108
- private
109
-
110
- def sign_with_secret(content, level: 1)
111
- result = content.to_s
112
- level.times do
113
- h = HMAC::SHA1.new(secret)
114
- h << result
115
- result = h.hexdigest
116
- end
117
- result
177
+ def order_status_changed(order)
178
+ statuses = Order::STATUSES.invert
179
+ if StraightServer::Config.count_orders
180
+ increment_order_counter!(statuses[order.old_status], -1) if order.old_status
181
+ increment_order_counter!(statuses[order.status])
118
182
  end
183
+ super
184
+ end
185
+
186
+ def order_counters(reload: false)
187
+ return @order_counters if @order_counters && !reload
188
+ @order_counters = {
189
+ new: get_order_counter(:new),
190
+ unconfirmed: get_order_counter(:unconfirmed),
191
+ paid: get_order_counter(:paid),
192
+ underpaid: get_order_counter(:underpaid),
193
+ overpaid: get_order_counter(:overpaid),
194
+ expired: get_order_counter(:expired)
195
+ }
196
+ end
197
+
198
+ def get_order_counter(counter_name)
199
+ raise OrderCountersDisabled unless StraightServer::Config.count_orders
200
+ @@redis.get("#{StraightServer::Config.redis[:prefix]}:gateway_#{id}:#{counter_name}_orders_counter").to_i || 0
201
+ end
202
+
203
+ def increment_order_counter!(counter_name, by=1)
204
+ raise OrderCountersDisabled unless StraightServer::Config.count_orders
205
+ @@redis.incrby("#{StraightServer::Config.redis[:prefix]}:gateway_#{id}:#{counter_name}_orders_counter", by)
206
+ end
207
+
208
+ private
119
209
 
120
210
  # Tries to send a callback HTTP request to the resource specified
121
211
  # in the #callback_url. If it fails for any reason, it keeps trying for an hour (3600 seconds)
@@ -141,7 +231,7 @@ module StraightServer
141
231
  sleep(delay)
142
232
  send_callback_http_request(order, delay: delay*2)
143
233
  else
144
- StraightServer.logger.warn "Callback request for order #{order.id} faile, see order's #callback_response field for details"
234
+ StraightServer.logger.warn "Callback request for order #{order.id} failed, see order's #callback_response field for details"
145
235
  end
146
236
  end
147
237
 
@@ -157,6 +247,75 @@ module StraightServer
157
247
  include GatewayModule
158
248
  plugin :timestamps, create: :created_at, update: :updated_at
159
249
  plugin :serialization, :marshal, :exchange_rate_adapter_names
250
+ plugin :serialization, :marshal
251
+ plugin :after_initialize
252
+
253
+ def self.find_by_hashed_id(s)
254
+ self.where(hashed_id: s).first
255
+ end
256
+
257
+ def before_create
258
+ super
259
+ encrypt_secret
260
+ end
261
+
262
+ def after_create
263
+ @@websockets[self.id] = {}
264
+ update(hashed_id: OpenSSL::HMAC.digest('sha256', Config.server_secret, self.id.to_s).unpack("H*").first)
265
+ end
266
+
267
+ def after_initialize
268
+ @status_check_schedule = Straight::GatewayModule::DEFAULT_STATUS_CHECK_SCHEDULE
269
+ @@websockets[self.id] ||= {} if self.id
270
+ initialize_callbacks
271
+ initialize_exchange_rate_adapters
272
+ initialize_blockchain_adapters
273
+ initialize_status_check_schedule
274
+ end
275
+
276
+ # We cannot allow to store gateway secret in a DB plaintext, this would be completetly unsecure.
277
+ # Althougth we use symmetrical encryption here and store the encryption key in the
278
+ # server's in a special file (~/.straight/server_secret), which in turn can also be stolen,
279
+ # this is still marginally better than doing nothing.
280
+ #
281
+ # Also, server admnistrators now have the freedom of developing their own strategy
282
+ # of storing that secret - it doesn't have to be stored on the same machine.
283
+ def secret
284
+ decrypt_secret
285
+ end
286
+
287
+ def self.find_by_id(id)
288
+ self[id]
289
+ end
290
+
291
+ private
292
+
293
+ def encrypt_secret
294
+ cipher = OpenSSL::Cipher::AES.new(128, :CBC)
295
+ cipher.encrypt
296
+ cipher.key = OpenSSL::HMAC.digest('sha256', 'nonce', Config.server_secret).unpack("H*").first[0,16]
297
+ cipher.iv = iv = OpenSSL::HMAC.digest('sha256', 'nonce', "#{self.id}#{Config.server_secret}").unpack("H*").first[0,16]
298
+ encrypted = cipher.update(self[:secret]) << cipher.final()
299
+ base64_encrypted = Base64.strict_encode64(encrypted).encode('utf-8')
300
+ result = "#{iv}:#{base64_encrypted}"
301
+
302
+ # Check whether we can decrypt. It should not be possible to encrypt the
303
+ # gateway secret unless we are sure we can decrypt it.
304
+ if decrypt_secret(result) == self[:secret]
305
+ self.secret = result
306
+ else
307
+ raise "Decrypted and original secrets don't match! Cannot proceed with writing the encrypted gateway secret."
308
+ end
309
+ end
310
+
311
+ def decrypt_secret(encrypted_field=self[:secret])
312
+ decipher = OpenSSL::Cipher::AES.new(128, :CBC)
313
+ iv, encrypted = encrypted_field.split(':')
314
+ decipher.decrypt
315
+ decipher.key = OpenSSL::HMAC.digest('sha256', 'nonce', Config.server_secret).unpack("H*").first[0,16]
316
+ decipher.iv = iv
317
+ decipher.update(Base64.decode64(encrypted)) + decipher.final
318
+ end
160
319
 
161
320
  end
162
321
 
@@ -192,19 +351,35 @@ module StraightServer
192
351
  attr_accessor :id
193
352
 
194
353
  attr_accessor :exchange_rate_adapter_names
354
+ attr_accessor :orders_expiration_period
355
+
356
+ # This affects whether it is possible to create a new order with the gateway.
357
+ # If it's set to false, then it won't be possible to create a new order, but
358
+ # it will keep checking on the existing ones.
359
+ attr_accessor :active
360
+
361
+ def self.find_by_hashed_id(s)
362
+ self.find_by_id(s)
363
+ end
364
+
365
+ def initialize
366
+ initialize_callbacks
367
+ initialize_exchange_rate_adapters
368
+ initialize_blockchain_adapters
369
+ initialize_status_check_schedule
370
+ end
195
371
 
196
372
  # Because this is a config based gateway, we only save last_keychain_id
197
373
  # and nothing more.
198
374
  def save
199
- File.open(@last_keychain_id_file, 'w') {|f| f.write(last_keychain_id) }
375
+ save_last_keychain_id!
200
376
  end
201
377
 
202
378
  # Loads last_keychain_id from a file in the .straight dir.
203
379
  # If the file doesn't exist, we create it. Later, whenever an attribute is updated,
204
380
  # we save it to the file.
205
381
  def load_last_keychain_id!
206
- @last_keychain_id_file = StraightServer::Initializer::STRAIGHT_CONFIG_PATH +
207
- "/#{name}_last_keychain_id"
382
+ @last_keychain_id_file ||= StraightServer::Initializer::ConfigDir.path + "/#{name}_last_keychain_id"
208
383
  if File.exists?(@last_keychain_id_file)
209
384
  self.last_keychain_id = File.read(@last_keychain_id_file).to_i
210
385
  else
@@ -213,6 +388,17 @@ module StraightServer
213
388
  end
214
389
  end
215
390
 
391
+ def save_last_keychain_id!
392
+ @last_keychain_id_file ||= StraightServer::Initializer::ConfigDir.path + "/#{name}_last_keychain_id"
393
+ File.open(@last_keychain_id_file, 'w') {|f| f.write(last_keychain_id) }
394
+ end
395
+
396
+ # This method is a replacement for the Sequel's model one used in DB version of the gateway
397
+ # and it finds gateways using the index of @@gateways Array.
398
+ def self.find_by_id(id)
399
+ @@gateways[id.to_i-1]
400
+ end
401
+
216
402
  # This will later be used in the #find_by_id. Because we don't use a DB,
217
403
  # the id will actually be the index of an element in this Array. Thus,
218
404
  # the order in which gateways follow in the config file is important.
@@ -223,27 +409,23 @@ module StraightServer
223
409
  StraightServer::Config.gateways.each do |name, attrs|
224
410
  i += 1
225
411
  gateway = self.new
226
- gateway.pubkey = attrs['pubkey']
227
- gateway.confirmations_required = attrs['confirmations_required'].to_i
228
- gateway.order_class = attrs['order_class']
229
- gateway.secret = attrs['secret']
230
- gateway.check_signature = attrs['check_signature']
231
- gateway.callback_url = attrs['callback_url']
232
- gateway.default_currency = attrs['default_currency']
233
- gateway.name = name
234
- gateway.id = i
412
+ gateway.pubkey = attrs['pubkey']
413
+ gateway.confirmations_required = attrs['confirmations_required'].to_i
414
+ gateway.order_class = attrs['order_class']
415
+ gateway.secret = attrs['secret']
416
+ gateway.check_signature = attrs['check_signature']
417
+ gateway.callback_url = attrs['callback_url']
418
+ gateway.default_currency = attrs['default_currency']
419
+ gateway.orders_expiration_period = attrs['orders_expiration_period']
420
+ gateway.active = attrs['active']
421
+ gateway.name = name
422
+ gateway.id = i
235
423
  gateway.exchange_rate_adapter_names = attrs['exchange_rate_adapters']
236
424
  gateway.initialize_exchange_rate_adapters
237
425
  gateway.load_last_keychain_id!
426
+ @@websockets[i] = {}
238
427
  @@gateways << gateway
239
- end
240
-
241
-
242
- # This method is a replacement for the Sequel's model one used in DB version of the gateway
243
- # and it finds gateways using the index of @@gateways Array.
244
- def self.find_by_id(id)
245
- @@gateways[id.to_i-1]
246
- end
428
+ end if StraightServer::Config.gateways
247
429
 
248
430
  end
249
431
 
@@ -251,7 +433,7 @@ module StraightServer
251
433
  # so they can simply start using a single gateway on their machines, a gateway which attributes are defined
252
434
  # in a config file instead of a DB. That way they don't need special tools to access the DB and create
253
435
  # a gateway, but can simply edit the config file.
254
- Gateway = if StraightServer::Config.gateways_source = 'config'
436
+ Gateway = if StraightServer::Config.gateways_source == 'config'
255
437
  GatewayOnConfig
256
438
  else
257
439
  GatewayOnDB
@@ -2,76 +2,202 @@ module StraightServer
2
2
 
3
3
  module Initializer
4
4
 
5
- GEM_ROOT = File.expand_path('../..', File.dirname(__FILE__))
6
- STRAIGHT_CONFIG_PATH = ENV['HOME'] + '/.straight'
5
+ GEM_ROOT = File.expand_path('../..', File.dirname(__FILE__))
6
+
7
+ module ConfigDir
8
+
9
+ class << self
10
+
11
+ # Determine config dir or set default. Useful when we want to
12
+ # have different settings for production or staging or development environments.
13
+ def set!(path=nil)
14
+ @@config_dir = path and return if path
15
+ @@config_dir = ENV['HOME'] + '/.straight'
16
+ ARGV.each do |a|
17
+ if a =~ /\A--config-dir=.+/
18
+ @@config_dir = File.expand_path(a.sub('--config-dir=', ''))
19
+ break
20
+ elsif a =~ /\A-c .+/
21
+ @@config_dir = File.expand_path(a.sub('-c ', ''))
22
+ break
23
+ end
24
+ end
25
+ puts "Setting config dir to #{@@config_dir}"
26
+ end
27
+
28
+ def path
29
+ @@config_dir
30
+ end
31
+
32
+ end
33
+
34
+ end
7
35
 
8
36
  def prepare
9
- create_config_file unless File.exist?(STRAIGHT_CONFIG_PATH + '/config.yml')
37
+ ConfigDir.set!
38
+ create_config_files
10
39
  read_config_file
11
40
  create_logger
12
41
  connect_to_db
13
- run_migrations if migrations_pending?
42
+ run_migrations if migrations_pending?
43
+ setup_redis_connection if StraightServer::Config.count_orders
44
+ initialize_routes
14
45
  end
15
46
 
16
- private
47
+ def add_route(path, &block)
48
+ @routes[path] = block
49
+ end
17
50
 
18
- def create_config_file
51
+ def create_config_files
52
+ FileUtils.mkdir_p(ConfigDir.path) unless File.exist?(ConfigDir.path)
53
+
54
+ unless File.exist?(ConfigDir.path + '/addons.yml')
55
+ puts "\e[1;33mNOTICE!\e[0m \e[33mNo file ~/.straight/addons.yml was found. Created an empty sample for you.\e[0m"
56
+ puts "No need to restart until you actually list your addons there. Now will continue loading StraightServer."
57
+ FileUtils.cp(GEM_ROOT + '/templates/addons.yml', ENV['HOME'] + '/.straight/')
58
+ end
59
+
60
+ unless File.exist?(ConfigDir.path + '/server_secret')
61
+ puts "\e[1;33mNOTICE!\e[0m \e[33mNo file ~/.straight/server_secret was found. Created one for you.\e[0m"
62
+ puts "No need to restart so far. Now will continue loading StraightServer."
63
+ File.open(ConfigDir.path + '/server_secret', "w") do |f|
64
+ f.puts String.random(16)
65
+ end
66
+ end
67
+
68
+ unless File.exist?(ConfigDir.path + '/config.yml')
19
69
  puts "\e[1;33mWARNING!\e[0m \e[33mNo file ~/.straight/config was found. Created a sample one for you.\e[0m"
20
70
  puts "You should edit it and try starting the server again.\n"
21
71
 
22
- FileUtils.mkdir_p(STRAIGHT_CONFIG_PATH) unless File.exist?(STRAIGHT_CONFIG_PATH)
23
72
  FileUtils.cp(GEM_ROOT + '/templates/config.yml', ENV['HOME'] + '/.straight/')
24
73
  puts "Shutting down now.\n\n"
25
74
  exit
26
75
  end
27
76
 
28
- def read_config_file
29
- YAML.load_file(STRAIGHT_CONFIG_PATH + '/config.yml').each do |k,v|
30
- StraightServer::Config.send(k + '=', v)
31
- end
32
- end
77
+ end
33
78
 
34
- def connect_to_db
79
+ def read_config_file
80
+ YAML.load_file(ConfigDir.path + '/config.yml').each do |k,v|
81
+ StraightServer::Config.send(k + '=', v)
82
+ end
83
+ StraightServer::Config.server_secret = File.read(ConfigDir.path + '/server_secret').chomp
84
+ end
35
85
 
36
- # symbolize keys for convenience
37
- db_config = StraightServer::Config.db.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
86
+ def connect_to_db
38
87
 
39
- db_name = if db_config[:adapter] == 'sqlite'
40
- STRAIGHT_CONFIG_PATH + "/" + db_config[:name]
41
- else
42
- db_config[:name]
43
- end
88
+ # symbolize keys for convenience
89
+ db_config = StraightServer::Config.db.keys_to_sym
44
90
 
45
- StraightServer.db_connection = Sequel.connect(
46
- "#{db_config[:adapter]}://" +
47
- "#{db_config[:user]}#{(":" if db_config[:user])}" +
48
- "#{db_config[:password]}#{("@" if db_config[:user] || db_config[:password])}" +
49
- "#{db_config[:host]}#{(":" if db_config[:port])}" +
50
- "#{db_config[:port]}#{("/" if db_config[:host] || db_config[:port])}" +
51
- "#{db_name}"
52
- )
91
+ db_name = if db_config[:adapter] == 'sqlite'
92
+ ConfigDir.path + "/" + db_config[:name]
93
+ else
94
+ db_config[:name]
53
95
  end
54
96
 
55
- def run_migrations
56
- print "\nPending migrations for the database detected. Migrating..."
57
- Sequel::Migrator.run(StraightServer.db_connection, GEM_ROOT + '/db/migrations/')
58
- print "done\n\n"
59
- end
97
+ StraightServer.db_connection = Sequel.connect(
98
+ "#{db_config[:adapter]}://" +
99
+ "#{db_config[:user]}#{(":" if db_config[:user])}" +
100
+ "#{db_config[:password]}#{("@" if db_config[:user] || db_config[:password])}" +
101
+ "#{db_config[:host]}#{(":" if db_config[:port])}" +
102
+ "#{db_config[:port]}#{("/" if db_config[:host] || db_config[:port])}" +
103
+ "#{db_name}"
104
+ )
105
+ end
106
+
107
+ def run_migrations
108
+ print "\nPending migrations for the database detected. Migrating..."
109
+ Sequel::Migrator.run(StraightServer.db_connection, GEM_ROOT + '/db/migrations/')
110
+ print "done\n\n"
111
+ end
112
+
113
+ def migrations_pending?
114
+ !Sequel::Migrator.is_current?(StraightServer.db_connection, GEM_ROOT + '/db/migrations/')
115
+ end
116
+
117
+ def create_logger
118
+ require_relative 'logger'
119
+ StraightServer.logger = StraightServer::Logger.new(
120
+ log_level: ::Logger.const_get(Config.logmaster['log_level'].upcase),
121
+ file: ConfigDir.path + '/' + Config.logmaster['file'],
122
+ raise_exception: Config.logmaster['raise_exception'],
123
+ name: Config.logmaster['name'],
124
+ email_config: Config.logmaster['email_config']
125
+ )
126
+ end
60
127
 
61
- def migrations_pending?
62
- !Sequel::Migrator.is_current?(StraightServer.db_connection, GEM_ROOT + '/db/migrations/')
128
+ def initialize_routes
129
+ @routes = {}
130
+ add_route /\A\/gateways\/.+?\/orders(\/.+)?\Z/ do |env|
131
+ controller = OrdersController.new(env)
132
+ controller.response
63
133
  end
134
+ end
64
135
 
65
- def create_logger
66
- require_relative 'logger'
67
- StraightServer.logger = StraightServer::Logger.new(
68
- log_level: ::Logger.const_get(Config.logmaster['log_level'].upcase),
69
- file: STRAIGHT_CONFIG_PATH + '/' + Config.logmaster['file'],
70
- raise_exception: Config.logmaster['raise_exception'],
71
- name: Config.logmaster['name'],
72
- email_config: Config.logmaster['email_config']
73
- )
136
+ # Loads addon modules into StraightServer::Server. To be useful,
137
+ # an addon most probably has to implement self.extended(server) callback.
138
+ # That way, it can access the server object and, for example, add routes
139
+ # with StraightServer::Server#add_route.
140
+ #
141
+ # Addon modules can be both rubygems or files under ~/.straight/addons/.
142
+ # If ~/.straight/addons.yml contains a 'path' key for a particular addon, then it means
143
+ # the addon is placed under the ~/.straight/addons/. If not, it is assumed it
144
+ # is already in the LOAD_PATH somehow, with rubygems for example.
145
+ def load_addons
146
+ # load ~/.straight/addons.yml
147
+ addons = YAML.load_file(ConfigDir.path + '/addons.yml')
148
+ addons.each do |name, addon|
149
+ StraightServer.logger.info "Loading #{name} addon"
150
+ if addon['path'] # First, check the ~/.straight/addons dir
151
+ require ConfigDir.path + '/' + addon['path']
152
+ else # then assume it's already loaded using rubygems
153
+ require name
154
+ end
155
+ # extending the current server object with the addon
156
+ extend Kernel.const_get("StraightServer::Addon::#{addon['module']}")
157
+ end if addons
158
+ end
159
+
160
+ # Finds orders that have statuses < 2 and starts querying the blockchain
161
+ # for them (unless they are also expired). This is for cases when the server was shut down,
162
+ # but some orders statuses are not resolved.
163
+ def resume_tracking_active_orders!
164
+ StraightServer::Order.where('status < 2').each do |order|
165
+
166
+ # Order is expired, but status is < 2! Suspcicious, probably
167
+ # an unclean shutdown of the server. Let's check and update the status manually once.
168
+ if order.time_left_before_expiration < 1
169
+ StraightServer.logger.info "Order #{order.id} seems to be expired, but status remains #{order.status}. Will check for status update manually."
170
+ order.status(reload: true)
171
+
172
+ # if we still see no transactions to that address,
173
+ # consider the order truly expired and update the status accordingly
174
+ order.status = StraightServer::Order::STATUSES[:expired] if order.status < 2
175
+ order.save
176
+ StraightServer.logger.info "Order #{order.id} status updated, new status is #{order.status}"
177
+
178
+ # Order is NOT expired and status is < 2. Let's keep tracking it.
179
+ else
180
+ StraightServer.logger.info "Resuming tracking of order #{order.id}, current status is #{order.status}, time before expiration: #{order.time_left_before_expiration} seconds."
181
+ StraightServer::Thread.new do
182
+ order.start_periodic_status_check
183
+ end
184
+ end
74
185
  end
186
+ end
187
+
188
+ # Loads redis gem and sets up key prefixes for order counters
189
+ # for the current straight environment.
190
+ def setup_redis_connection
191
+ require 'redis'
192
+ Config.redis = Config.redis.keys_to_sym
193
+ Config.redis[:connection] = Redis.new(
194
+ host: Config.redis[:host],
195
+ port: Config.redis[:port],
196
+ db: Config.redis[:db],
197
+ password: Config.redis[:password]
198
+ )
199
+ Config.redis[:prefix] ||= "StraightServer:#{Config.environment}"
200
+ end
75
201
 
76
202
  end
77
203
 
@@ -6,7 +6,7 @@ module StraightServer
6
6
 
7
7
  n.times { puts "\n" }
8
8
 
9
- File.open(StraightServer::Initializer::STRAIGHT_CONFIG_PATH + '/' + Config.logmaster['file'], 'a') do |f|
9
+ File.open(StraightServer::Initializer::ConfigDir.path + '/' + Config.logmaster['file'], 'a') do |f|
10
10
  n.times do
11
11
  f.puts "\n"
12
12
  end