straight-server 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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