Skip to content

Commit

Permalink
Add management API v1 (#740)
Browse files Browse the repository at this point in the history
Introducing Management API Alpha version, provide the ability to manage deposits and withdrawal with third-party RSA signed payload, Now Barong and AppLogic application can participate in fiat transfer logic.
  • Loading branch information
yivo authored and Louis committed Apr 4, 2018
1 parent c751c94 commit 859031d
Show file tree
Hide file tree
Showing 89 changed files with 2,464 additions and 723 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -27,6 +27,7 @@ tmp
/config/withdraw_channels.yml
/config/deposit_channels.yml
/config/plugins.yml
/config/management_api_v1.yml
/config/seed/*

# Ignore translations.js
Expand Down
4 changes: 3 additions & 1 deletion Gemfile
Expand Up @@ -56,6 +56,7 @@ gem 'grape', '~> 1.0.1'
gem 'grape-entity', '~> 0.5.2'
gem 'grape-swagger', '~> 0.27.3'
gem 'grape-swagger-ui', '~> 2.2.8'
gem 'grape-swagger-entity', '~> 0.2'
gem 'rack-attack', '~> 4.3.1'
gem 'easy_table'
gem 'faraday', '~> 0.12'
Expand All @@ -72,10 +73,11 @@ gem 'method-not-implemented', '~> 1.0'
gem 'passgen', '~> 1.0'
gem 'validates_lengths_from_database', '~> 0.7.0'
gem 'ejs','~> 1.1'
gem 'jwt-multisig', '~> 1.0'

group :development, :test do
gem 'factory_bot_rails'
gem 'faker', '~> 1.4.3'
gem 'faker', '~> 1.8'
gem 'binding_of_caller'
gem 'quiet_assets'
gem 'timecop'
Expand Down
20 changes: 14 additions & 6 deletions Gemfile.lock
Expand Up @@ -143,8 +143,8 @@ GEM
factory_bot_rails (4.8.2)
factory_bot (~> 4.8.2)
railties (>= 3.0.0)
faker (1.4.3)
i18n (~> 0.5)
faker (1.8.7)
i18n (>= 0.7)
faraday (0.12.2)
multipart-post (>= 1.2, < 3)
ffi (1.9.18)
Expand All @@ -171,14 +171,17 @@ GEM
multi_json (>= 1.3.2)
grape-swagger (0.27.3)
grape (>= 0.16.2)
grape-swagger-entity (0.2.3)
grape-entity (>= 0.5.0)
grape-swagger (>= 0.20.4)
grape-swagger-ui (2.2.8)
railties (>= 3.1)
hashdiff (0.3.7)
hashie (3.5.7)
http_accept_language (2.1.1)
http_parser.rb (0.6.0)
httpclient (2.8.3)
i18n (0.9.1)
i18n (0.9.5)
concurrent-ruby (~> 1.0)
ice_nine (0.11.2)
io-like (0.3.0)
Expand All @@ -191,6 +194,9 @@ GEM
thor (>= 0.14, < 2.0)
json (2.1.0)
jwt (2.1.0)
jwt-multisig (1.0.0)
activesupport (>= 4.0, < 6.0)
jwt (~> 2.1)
kaminari (1.1.1)
activesupport (>= 4.1.0)
kaminari-actionview (= 1.1.1)
Expand Down Expand Up @@ -219,7 +225,7 @@ GEM
mini_portile2 (2.3.0)
mini_racer (0.1.15)
libv8 (~> 6.3)
minitest (5.10.3)
minitest (5.11.3)
mocha (1.3.0)
metaclass (~> 0.0.1)
momentjs-rails (2.17.1)
Expand Down Expand Up @@ -394,7 +400,7 @@ GEM
thread_safe (0.3.6)
tilt (2.0.8)
timecop (0.9.1)
tzinfo (1.2.4)
tzinfo (1.2.5)
thread_safe (~> 0.1)
uglifier (4.1.3)
execjs (>= 0.3.0, < 3)
Expand Down Expand Up @@ -444,7 +450,7 @@ DEPENDENCIES
enumerize
eventmachine (~> 1.0.4)
factory_bot_rails
faker (~> 1.4.3)
faker (~> 1.8)
faraday (~> 0.12)
figaro
font-awesome-sass
Expand All @@ -453,13 +459,15 @@ DEPENDENCIES
grape (~> 1.0.1)
grape-entity (~> 0.5.2)
grape-swagger (~> 0.27.3)
grape-swagger-entity (~> 0.2)
grape-swagger-ui (~> 2.2.8)
hashie (~> 3.0)
http_accept_language
jbuilder
jquery-rails
json
jwt (~> 2.1)
jwt-multisig (~> 1.0)
kaminari
liability-proof (= 0.0.9)
memoist (~> 0.16)
Expand Down
2 changes: 1 addition & 1 deletion app/api/api_v2/entities/order.rb
Expand Up @@ -10,7 +10,7 @@ class Order < Base

expose :avg_price, documentation: "Average execution price, average of price in trades."

expose :state, documentation: "One of 'wait', 'done', or 'cancel'. An order in 'wait' is an active order, waiting fullfillment; a 'done' order is an order fullfilled; 'cancel' means the order has been cancelled."
expose :state, documentation: "One of 'wait', 'done', or 'cancel'. An order in 'wait' is an active order, waiting fullfillment; a 'done' order is an order fullfilled; 'cancel' means the order has been canceled."

expose :market_id, as: :market, documentation: "The market in which the order is placed, e.g. 'btcusd'. All available markets can be found at /api/v2/markets."

Expand Down
10 changes: 1 addition & 9 deletions app/api/api_v2/entities/withdraw.rb
Expand Up @@ -8,15 +8,7 @@ class Withdraw < Base
expose :fee
expose :txid
expose :destination, using: APIv2::Entities::WithdrawDestination
expose :state do |withdraw|
case withdraw.aasm_state
when :canceled then :cancelled
when :suspect then :suspected
when :rejected, :accepted, :done, :failed then withdraw.aasm_state
when :processing then :processing
else :submitted
end
end
expose :aasm_state, as: :state
expose :created_at, :updated_at, :done_at, format_with: :iso8601
end
end
Expand Down
14 changes: 10 additions & 4 deletions app/api/api_v2/mount.rb
Expand Up @@ -38,9 +38,15 @@ class Mount < Grape::API
mount APIv2::Sessions
mount APIv2::Solvency

base_path = Rails.env.production? ? "#{ENV['URL_SCHEME']}://#{ENV['URL_HOST']}/#{PREFIX}" : PREFIX
add_swagger_documentation base_path: base_path,
mount_path: '/doc/swagger', api_version: 'v2',
hide_documentation_path: true
# The documentation is accessible at http://localhost:3000/swagger?url=/api/v2/swagger
add_swagger_documentation base_path: PREFIX,
mount_path: '/swagger',
api_version: 'v2',
doc_version: '0.0.1',
info: {
title: 'Member API v2',
description: 'Member API is API which can be used by client application like SPA.',
licence: 'MIT',
license_url: 'https://github.com/rubykube/peatio/blob/master/LICENSE.md' }
end
end
3 changes: 1 addition & 2 deletions app/api/api_v2/withdraws.rb
Expand Up @@ -74,7 +74,7 @@ class Withdraws < Grape::API
end
end

desc 'Create withdraw.', scopes: %w[ withdraw ]
desc '[DEPRECATED] Create withdraw.', scopes: %w[ withdraw ]
params do
requires :currency, type: String, values: -> { Currency.codes(bothcase: true) }, desc: -> { "Any supported currency: #{Currency.codes(bothcase: true).join(',')}." }
requires :amount, type: BigDecimal, desc: 'Withdraw amount without fees.'
Expand All @@ -88,7 +88,6 @@ class Withdraws < Grape::API
member_id: current_user.id,
currency: currency
if withdraw.save
withdraw.submit!
present withdraw, with: APIv2::Entities::Withdraw
else
body errors: withdraw.errors.full_messages
Expand Down
99 changes: 99 additions & 0 deletions app/api/management_api_v1/deposits.rb
@@ -0,0 +1,99 @@
module ManagementAPIv1
class Deposits < Grape::API

desc 'Returns deposits as paginated collection.' do
@settings[:scope] = :read_deposits
success ManagementAPIv1::Entities::Deposit
end
params do
optional :uid, type: String, desc: 'The shared user ID.'
optional :currency, type: String, values: -> { Currency.codes(bothcase: true) }, desc: 'The currency code.'
optional :page, type: Integer, default: 1, integer_gt_zero: true, desc: 'The page number (defaults to 1).'
optional :limit, type: Integer, default: 100, range: 1..1000, desc: 'The number of deposits per page (defaults to 100, maximum is 1000).'
optional :state, type: String, values: -> { Deposit::STATES.map(&:to_s) }, desc: 'The state to filter by.'
end
post '/deposits' do
if params[:currency].present?
currency = Currency.find_by!(code: params[:currency])
end

if params[:uid].present?
member = Authentication.find_by!(provider: :barong, uid: params[:uid]).member
end

Deposit
.order(id: :desc)
.tap { |q| q.where!(currency: currency) if currency }
.tap { |q| q.where!(member: member) if member }
.tap { |q| q.where!(aasm_state: params[:state]) if params[:state] }
.page(params[:page])
.per(params[:limit])
.tap { |q| present q, with: ManagementAPIv1::Entities::Deposit }
status 200
end

desc 'Returns deposit by TID.' do
@settings[:scope] = :read_deposits
success ManagementAPIv1::Entities::Deposit
end
params do
requires :tid, type: String, desc: 'The transaction ID.'
end
post '/deposits/get' do
present Deposit.find_by!(params.slice(:tid)), with: ManagementAPIv1::Entities::Deposit
end

desc 'Creates new fiat deposit with state set to «submitted». ' \
'Optionally pass field «state» set to «accepted» if want to load money instantly. ' \
'You can also use PUT /fiat_deposits/:id later to load money or cancel deposit.' do
@settings[:scope] = :write_deposits
success ManagementAPIv1::Entities::Deposit
end
params do
requires :uid, type: String, desc: 'The shared user ID.'
optional :tid, type: String, desc: 'The shared transaction ID. Must not exceed 64 characters. Peatio will generate one automatically unless supplied.'
requires :currency, type: String, values: -> { Currency.fiats.codes(bothcase: true) }, desc: 'The currency code.'
requires :amount, type: BigDecimal, desc: 'The deposit amount.'
optional :state, type: String, desc: 'The state of deposit.', values: %w[accepted]
end
post '/deposits/new' do
member = Authentication.find_by(provider: :barong, uid: params[:uid])&.member
currency = Currency.find_by(code: params[:currency])
account = member&.ac(currency)
data = { member: member, currency: currency, account: account }.merge!(params.slice(:amount, :tid))
deposit = ::Deposits::Fiat.new(data)
if deposit.save
deposit.with_lock do
deposit.accept!
deposit.touch(:done_at)
end if params[:state] == 'accepted'
present deposit, with: ManagementAPIv1::Entities::Deposit
else
body errors: deposit.errors.full_messages
status 422
end
end

desc 'Allows to load money or cancel deposit.' do
@settings[:scope] = :write_deposits
success ManagementAPIv1::Entities::Deposit
end
params do
requires :tid, type: String, desc: 'The shared transaction ID.'
requires :state, type: String, desc: 'The new state to apply.', values: %w[canceled accepted]
end
put '/deposits/state' do
deposit = ::Deposits::Fiat.find_by!(params.slice(:tid))
if deposit.submitted?
deposit.with_lock do
params[:state] == 'canceled' ? deposit.cancel! : deposit.accept!
deposit.touch(:done_at)
end
present deposit, with: ManagementAPIv1::Entities::Deposit
status 200
else
status 422
end
end
end
end
8 changes: 8 additions & 0 deletions app/api/management_api_v1/entities/base.rb
@@ -0,0 +1,8 @@
module ManagementAPIv1
module Entities
class Base < Grape::Entity
format_with(:iso8601) { |t| t.iso8601 if t }
format_with(:decimal) { |d| d.to_s('F') if d }
end
end
end
21 changes: 21 additions & 0 deletions app/api/management_api_v1/entities/deposit.rb
@@ -0,0 +1,21 @@
module ManagementAPIv1
module Entities
class Deposit < Base
expose :tid, documentation: { type: Integer, desc: 'The shared transaction ID.' }
expose(:currency, documentation: { type: String, desc: 'The currency code.' }) { |d| d.currency.code }
expose(:uid, documentation: { type: String, desc: 'The shared user ID.' }) { |w| w.member.authentications.barong.first.uid }
expose(:type, documentation: { type: String, desc: 'The deposit type (fiat or coin).' }) { |d| d.class.name.demodulize.underscore }
expose :amount, documentation: { type: String, desc: 'The deposit amount.' }, format_with: :decimal
states = [
'«submitted» – initial state.',
'«canceled» – deposit has been canceled by outer service.',
'«rejected» – deposit has been rejected by outer service..',
'«accepted» – deposit has been accepted by outer service, money are loaded.'
]
expose :aasm_state, as: :state, documentation: { type: String, desc: 'The deposit state. ' + states.join(' ') }
expose :created_at, format_with: :iso8601, documentation: { type: String, desc: 'The datetime when deposit was created.' }
expose :done_at, as: :completed_at, format_with: :iso8601, documentation: { type: String, desc: 'The datetime when deposit was completed.' }
expose :txid, as: :blockchain_txid, if: -> (d, _) { d.coin? }, documentation: { type: String, desc: 'The transaction ID on the Blockchain (coin only).' }
end
end
end
27 changes: 27 additions & 0 deletions app/api/management_api_v1/entities/withdraw.rb
@@ -0,0 +1,27 @@
module ManagementAPIv1
module Entities
class Withdraw < Base
expose :tid, documentation: { type: Integer, desc: 'The shared transaction ID.' }
expose(:uid, documentation: { type: String, desc: 'The shared user ID.' }) { |w| w.member.authentications.barong.first.uid }
expose(:currency, documentation: { type: String, desc: 'The currency code.' }) { |w| w.currency.code }
expose(:type, documentation: { type: String, desc: 'The withdraw type (fiat or coin).' }) { |w| w.class.name.demodulize.underscore }
expose :amount, documentation: { type: String, desc: 'The withdraw amount excluding fee.' }, format_with: :decimal
expose :fee, documentation: { type: String, desc: 'The exchange fee.' }, format_with: :decimal
expose :rid, documentation: { type: String, desc: 'The beneficiary ID or wallet address on the Blockchain.' }
states = [
'«prepared» – initial state, money are not locked.',
'«submitted» – withdraw has been allowed by outer service for further validation, money are locked.',
'«canceled» – withdraw has been canceled by outer service, money are unlocked.',
'«accepted» – system has validated withdraw and queued it for processing by worker, money are locked.',
'«rejected» – system has validated withdraw and found errors, money are unlocked.',
'«suspected» – system detected suspicious activity, money are unlocked.',
'«processing» – worker is processing withdraw as the current moment, money are locked.',
'«succeed» – worker has successfully processed withdraw, money are subtracted from the account.',
'«failed» – worker has encountered an unhandled error while processing withdraw, money are unlocked.'
]
expose :aasm_state, as: :state, documentation: { type: String, desc: 'The withdraw state. ' + states.join(' ') }
expose :created_at, format_with: :iso8601, documentation: { type: String, desc: 'The datetime when withdraw was created.' }
expose :txid, as: :blockchain_txid, documentation: { type: String, desc: 'The transaction ID on the Blockchain (coin only).' }, if: -> (w, _) { w.coin? }
end
end
end
16 changes: 16 additions & 0 deletions app/api/management_api_v1/entities/withdraw_destination.rb
@@ -0,0 +1,16 @@
module ManagementAPIv1
module Entities
class WithdrawDestination < Base
expose :id, documentation: { type: Integer, desc: 'The withdraw destination ID.' }
expose(:currency, documentation: { type: String, desc: 'The currency code.' }) { |w| w.currency.code }
expose(:uid, documentation: { type: String, desc: 'The shared user ID.' }) { |w| w.member.authentications.barong.first.uid }
expose :label, documentation: { type: String, desc: 'The associated label.' }
expose(:type, documentation: { type: String, desc: 'The withdraw destination type (fiat or coin).' }) { |w| w.class.name.demodulize.underscore }
%w[ fiat coin ].each do |type|
"withdraw_destination/#{type}".camelize.constantize.fields.each do |field, desc|
expose(field, documentation: { type: String, desc: desc }, if: -> (w, _) { w.respond_to?(field) }) { |w| w.public_send(field) }
end
end
end
end
end
9 changes: 9 additions & 0 deletions app/api/management_api_v1/exceptions/authentication.rb
@@ -0,0 +1,9 @@
module ManagementAPIv1
module Exceptions
class Authentication < Base
def status
@options.fetch(:status, 401)
end
end
end
end
18 changes: 18 additions & 0 deletions app/api/management_api_v1/exceptions/base.rb
@@ -0,0 +1,18 @@
module ManagementAPIv1
module Exceptions
class Base < StandardError
def initialize(message:, **options)
@options = options
super(message)
end

def headers
@options.fetch(:headers, {})
end

def status
@options.fetch(:status)
end
end
end
end
5 changes: 5 additions & 0 deletions app/api/management_api_v1/helpers.rb
@@ -0,0 +1,5 @@
module ManagementAPIv1
module Helpers

end
end

0 comments on commit 859031d

Please sign in to comment.