Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add management API v1 #740

Merged
merged 57 commits into from Apr 4, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
10a2cc4
Remove states: submitting, checked, warning from deposit
yivo Mar 24, 2018
9e08a49
Add auth by JWT
yivo Mar 26, 2018
677b5d6
Handle param validation
yivo Mar 26, 2018
84617c6
Add JWT multisig
yivo Mar 26, 2018
440460b
Add docs
yivo Mar 26, 2018
b0c9c9a
Add JWT verification stuff
yivo Mar 26, 2018
9dd4fa3
Add API config validation
yivo Mar 26, 2018
74aeea4
Add swagger doc for API
yivo Mar 27, 2018
54b4db9
Replace public? => private?
yivo Mar 27, 2018
7c9dbbc
Bugfix
yivo Mar 27, 2018
dbf29c4
Add documentation
yivo Mar 27, 2018
890b1e0
Update config
yivo Mar 27, 2018
16e9559
cancelled -> canceled
yivo Mar 28, 2018
eb1d396
Remove state «submitting»
yivo Mar 28, 2018
e4a7c14
Add withdraws API
yivo Mar 28, 2018
5492881
Update deposit API
yivo Mar 29, 2018
0ee5046
Update withdraws API
yivo Mar 29, 2018
62d453a
Fix specs
yivo Mar 29, 2018
9e8afda
Update API docs
yivo Mar 29, 2018
b594010
Specs for entities
yivo Mar 30, 2018
839afe3
Improvements for factories
yivo Mar 31, 2018
7565293
Update authenticator and management api config
yivo Mar 31, 2018
4e81da7
Add scopes for APIs
yivo Mar 31, 2018
5e3a99e
Rename withdraw factories, add minimal specs for deposit & withdraw APIs
yivo Mar 31, 2018
80bb2f1
Fixes
yivo Mar 31, 2018
04f19e5
Bugfix for auth middleware
yivo Mar 31, 2018
95abd98
Fixes for deposit API
yivo Mar 31, 2018
28a53bb
Deposit API specs
yivo Mar 31, 2018
eecbab1
Update gems
yivo Apr 2, 2018
d2064a7
Add tools API
yivo Apr 2, 2018
95a453b
Add specs for JWT middleware
yivo Apr 2, 2018
a465b3c
Fix specs
yivo Apr 2, 2018
1590d91
Add specs for withdraw API
yivo Apr 2, 2018
4cfecb7
Update specs for withdraw
yivo Apr 2, 2018
304050d
Update deposit API specs
yivo Apr 2, 2018
0397087
Update deposit API specs
yivo Apr 2, 2018
dc88df8
Update configs
yivo Apr 2, 2018
e50e077
Output member UID
yivo Apr 2, 2018
e7226d5
Set jwt-multisig to 1.0.0
yivo Apr 3, 2018
37be825
Annotate models
yivo Apr 3, 2018
30cdaa6
Use API doc version similar to Peatio
yivo Apr 3, 2018
eeca627
Update config
yivo Apr 3, 2018
10abd98
Add missing state
yivo Apr 3, 2018
3dbfe8c
Replace member with uid in withdraw dest
yivo Apr 3, 2018
0f5886f
member -> uid
yivo Apr 3, 2018
e9f7967
Add TID to deposits
yivo Apr 3, 2018
feb9eff
Add TID to withdraw
yivo Apr 3, 2018
dcb527a
Add BID
yivo Apr 3, 2018
9ef7c3f
Annotate models
yivo Apr 3, 2018
7b33efc
Replace BID with RID. Add API docs.
yivo Apr 3, 2018
0983c37
Fix
yivo Apr 3, 2018
6a473ea
Fix
yivo Apr 3, 2018
15848eb
Fix
yivo Apr 3, 2018
68c9ca2
Missing translations
yivo Apr 3, 2018
b01e483
Mark API as deprecated
yivo Apr 3, 2018
ac20ab5
Change created -> prepared. Add extra migration.
yivo Apr 3, 2018
7956598
Update docs
yivo Apr 4, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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