Initial commit
This commit is contained in:
9
app/api/mount.rb
Normal file
9
app/api/mount.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
module API
|
||||
class Mount < Grape::API
|
||||
PREFIX = '/api'
|
||||
|
||||
cascade false
|
||||
|
||||
mount API::V2::Mount => API::V2::Mount::API_VERSION
|
||||
end
|
||||
end
|
||||
75
app/api/v2/account/balances.rb
Normal file
75
app/api/v2/account/balances.rb
Normal file
@@ -0,0 +1,75 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Balances < Grape::API
|
||||
|
||||
helpers ::API::V2::ParamHelpers
|
||||
|
||||
# TODO: Add failures.
|
||||
# TODO: Move desc hash options to block once issues are resolved.
|
||||
# https://github.com/ruby-grape/grape/issues/1789
|
||||
# https://github.com/ruby-grape/grape-swagger/issues/705
|
||||
desc 'Get list of user accounts',
|
||||
is_array: true,
|
||||
success: API::V2::Entities::Account
|
||||
params do
|
||||
use :pagination
|
||||
optional :nonzero,
|
||||
type: { value: Boolean, message: 'account.balances.invalid_nonzero' },
|
||||
default: false,
|
||||
desc: 'Filter non zero balances.'
|
||||
optional :search, type: JSON, default: {} do
|
||||
optional :currency_code,
|
||||
as: :code,
|
||||
type: String
|
||||
optional :currency_name,
|
||||
as: :name,
|
||||
type: String
|
||||
end
|
||||
end
|
||||
get '/balances' do
|
||||
user_authorize! :read, ::Operations::Account
|
||||
|
||||
search_params = params[:search]
|
||||
.slice(:code, :name)
|
||||
.transform_keys {|k| "#{k}_cont"}
|
||||
.merge(m: 'or')
|
||||
|
||||
accounts = ::Currency.visible.ransack(search_params).result.each_with_object([]) do |c, result|
|
||||
account = ::Account.find_by(currency: c, member: current_user)
|
||||
if account.present?
|
||||
next if params[:nonzero].present? && account.amount.zero? && account.locked.zero?
|
||||
|
||||
result << account
|
||||
elsif account.blank? && params[:nonzero].blank?
|
||||
result << ::Account.new(currency: c, member: current_user)
|
||||
end
|
||||
end
|
||||
|
||||
present paginate(accounts),
|
||||
with: Entities::Account, current_user: current_user
|
||||
end
|
||||
|
||||
desc 'Get user account by currency' do
|
||||
success API::V2::Entities::Account
|
||||
# TODO: Add failures.
|
||||
end
|
||||
params do
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.pluck(:id) }, message: 'account.currency.doesnt_exist' },
|
||||
desc: 'The currency code.'
|
||||
end
|
||||
get '/balances/:currency', requirements: { currency: /[\w\.\-]+/ } do
|
||||
user_authorize! :read, ::Operations::Account
|
||||
|
||||
present current_user.accounts.visible.find_by!(currency_id: params[:currency]),
|
||||
with: API::V2::Entities::Account
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
205
app/api/v2/account/beneficiaries.rb
Normal file
205
app/api/v2/account/beneficiaries.rb
Normal file
@@ -0,0 +1,205 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Beneficiaries < Grape::API
|
||||
|
||||
before { withdraws_must_be_permitted! }
|
||||
|
||||
namespace :beneficiaries do
|
||||
desc 'Get list of user beneficiaries',
|
||||
is_array: true,
|
||||
success: API::V2::Entities::Beneficiary
|
||||
|
||||
params do
|
||||
optional :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist' },
|
||||
as: :currency_id,
|
||||
desc: 'Beneficiary currency code.'
|
||||
optional :state,
|
||||
type: String,
|
||||
values: { value: -> { ::Beneficiary::STATES_AVAILABLE_FOR_MEMBER.map(&:to_s) }, message: 'account.beneficiary.invalid_state'},
|
||||
desc: 'Defines either beneficiary active - user can use it to withdraw money'\
|
||||
'or pending - requires beneficiary activation with pin.'
|
||||
|
||||
end
|
||||
get do
|
||||
user_authorize! :read, ::Beneficiary
|
||||
|
||||
current_user
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.tap do |q|
|
||||
q.where!(currency_id: params[:currency_id]) if params[:currency_id].present?
|
||||
end
|
||||
.tap do |q|
|
||||
q.where!(state: params[:state]) if params[:state].present?
|
||||
end
|
||||
.yield_self do |b|
|
||||
present paginate(b), with: API::V2::Entities::Beneficiary
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get beneficiary by ID',
|
||||
success: API::V2::Entities::Beneficiary
|
||||
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'account.beneficiary.non_integer_id' },
|
||||
desc: 'Beneficiary Identifier in Database'
|
||||
end
|
||||
get ':id' do
|
||||
user_authorize! :read, ::Beneficiary
|
||||
|
||||
current_user
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.find_by!(id: params[:id])
|
||||
.yield_self { |b| present b, with: API::V2::Entities::Beneficiary }
|
||||
end
|
||||
|
||||
desc 'Create new beneficiary',
|
||||
success: API::V2::Entities::Beneficiary
|
||||
|
||||
params do
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist' },
|
||||
as: :currency_id,
|
||||
desc: 'Beneficiary currency code.'
|
||||
requires :name,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
values: { value: ->(v) { v.present? && v.size <= 64 }, message: 'account.beneficiary.too_long_name' },
|
||||
desc: 'Human rememberable name which refer beneficiary.'
|
||||
optional :description,
|
||||
type: String,
|
||||
values: { value: ->(v) { v.size <= 255 }, message: 'account.beneficiary.too_long_description' },
|
||||
desc: 'Human rememberable name which refer beneficiary.'
|
||||
requires :data,
|
||||
type: { value: JSON, message: 'account.beneficiary.non_json_data' },
|
||||
allow_blank: false,
|
||||
desc: 'Beneficiary data in JSON format'
|
||||
end
|
||||
post do
|
||||
user_authorize! :create, ::Beneficiary
|
||||
|
||||
declared_params = declared(params)
|
||||
|
||||
currency = Currency.find_by!(id: params[:currency_id])
|
||||
|
||||
if !currency.withdrawal_enabled?
|
||||
error!({ errors: ['account.currency.withdrawal_disabled'] }, 422)
|
||||
elsif currency.coin? && declared_params.dig(:data, :address).blank?
|
||||
error!({ errors: ['account.beneficiary.missing_address_in_data'] }, 422)
|
||||
elsif currency.fiat? && declared_params.dig(:data, :full_name).blank?
|
||||
error!({ errors: ['account.beneficiary.missing_full_name_in_data'] }, 422)
|
||||
end
|
||||
|
||||
# Since data is stored in MySQL JSON format we iterate through all
|
||||
# beneficiaries one by one to detect duplicated address.
|
||||
if currency.coin? &&
|
||||
current_user
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.where(currency: currency)
|
||||
.any? { |b| b.data['address'] == declared_params.dig(:data, :address) }
|
||||
error!({ errors: ['account.beneficiary.duplicate_address'] }, 422)
|
||||
end
|
||||
|
||||
present current_user
|
||||
.beneficiaries
|
||||
.create!(declared_params),
|
||||
with: API::V2::Entities::Beneficiary
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
report_exception(e)
|
||||
error!({ errors: ['account.beneficiary.failed_to_create'] }, 422)
|
||||
end
|
||||
|
||||
desc 'Resend beneficiary pin'
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'account.beneficiary.non_integer_id' },
|
||||
desc: 'Beneficiary Identifier in Database'
|
||||
end
|
||||
patch ':id/resend_pin' do
|
||||
user_authorize! :update, ::Beneficiary
|
||||
|
||||
beneficiary = current_user
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.find_by!(id: params[:id])
|
||||
|
||||
unless beneficiary.pending?
|
||||
error!({ errors: ['account.beneficiary.cant_resend'] }, 422)
|
||||
end
|
||||
|
||||
if Time.now - beneficiary.sent_at < 60
|
||||
error!({ errors: ['account.beneficiary.cant_resend_within_1_minute'], sent_at: beneficiary.sent_at.iso8601 }, 422)
|
||||
end
|
||||
|
||||
beneficiary.regenerate_pin!
|
||||
status 204
|
||||
end
|
||||
|
||||
|
||||
desc 'Activates beneficiary with pin',
|
||||
success: API::V2::Entities::Beneficiary
|
||||
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'account.beneficiary.non_integer_id' },
|
||||
desc: 'Beneficiary Identifier in Database'
|
||||
|
||||
requires :pin,
|
||||
type: { value: Integer, message: 'account.beneficiary.non_integer_pin' },
|
||||
desc: 'Pin code for beneficiary activation'
|
||||
end
|
||||
patch ':id/activate' do
|
||||
user_authorize! :update, ::Beneficiary
|
||||
|
||||
beneficiary = current_user
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.find_by!(id: params[:id])
|
||||
|
||||
unless beneficiary.pending?
|
||||
error!({ errors: ['account.beneficiary.cant_activate'] }, 422)
|
||||
end
|
||||
|
||||
if beneficiary.activate!(params[:pin])
|
||||
present beneficiary, with: API::V2::Entities::Beneficiary
|
||||
else
|
||||
error!({ errors: ['account.beneficiary.invalid_pin'] }, 422)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Delete beneficiary'
|
||||
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'account.beneficiary.non_integer_id' },
|
||||
desc: 'Beneficiary Identifier in Database'
|
||||
end
|
||||
delete ':id' do
|
||||
user_authorize! :destroy, ::Beneficiary
|
||||
|
||||
beneficiary = current_user
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.find_by!(id: params[:id])
|
||||
|
||||
if beneficiary.archive!
|
||||
body false
|
||||
else
|
||||
error!({ errors: ['account.beneficiary.cant_delete'] }, 422)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
30
app/api/v2/account/bonuses.rb
Normal file
30
app/api/v2/account/bonuses.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Bonuses < Grape::API
|
||||
desc 'Get bonus simple data of a user',
|
||||
is_array: true
|
||||
get '/bonus' do
|
||||
user_bonuses = Bonus.where(bonus_member: current_user)
|
||||
all = user_bonuses.inject(0){ |sum, x| sum + x.amount }
|
||||
h24 = user_bonuses.h24.inject(0){ |sum, x| sum + x.amount }
|
||||
number = Member.where(referral_uid: current_user.uid).count
|
||||
|
||||
{ 'h24': h24, 'all': all, 'number': number }
|
||||
|
||||
end
|
||||
|
||||
desc 'Get bonus report',
|
||||
is_array: true
|
||||
get '/bonus/report' do
|
||||
bonus = Bonus.where(bonus_member: current_user)
|
||||
present paginate(bonus), with: Entities::Bonus
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
207
app/api/v2/account/deposits.rb
Normal file
207
app/api/v2/account/deposits.rb
Normal file
@@ -0,0 +1,207 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../validations'
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Deposits < Grape::API
|
||||
|
||||
before { deposits_must_be_permitted! }
|
||||
|
||||
desc 'Get your deposits history.',
|
||||
is_array: true,
|
||||
success: API::V2::Entities::Deposit
|
||||
|
||||
params do
|
||||
optional :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist' },
|
||||
desc: 'Currency code'
|
||||
optional :state,
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Deposit.aasm.states.map(&:name).map(&:to_s)).blank? }, message: 'account.deposit.invalid_state' },
|
||||
desc: 'Filter deposits by states.'
|
||||
optional :txid,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Deposit transaction id.'
|
||||
optional :time_from,
|
||||
allow_blank: { value: false, message: 'account.deposit.empty_time_from' },
|
||||
type: { value: Integer, message: 'account.deposit.non_integer_time_from' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'
|
||||
optional :time_to,
|
||||
type: { value: Integer, message: 'account.deposit.non_integer_time_to' },
|
||||
allow_blank: { value: false, message: 'account.deposit.empty_time_to' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'
|
||||
optional :limit,
|
||||
type: { value: Integer, message: 'account.deposit.non_integer_limit' },
|
||||
values: { value: 1..100, message: 'account.deposit.invalid_limit' },
|
||||
default: 100,
|
||||
desc: "Number of deposits per page (defaults to 100, maximum is 100)."
|
||||
optional :page,
|
||||
type: { value: Integer, message: 'account.deposit.non_integer_page' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'account.deposit.non_positive_page'},
|
||||
default: 1,
|
||||
desc: 'Page number (defaults to 1).'
|
||||
end
|
||||
get "/deposits" do
|
||||
user_authorize! :read, ::Deposit
|
||||
|
||||
currency = Currency.find(params[:currency]) if params[:currency].present?
|
||||
|
||||
current_user.deposits.order(id: :desc)
|
||||
.tap { |q| q.where!(currency: currency) if currency }
|
||||
.tap { |q| q.where!(txid: params[:txid]) if params[:txid] }
|
||||
.tap { |q| q.where!(aasm_state: params[:state]) if params[:state] }
|
||||
.tap { |q| q.where!('updated_at >= ?', Time.at(params[:time_from])) if params[:time_from].present? }
|
||||
.tap { |q| q.where!('updated_at <= ?', Time.at(params[:time_to])) if params[:time_to].present? }
|
||||
.tap { |q| present paginate(q), with: API::V2::Entities::Deposit }
|
||||
end
|
||||
|
||||
desc 'Get details of specific deposit.' do
|
||||
success API::V2::Entities::Deposit
|
||||
end
|
||||
params do
|
||||
requires :txid,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: "Deposit transaction id"
|
||||
end
|
||||
get "/deposits/:txid" do
|
||||
user_authorize! :read, ::Deposit
|
||||
|
||||
deposit = current_user.deposits.find_by!(txid: params[:txid])
|
||||
present deposit, with: API::V2::Entities::Deposit
|
||||
end
|
||||
|
||||
desc 'Returns deposit address for account you want to deposit to by currency. ' \
|
||||
'The address may be blank because address generation process is still in progress. ' \
|
||||
'If this case you should try again later.',
|
||||
success: API::V2::Entities::Deposit
|
||||
params do
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.coins.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist'},
|
||||
desc: 'The account you want to deposit to.'
|
||||
given :currency do
|
||||
optional :address_format,
|
||||
type: String,
|
||||
values: { value: -> { %w[legacy cash] }, message: 'account.deposit_address.invalid_address_format' },
|
||||
validate_currency_address_format: { value: true, prefix: 'account.deposit_address' },
|
||||
desc: 'Address format legacy/cash'
|
||||
end
|
||||
end
|
||||
get '/deposit_address/:currency', requirements: { currency: /[\w\.\-]+/ } do
|
||||
user_authorize! :read, ::PaymentAddress
|
||||
|
||||
currency = Currency.find(params[:currency])
|
||||
|
||||
unless currency.deposit_enabled?
|
||||
error!({ errors: ['account.currency.deposit_disabled'] }, 422)
|
||||
end
|
||||
|
||||
wallet = Wallet.deposit_wallet(currency.id)
|
||||
|
||||
unless wallet.present?
|
||||
error!({ errors: ['account.wallet.not_found'] }, 422)
|
||||
end
|
||||
|
||||
payment_address = current_user.payment_address(wallet.id)
|
||||
present payment_address, with: API::V2::Entities::PaymentAddress, address_format: params[:address_format]
|
||||
end
|
||||
############
|
||||
|
||||
desc 'Create fiat deposits'
|
||||
params do
|
||||
requires :amount,
|
||||
type: { value: BigDecimal, message: 'deposit.fiat.non_decimal_amount' },
|
||||
values: { value: ->(v) { v.try(:positive?) }, message: 'deposit.fiat.non_positive_amount' },
|
||||
desc: 'The amount to deposit fiat.'
|
||||
requires :callback_url,
|
||||
regexp: { value: URI::regexp, message: 'callback_url.invalid' },
|
||||
desc: -> { 'callback_url payment url' }
|
||||
requires :currency,
|
||||
type: String,
|
||||
default: 'irt',
|
||||
desc: -> { 'currency' }
|
||||
requires :card,
|
||||
type: String,
|
||||
desc: 'card bank number'
|
||||
optional :factorNumber,
|
||||
type: String,
|
||||
allow_blank: true,
|
||||
desc: 'factor number'
|
||||
optional :description,
|
||||
type: String,
|
||||
allow_blank: true,
|
||||
desc: 'description'
|
||||
|
||||
end
|
||||
post '/deposits/fiat' do
|
||||
user_authorize! :create, ::Deposit
|
||||
error!({ errors: ['account.deposit.use_valid_card'] }, 403) if current_user.cards.exclude?(params[:card].to_s)
|
||||
|
||||
deposit_data = { member: current_user, currency: Currency.find(params['currency']), aasm_state: 'canceled',
|
||||
address: params['card'], amount: params['amount'], transfer_type: :fiat }
|
||||
deposit = ::Deposits::Fiat.new(deposit_data)
|
||||
|
||||
error!({ errors: deposit.errors.full_messages }, 422) if deposit.errors.any?
|
||||
|
||||
data = { amount: (params[:amount] * 10), callback_url: params[:callback_url],
|
||||
description: params[:description], valid_card_number: params[:card] }
|
||||
response = VandarService::generate_token(data)
|
||||
if response.dig('status').to_i.positive?
|
||||
deposit.tap { |d| d.tid = response.dig('token') }.save!
|
||||
present(response: response.dig('token'))
|
||||
else
|
||||
present(response: response)
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Confirm fiat deposits'
|
||||
params do
|
||||
requires :token,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'payment token'
|
||||
end
|
||||
post '/deposits/confirm' do
|
||||
deposit = ::Deposits::Fiat.find_by(tid: params[:token])
|
||||
error!({ errors: ['corresponding.deposit.record.not.found'] }, 403) unless deposit.present?
|
||||
|
||||
# because vandar just response one
|
||||
if deposit.txid.present?
|
||||
db_response = {
|
||||
status: 1,
|
||||
amount: deposit.amount,
|
||||
wage: deposit.fee,
|
||||
cardNumber: deposit.address,
|
||||
transId: deposit.txid
|
||||
}
|
||||
return present(response: db_response)
|
||||
end
|
||||
|
||||
# first step: check everything is ok
|
||||
response = ::VandarService.transaction(params[:token])
|
||||
if response.dig('status').to_i.positive? && response.dig('trackingCode').present?
|
||||
if response['cardNumber'].last(4).to_i == deposit.address.last(4).to_i
|
||||
response = VandarService.verify(params[:token])
|
||||
if response.dig('status').to_i.positive?
|
||||
deposit.update(txid: response['transId'], fee: response['wage'].to_f / 10, aasm_state: 'submitted')
|
||||
deposit.charge!
|
||||
end
|
||||
end
|
||||
# TODO we should see the response and check it for rial and toman confirmation
|
||||
present(response: response)
|
||||
else
|
||||
present(response: response)
|
||||
status 422
|
||||
end
|
||||
# second step: verify payment
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
90
app/api/v2/account/internal_transfers.rb
Normal file
90
app/api/v2/account/internal_transfers.rb
Normal file
@@ -0,0 +1,90 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class InternalTransfers < Grape::API
|
||||
namespace :internal_transfers do
|
||||
desc 'List your internal transfers as paginated collection.',
|
||||
is_array: true,
|
||||
success: API::V2::Entities::InternalTransfer
|
||||
params do
|
||||
optional :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist' },
|
||||
desc: 'Currency code.'
|
||||
optional :state, type: String, desc: 'The state to filter by.'
|
||||
optional :sender
|
||||
end
|
||||
|
||||
get do
|
||||
user_authorize! :read, ::InternalTransfer
|
||||
|
||||
ransack_params = ::API::V2::Admin::Helpers::RansackBuilder.new(params)
|
||||
.eq(:state)
|
||||
.translate(currency: :currency_id)
|
||||
.merge(g: [
|
||||
{ sender_id_eq: current_user.id, receiver_id_eq: current_user.id, m: 'or' }
|
||||
]).build
|
||||
search = InternalTransfer.ransack(ransack_params)
|
||||
.result
|
||||
.order('id desc')
|
||||
|
||||
present paginate(search), with: API::V2::Entities::InternalTransfer, current_user: current_user
|
||||
end
|
||||
desc 'Creates internal transfer.'
|
||||
params do
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist' },
|
||||
desc: 'The currency code.'
|
||||
requires :amount,
|
||||
type: { value: BigDecimal, message: 'account.internal_transfer.non_decimal_amount' },
|
||||
values: { value: ->(v) { v.try(:positive?) }, message: 'account.internal_transfer.non_positive_amount' },
|
||||
desc: 'The amount to transfer.'
|
||||
requires :otp,
|
||||
type: { value: Integer, message: 'account.internal_transfer.non_integer_otp' },
|
||||
allow_blank: false,
|
||||
desc: 'OTP to perform action'
|
||||
requires :username_or_uid,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Receiver uid or username.'
|
||||
end
|
||||
post do
|
||||
receiver = Member.find_by_username_or_uid(params[:username_or_uid])
|
||||
|
||||
error!({ errors: ['account.internal_transfer.receiver_not_found'] }, 422) if receiver.nil?
|
||||
currency = Currency.find(params[:currency])
|
||||
|
||||
unless Vault::TOTP.validate?(current_user.uid, params[:otp])
|
||||
error!({ errors: ['account.internal_transfer.invalid_otp'] }, 422)
|
||||
end
|
||||
|
||||
if current_user.get_account(currency).balance < params[:amount]
|
||||
error!({ errors: ['account.internal_transfer.insufficient_balance'] }, 422)
|
||||
end
|
||||
|
||||
if current_user == receiver
|
||||
error!({ errors: ['account.internal_transfer.can_not_tranfer_to_yourself'] }, 422)
|
||||
end
|
||||
|
||||
internal_transfer = ::InternalTransfer.new(
|
||||
currency: currency,
|
||||
sender: current_user,
|
||||
receiver: receiver,
|
||||
amount: params[:amount]
|
||||
)
|
||||
if internal_transfer.save
|
||||
present internal_transfer, with: API::V2::Entities::InternalTransfer
|
||||
status 201
|
||||
else
|
||||
body errors: internal_transfer.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
28
app/api/v2/account/levels.rb
Normal file
28
app/api/v2/account/levels.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Levels < Grape::API
|
||||
|
||||
desc 'Get current level and next level informations',
|
||||
is_array: true
|
||||
get '/levels' do
|
||||
current_level = current_user.group
|
||||
next_level = current_user.next_group
|
||||
current_level_min_max = Member.min_max_group(current_level)
|
||||
next_level_min_max = Member.min_max_group(next_level)
|
||||
|
||||
current_level_fee = TradingFee.for(group: current_level, market_id: 'ANY')
|
||||
next_level_fee = TradingFee.for(group: next_level, market_id: 'ANY')
|
||||
|
||||
{ 'current': { 'name': current_level, 'max': current_level_min_max[1], 'min': current_level_min_max[0],
|
||||
'maker_fee': current_level_fee.maker, 'taker_fee': current_level_fee.taker },
|
||||
'next': { 'name': next_level, 'max': next_level_min_max[1], 'min': next_level_min_max[0],
|
||||
'maker_fee': next_level_fee.maker, 'taker_fee': next_level_fee.taker }}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
21
app/api/v2/account/mount.rb
Normal file
21
app/api/v2/account/mount.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
module API::V2
|
||||
module Account
|
||||
class Mount < Grape::API
|
||||
|
||||
before { authenticate! }
|
||||
before { set_ets_context! }
|
||||
|
||||
mount Account::Balances
|
||||
mount Account::Deposits
|
||||
mount Account::Beneficiaries
|
||||
mount Account::Withdraws
|
||||
mount Account::Transactions
|
||||
mount Account::Stats
|
||||
mount Account::InternalTransfers
|
||||
mount Account::Otp
|
||||
mount Account::Treasury
|
||||
mount Account::Levels
|
||||
mount Account::Bonuses
|
||||
end
|
||||
end
|
||||
end
|
||||
78
app/api/v2/account/otp.rb
Normal file
78
app/api/v2/account/otp.rb
Normal file
@@ -0,0 +1,78 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Otp < Grape::API
|
||||
helpers API::V2::Account::Utils
|
||||
resource :otp do
|
||||
# for example
|
||||
# for withdraw with id 3
|
||||
# we create a cache with name -> withdraw_3_2fa
|
||||
#
|
||||
# # for example
|
||||
# # for deposit with id 6
|
||||
# # we create a cache with name -> deposit_6_2fa
|
||||
desc 'validate user otp'
|
||||
params do
|
||||
requires :otp,
|
||||
type: { value: Integer, message: 'integer_otp' },
|
||||
allow_blank: false,
|
||||
desc: 'OTP to perform action'
|
||||
optional :action,
|
||||
type: String,
|
||||
desc: 'action for top'
|
||||
# optional :id,
|
||||
# type: String,
|
||||
# desc: 'if for top action'
|
||||
end
|
||||
post do
|
||||
error!({ errors: ['account.not_active_otp'] }, 422) unless current_user.otp
|
||||
|
||||
error!({ errors: ['account.invalid_otp'] }, 422) unless Vault::TOTP.validate?(current_user.uid, params[:otp])
|
||||
|
||||
action = 'validate'
|
||||
# id = '0'
|
||||
action = params[:action] if params[:action].present?
|
||||
# id = params[:id] if params[:id].present?
|
||||
current_user.write_cache("#{action}_2fa", 1, 60)
|
||||
status 200
|
||||
end
|
||||
|
||||
desc 'Send confirmations code',
|
||||
success: { code: 201, message: 'Generated verification code' },
|
||||
failure: [
|
||||
{ code: 400, message: 'Required params are missing' },
|
||||
{ code: 422, message: 'Validation errors' }
|
||||
]
|
||||
params do
|
||||
requires :data,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Account email or Telephone number'
|
||||
requires :action,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'for what need auth code'
|
||||
optional :channel,
|
||||
type: String,
|
||||
allow_blank: true,
|
||||
default: 'email',
|
||||
desc: 'channel that send in'
|
||||
optional :captcha_response,
|
||||
types: [String, Hash],
|
||||
desc: 'Response from captcha widget'
|
||||
end
|
||||
post '/resend' do
|
||||
current_user = Member.find_by(email: params[:data])
|
||||
return status 201 if current_user.nil?
|
||||
|
||||
publish_confirmation_code(current_user, params[:action])
|
||||
status 201
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
36
app/api/v2/account/stats.rb
Normal file
36
app/api/v2/account/stats.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Stats < Grape::API
|
||||
desc 'Get assets pnl calculated into one currency'
|
||||
params do
|
||||
optional :pnl_currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'pnl.currency.doesnt_exist' },
|
||||
desc: 'Currency code in which the PnL is calculated'
|
||||
end
|
||||
get '/stats/pnl' do
|
||||
user_authorize! :read, ::StatsMemberPnl
|
||||
|
||||
query = 'SELECT pnl_currency_id, currency_id, total_credit, total_debit, total_credit_value, total_debit_value, ' \
|
||||
'total_credit_value / NULLIF(total_credit, 0) "average_buy_price", ' \
|
||||
'total_debit_value / NULLIF(total_debit, 0) "average_sell_price", ' \
|
||||
'average_balance_price, total_balance_value ' \
|
||||
'FROM stats_member_pnl WHERE member_id = ?'
|
||||
conditions = [current_user.id]
|
||||
|
||||
if params[:pnl_currency].present?
|
||||
query += ' AND pnl_currency_id = ?'
|
||||
conditions << params[:pnl_currency]
|
||||
end
|
||||
|
||||
squery = ActiveRecord::Base.sanitize_sql_for_conditions([query] + conditions)
|
||||
result = ActiveRecord::Base.connection.exec_query(squery).to_hash
|
||||
present result.each(&:symbolize_keys!), with: API::V2::Entities::Pnl
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
109
app/api/v2/account/transactions.rb
Normal file
109
app/api/v2/account/transactions.rb
Normal file
@@ -0,0 +1,109 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../validations'
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Transactions < Grape::API
|
||||
|
||||
before { deposits_must_be_permitted! }
|
||||
before { withdraws_must_be_permitted! }
|
||||
|
||||
desc 'Get your transactions history.',
|
||||
is_array: true
|
||||
|
||||
params do
|
||||
optional :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.transactions.currency_doesnt_exist' },
|
||||
desc: 'Currency code'
|
||||
|
||||
optional :order_by,
|
||||
type: String,
|
||||
values: { value: %w(asc desc), message: 'account.transactions.order_by_invalid' },
|
||||
default: 'desc',
|
||||
desc: 'Sorting order'
|
||||
|
||||
optional :time_from,
|
||||
allow_blank: { value: false, message: 'account.transactions.empty_time_from' },
|
||||
type: { value: Integer, message: 'account.transactions.non_integer_time_from' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'
|
||||
|
||||
optional :time_to,
|
||||
type: { value: Integer, message: 'account.transactions.non_integer_time_to' },
|
||||
allow_blank: { value: false, message: 'account.transactions.empty_time_to' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'
|
||||
|
||||
optional :deposit_state,
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Deposit.aasm.states.map(&:name).map(&:to_s)).blank? }, message: 'account.transactions.invalid_deposit_state' },
|
||||
desc: 'Filter deposits by states.',
|
||||
default: []
|
||||
|
||||
optional :withdraw_state,
|
||||
values: { value: ->(v) { (Array.wrap(v) - Withdraw::STATES.map(&:to_s)).blank? }, message: 'account.transactions.invalid_withdraw_state' },
|
||||
desc: 'Filter withdraws by states.',
|
||||
default: []
|
||||
|
||||
optional :txid,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Transaction id.'
|
||||
|
||||
optional :limit,
|
||||
type: { value: Integer, message: 'account.transactions.non_integer_limit' },
|
||||
values: { value: 1..1000, message: 'account.transactions.invalid_limit' },
|
||||
default: 100,
|
||||
desc: 'Limit the number of returned transactions. Default to 100.'
|
||||
|
||||
optional :page,
|
||||
type: { value: Integer, message: 'account.transactions.non_integer_page' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'account.transactions.non_positive_page'},
|
||||
allow_blank: false,
|
||||
default: 1,
|
||||
desc: 'Specify the page of paginated results.'
|
||||
|
||||
end
|
||||
get "/transactions" do
|
||||
user_authorize! :read, ::Withdraw
|
||||
user_authorize! :read, ::Deposit
|
||||
|
||||
deposit_state = params[:deposit_state]&.split(/\W+/)&.join(',')
|
||||
withdraw_state = params[:withdraw_state]&.split(/\W+/)&.join(',')
|
||||
|
||||
deposit_sql = "(SELECT d.id, currency_id, amount, fee, address, aasm_state, NULL AS note, txid, d.created_at, d.updated_at, d.type, b.height - block_number AS confirmations FROM deposits d " \
|
||||
"INNER JOIN currencies c ON c.id=d.currency_id LEFT JOIN blockchains b ON b.key=c.blockchain_key WHERE member_id=#{current_user.id} "
|
||||
if params[:deposit_state].present?
|
||||
deposit_sql += if Rails.configuration.database_adapter.downcase == 'PostgreSQL'.downcase
|
||||
"and aasm_state = any(string_to_array('#{deposit_state}',','))"
|
||||
else
|
||||
"and FIND_IN_SET(aasm_state, '#{deposit_state}')"
|
||||
end
|
||||
end
|
||||
|
||||
withdraw_sql = "SELECT w.id, currency_id, amount, fee, rid, aasm_state, note, txid, w.created_at, w.updated_at, w.type, b.height - block_number AS confirmations FROM withdraws w " \
|
||||
"INNER JOIN currencies c ON c.id=w.currency_id LEFT JOIN blockchains b ON b.key=c.blockchain_key WHERE member_id=#{current_user.id} "
|
||||
if params[:withdraw_state].present?
|
||||
withdraw_sql += if Rails.configuration.database_adapter.downcase == 'PostgreSQL'.downcase
|
||||
"and aasm_state = any(string_to_array('#{withdraw_state}',','))"
|
||||
else
|
||||
"and FIND_IN_SET(aasm_state, '#{withdraw_state}')"
|
||||
end
|
||||
end
|
||||
|
||||
sql = "SELECT * FROM " + deposit_sql + "UNION " + withdraw_sql + ") AS transactions ORDER BY updated_at #{params[:order_by].upcase}"
|
||||
|
||||
result = ActiveRecord::Base.connection.exec_query(sql).to_hash
|
||||
|
||||
result.select! { |t| t['currency_id'] == params[:currency].downcase } if params[:currency].present?
|
||||
result.select! { |t| t['txid'] == params[:txid] } if params[:txid].present?
|
||||
result.select! { |t| t['updated_at'] >= Time.at(params[:time_from]) } if params[:time_from].present?
|
||||
result.select! { |t| t['updated_at'] <= Time.at(params[:time_to]) } if params[:time_to].present?
|
||||
|
||||
present paginate(result.each(&:symbolize_keys!)), with: API::V2::Entities::Transactions
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
43
app/api/v2/account/treasury.rb
Normal file
43
app/api/v2/account/treasury.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Treasury < Grape::API
|
||||
|
||||
helpers ::API::V2::ParamHelpers
|
||||
|
||||
desc 'Get list of user cards',
|
||||
is_array: true
|
||||
params do
|
||||
# use :pagination
|
||||
optional :uid,
|
||||
type: String,
|
||||
default: false,
|
||||
desc: 'UID of user'
|
||||
end
|
||||
get '/treasury/cards' do
|
||||
user_authorize! :read, ::Operations::Account
|
||||
|
||||
present current_user.cards
|
||||
end
|
||||
|
||||
desc 'Get list of user ibans',
|
||||
is_array: true
|
||||
params do
|
||||
# use :pagination
|
||||
optional :uid,
|
||||
type: String,
|
||||
default: false,
|
||||
desc: 'UID of user'
|
||||
end
|
||||
get '/treasury/ibans' do
|
||||
user_authorize! :read, ::Operations::Account
|
||||
|
||||
present current_user.ibans
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
19
app/api/v2/account/utils.rb
Normal file
19
app/api/v2/account/utils.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
module Utils
|
||||
def session
|
||||
request.session
|
||||
end
|
||||
|
||||
def publish_confirmation_code(user, action)
|
||||
totp = ::Vault::TOTPAction.new(action)
|
||||
totp.create(user.uid, user.email)
|
||||
::EventAPI.notify(action, record: { user: user.as_json_for_event_api,
|
||||
domain: Peatio::App.config.domain,
|
||||
code: totp.read_code(user.uid) })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
245
app/api/v2/account/withdraws.rb
Normal file
245
app/api/v2/account/withdraws.rb
Normal file
@@ -0,0 +1,245 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Withdraws < Grape::API
|
||||
|
||||
before { withdraws_must_be_permitted! }
|
||||
helpers API::V2::Account::Utils
|
||||
|
||||
desc 'List your withdraws as paginated collection.',
|
||||
is_array: true,
|
||||
success: API::V2::Entities::Withdraw
|
||||
params do
|
||||
optional :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist'},
|
||||
desc: 'Currency code.'
|
||||
optional :limit,
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_limit' },
|
||||
values: { value: 1..100, message: 'account.withdraw.invalid_limit' },
|
||||
default: 100,
|
||||
desc: "Number of withdraws per page (defaults to 100, maximum is 100)."
|
||||
optional :state,
|
||||
values: { value: ->(v) { (Array.wrap(v) - Withdraw::STATES.map(&:to_s)).blank? }, message: 'account.withdraw.invalid_state' },
|
||||
desc: 'Filter withdrawals by states.'
|
||||
optional :rid,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Wallet address on the Blockchain.'
|
||||
optional :time_from,
|
||||
allow_blank: { value: false, message: 'account.withdraw.empty_time_from' },
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_time_from' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'
|
||||
optional :time_to,
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_time_to' },
|
||||
allow_blank: { value: false, message: 'account.withdraw.empty_time_to' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'
|
||||
optional :page,
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_page' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'account.withdraw.non_positive_page'},
|
||||
default: 1,
|
||||
desc: 'Page number (defaults to 1).'
|
||||
end
|
||||
get '/withdraws' do
|
||||
user_authorize! :read, ::Withdraw
|
||||
|
||||
currency = Currency.find(params[:currency]) if params[:currency].present?
|
||||
|
||||
current_user.withdraws.order(id: :desc)
|
||||
.tap { |q| q.where!(currency: currency) if currency }
|
||||
.tap { |q| q.where!(aasm_state: params[:state]) if params[:state] }
|
||||
.tap { |q| q.where!(rid: params[:rid]) if params[:rid] }
|
||||
.tap { |q| q.where!('updated_at >= ?', Time.at(params[:time_from])) if params[:time_from].present? }
|
||||
.tap { |q| q.where!('updated_at <= ?', Time.at(params[:time_to])) if params[:time_to].present? }
|
||||
.tap { |q| present paginate(q), with: API::V2::Entities::Withdraw }
|
||||
end
|
||||
|
||||
desc 'Returns withdrawal sums for last 4 hours and 1 month'
|
||||
get '/withdraws/sums' do
|
||||
user_authorize! :read, ::Withdraw
|
||||
|
||||
sum_24_hours, sum_1_month = Withdraw.sanitize_execute_sum_queries(current_user.id)
|
||||
|
||||
present({ last_24_hours: sum_24_hours, last_1_month: sum_1_month })
|
||||
end
|
||||
|
||||
desc 'Creates new withdrawal to active beneficiary.'
|
||||
params do
|
||||
requires :otp,
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_otp' },
|
||||
allow_blank: false,
|
||||
desc: 'OTP to perform action'
|
||||
requires :beneficiary_id,
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_beneficiary_id' },
|
||||
allow_blank: false,
|
||||
desc: 'ID of Active Beneficiary belonging to user.'
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist'},
|
||||
desc: 'The currency code.'
|
||||
requires :amount,
|
||||
type: { value: BigDecimal, message: 'account.withdraw.non_decimal_amount' },
|
||||
values: { value: ->(v) { v.try(:positive?) }, message: 'account.withdraw.non_positive_amount' },
|
||||
desc: 'The amount to withdraw.'
|
||||
optional :note,
|
||||
type: String,
|
||||
values: { value: ->(v) { v.size <= 256 }, message: 'account.withdraw.too_long_note' },
|
||||
desc: 'Optional user metadata to be applied to the transaction. Used to tag transactions with memorable comments.'
|
||||
end
|
||||
post '/withdraws' do
|
||||
user_authorize! :create, ::Withdraw
|
||||
|
||||
withdraw_api_must_be_enabled!
|
||||
|
||||
if current_user.otp.present? && current_user.read_cache('withdraw_2fa').blank?
|
||||
error!({ errors: ['account.withdraw.need.2fa'] }, 403)
|
||||
end
|
||||
|
||||
totp = Vault::TOTPAction.new('withdraw-coin')
|
||||
error!({ errors: ['withdraw.totp.code'] }, 422) unless totp.validate?(current_user.uid, params[:otp])
|
||||
|
||||
beneficiary = current_user
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.find_by(id: params[:beneficiary_id])
|
||||
|
||||
if beneficiary.blank?
|
||||
error!({ errors: ['account.beneficiary.doesnt_exist'] }, 422)
|
||||
elsif !beneficiary.active?
|
||||
error!({ errors: ['account.beneficiary.invalid_state_for_withdrawal'] }, 422)
|
||||
end
|
||||
|
||||
currency = Currency.find(params[:currency])
|
||||
error!({ errors: ['account.currency.withdrawal_disabled'] }, 422) unless currency.withdrawal_enabled?
|
||||
|
||||
# TODO: Delete subclasses from Deposit and Withdraw
|
||||
withdraw = "withdraws/#{currency.type}".camelize.constantize.new \
|
||||
beneficiary: beneficiary,
|
||||
sum: params[:amount],
|
||||
member: current_user,
|
||||
currency: currency,
|
||||
note: params[:note]
|
||||
withdraw.save!
|
||||
withdraw.with_lock { withdraw.accept! }
|
||||
current_user.delete_cache('withdraw_2fa')
|
||||
present withdraw, with: API::V2::Entities::Withdraw
|
||||
|
||||
rescue ::Account::AccountError => e
|
||||
report_api_error(e, request)
|
||||
error!({ errors: ['account.withdraw.insufficient_balance'] }, 422)
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
report_api_error(e, request)
|
||||
# TODO: Check if there are other errors possible here.
|
||||
# For now single error which is not handled by params validations is
|
||||
# sum precision validation error (PrecisionValidator).
|
||||
error!({ errors: ['account.withdraw.invalid_amount'] }, 422)
|
||||
rescue => e
|
||||
report_exception(e)
|
||||
error!({ errors: ['account.withdraw.create_error'] }, 422)
|
||||
end
|
||||
|
||||
desc 'Creates new fiat withdrawal'
|
||||
params do
|
||||
requires :amount,
|
||||
type: { value: BigDecimal, message: 'account.withdraw.non_decimal_amount' },
|
||||
values: { value: ->(v) { v.try(:positive?) }, message: 'account.withdraw.non_positive_amount' },
|
||||
desc: 'The amount to withdraw.'
|
||||
requires :iban,
|
||||
type: String,
|
||||
desc: 'iban bank number'
|
||||
requires :currency,
|
||||
type: String,
|
||||
default: 'irt',
|
||||
desc: -> { 'currency' }
|
||||
optional :note,
|
||||
type: String,
|
||||
values: { value: ->(v) { v.size <= 256 }, message: 'account.withdraw.too_long_note' },
|
||||
desc: 'Optional user metadata to be applied to the transaction. Used to tag transactions with memorable comments.'
|
||||
end
|
||||
post '/withdraws/fiat' do
|
||||
user_authorize! :create, ::Withdraw
|
||||
withdraw_api_must_be_enabled!
|
||||
|
||||
if current_user.otp.present? && current_user.read_cache('withdraw_2fa').blank?
|
||||
error!({ errors: ['account.withdraw.need.2fa'] }, 403)
|
||||
end
|
||||
|
||||
error!({ errors: ['account.withdraw.use_valid_iban'] }, 403) if current_user.ibans.exclude?(params[:iban])
|
||||
|
||||
currency = Currency.find(params[:currency])
|
||||
|
||||
withdraw = "withdraws/#{currency.type}".camelize.constantize.new \
|
||||
rid: params[:iban],
|
||||
sum: params[:amount],
|
||||
member: current_user,
|
||||
currency: currency,
|
||||
transfer_type: :fiat,
|
||||
note: params[:note]
|
||||
|
||||
withdraw.save
|
||||
error!({ errors: withdraw.errors.full_messages }, 422) unless withdraw.valid?
|
||||
# delete 2fa cache
|
||||
current_user.delete_cache('withdraw_2fa')
|
||||
publish_confirmation_code(current_user, 'withdraw-fiat')
|
||||
present withdraw, with: API::V2::Entities::Withdraw
|
||||
end
|
||||
#
|
||||
#
|
||||
desc 'confirm fiat withdrawal'
|
||||
params do
|
||||
requires :id,
|
||||
type: Integer,
|
||||
desc: 'withdraw id'
|
||||
requires :otp,
|
||||
type: Integer,
|
||||
desc: 'auth otp in sms or email'
|
||||
end
|
||||
post '/withdraws/confirm' do
|
||||
user_authorize! :create, ::Withdraw
|
||||
withdraw = current_user.withdraws.find_by(id: params[:id])
|
||||
error!({ errors: ['account.withdraw.not.found'] }, 403) unless withdraw.present?
|
||||
|
||||
error!({ errors: ['account.withdraw.process_before'] }, 403) unless withdraw.aasm_state == 'prepared'
|
||||
|
||||
totp = Vault::TOTPAction.new('withdraw-fiat')
|
||||
error!({ errors: ['withdraw.totp.code'] }, 422) unless totp.validate?(current_user.uid, params[:otp])
|
||||
|
||||
# here must lock found
|
||||
withdraw.with_lock { withdraw.accept! }
|
||||
response = ::VandarService.new.create_withdraw(amount: withdraw.sum, iban: withdraw.rid)
|
||||
if response.dig('status').to_i.positive?
|
||||
withdraw.update(txid: response['data'].dig('settlement', 0, 'transaction_id'), aasm_state: 'confirming')
|
||||
else
|
||||
withdraw.with_lock { withdraw.reject! }
|
||||
end
|
||||
|
||||
present(response: response)
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
desc 'Get fiat withdrawal'
|
||||
params do
|
||||
requires :id,
|
||||
type: Integer,
|
||||
desc: 'withdraw id'
|
||||
optional :with_otp,
|
||||
type: String,
|
||||
desc: -> { 'send otp or not' }
|
||||
end
|
||||
get '/withdraw' do
|
||||
user_authorize! :read, ::Withdraw
|
||||
|
||||
withdraw = current_user.withdraws.find_by(id: params[:id])
|
||||
if withdraw.present? && withdraw.accepted? && params['with_otp'].present?
|
||||
publish_confirmation_code(current_user, 'withdraw')
|
||||
end
|
||||
present withdraw, with: API::V2::Entities::Withdraw
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
17
app/api/v2/admin/abilities.rb
Normal file
17
app/api/v2/admin/abilities.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Abilities < Grape::API
|
||||
namespace :abilities do
|
||||
desc 'Get all roles and permissions.'
|
||||
get do
|
||||
Ability.admin_permissions[current_user.role] || {}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
155
app/api/v2/admin/adjustments.rb
Normal file
155
app/api/v2/admin/adjustments.rb
Normal file
@@ -0,0 +1,155 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Adjustments < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
namespace :adjustments do
|
||||
desc 'Get all adjustments, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Adjustment
|
||||
params do
|
||||
use :currency
|
||||
use :date_picker
|
||||
use :pagination
|
||||
use :ordering
|
||||
optional :state,
|
||||
type: String,
|
||||
values: { value: -> { Adjustment.aasm.states.map(&:name).map(&:to_s) }, message: 'admin.adjustment.invalid_action' },
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:state][:desc] }
|
||||
optional :category,
|
||||
type: String,
|
||||
values: { value: -> { ::Adjustment::CATEGORIES }, message: 'admin.adjustment.invalid_category' },
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:category][:desc] }
|
||||
end
|
||||
get do
|
||||
admin_authorize! :read, Adjustment
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:state, :category)
|
||||
.translate(currency: :currency_id)
|
||||
.with_daterange
|
||||
.build
|
||||
|
||||
search = Adjustment.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::Adjustment
|
||||
end
|
||||
|
||||
desc 'Get adjustment by ID',
|
||||
success: API::V2::Admin::Entities::Adjustment
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'account.adjustment.non_integer_id' },
|
||||
desc: 'Adjsustment Identifier in Database'
|
||||
end
|
||||
get ':id' do
|
||||
admin_authorize! :read, Adjustment
|
||||
|
||||
present ::Adjustment.find(params[:id]), with: API::V2::Admin::Entities::Adjustment
|
||||
end
|
||||
|
||||
desc 'Create new adjustment.',
|
||||
success: API::V2::Admin::Entities::Adjustment
|
||||
params do
|
||||
requires :reason,
|
||||
type: String,
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:reason][:desc] }
|
||||
requires :description,
|
||||
type: String,
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:description][:desc] }
|
||||
requires :category,
|
||||
type: String,
|
||||
values: { value: -> { ::Adjustment::CATEGORIES }, message: 'admin.adjustment.invalid_category' },
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:category][:desc] }
|
||||
requires :amount,
|
||||
type: { value: BigDecimal, message: 'admin.adjustment.non_decimal_amount' },
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:amount][:desc] }
|
||||
requires :currency_id,
|
||||
type: String,
|
||||
values: { value: -> { ::Currency.codes }, message: 'admin.adjustment.currency_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:currency][:desc] }
|
||||
requires :asset_account_code,
|
||||
type: { value: Integer, message: 'admin.adjustment.non_integer_asset_account_code' },
|
||||
values: { value: -> { ::Operations::Account.where(type: :asset).pluck(:code) }, message: 'admin.adjustment.invalid_asset_account_code' },
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:asset_account_code][:desc] }
|
||||
requires :receiving_account_code,
|
||||
type: { value: Integer, message: 'admin.adjustment.non_integer_receiving_account_code' },
|
||||
values: { value: -> { ::Operations::Account.where.not(type: :asset).pluck(:code) }, message: 'admin.adjustment.invalid_receiving_account_code' },
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:receiving_account_code][:desc] }
|
||||
optional :receiving_member_uid,
|
||||
type: String,
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:receiving_account_code][:desc] }
|
||||
end
|
||||
post '/new' do
|
||||
admin_authorize! :create, Adjustment
|
||||
|
||||
# Do not accept member_uid if account code is not Liability or Revenue
|
||||
# Raise error if there is no :receiving_member_uid for Liability
|
||||
operation_klass = ::Operations.klass_for(code: params[:receiving_account_code])
|
||||
if operation_klass == ::Operations::Liability && params[:receiving_member_uid].blank?
|
||||
error!({ errors: ['admin.adjustment.missing_receiving_member_uid'] }, 422)
|
||||
elsif operation_klass == ::Operations::Expense && params[:receiving_member_uid].present?
|
||||
error!({ errors: ['admin.adjustment.redundant_receiving_member_uid'] }, 422)
|
||||
end
|
||||
|
||||
receiving = ::Operations.build_account_number(currency_id: params[:currency_id],
|
||||
account_code: params[:receiving_account_code],
|
||||
member_uid: params[:receiving_member_uid])
|
||||
|
||||
adjustment = Adjustment.new(declared(params)
|
||||
.except(:receiving_account_code, :receiving_member_uid)
|
||||
.merge(receiving_account_number: receiving,
|
||||
creator: current_user))
|
||||
if adjustment.save
|
||||
present adjustment, with: API::V2::Admin::Entities::Adjustment
|
||||
status 201
|
||||
else
|
||||
body errors: adjustment.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Accepts adjustment and creates operations or reject adjustment.',
|
||||
success: API::V2::Admin::Entities::Adjustment
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.adjustment.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:id][:desc] }
|
||||
requires :action,
|
||||
type: String,
|
||||
values: { value: -> { Adjustment.aasm.events.map(&:name).map(&:to_s) }, message: 'admin.adjustment.invalid_action' },
|
||||
desc: "Adjustment action all available actions: #{Adjustment.aasm.events.map(&:name)}"
|
||||
end
|
||||
post '/action' do
|
||||
admin_authorize! :update, Adjustment
|
||||
adjustment = Adjustment.find(params[:id])
|
||||
|
||||
if adjustment.amount < 0
|
||||
account_number_hash = ::Operations.split_account_number(account_number: adjustment.receiving_account_number)
|
||||
member = Member.find_by(uid: account_number_hash[:member_uid])
|
||||
balance = member.get_account(account_number_hash[:currency_id]).balance
|
||||
|
||||
if adjustment.amount.abs() > balance
|
||||
error!({ errors: ['admin.adjustment.user_insufficient_balance'] }, 422)
|
||||
end
|
||||
end
|
||||
|
||||
if adjustment.public_send("may_#{params[:action]}?")
|
||||
# TODO: Add behaviour in case of errors on action.
|
||||
adjustment.public_send("#{params[:action]}!", validator: current_user)
|
||||
present adjustment, with: API::V2::Admin::Entities::Adjustment
|
||||
else
|
||||
body errors: ["admin.adjustment.cannot_perform_#{params[:action]}_action"]
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
23
app/api/v2/admin/airdrops.rb
Normal file
23
app/api/v2/admin/airdrops.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'peatio/airdrop'
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Airdrops < Grape::API
|
||||
|
||||
desc 'Process user airdrop'
|
||||
params do
|
||||
requires :file,
|
||||
type: File
|
||||
end
|
||||
post '/airdrops' do
|
||||
Peatio::Airdrop.new.process(current_user, params)
|
||||
present(result: 'Airdrop processing started')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
77
app/api/v2/admin/beneficiaries.rb
Normal file
77
app/api/v2/admin/beneficiaries.rb
Normal file
@@ -0,0 +1,77 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Beneficiaries < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
namespace :beneficiaries do
|
||||
desc 'Get list of beneficiaries',
|
||||
success: API::V2::Admin::Entities::Beneficiary
|
||||
params do
|
||||
use :uid
|
||||
use :ordering
|
||||
use :pagination
|
||||
optional :id,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Entities::Beneficiary.documentation[:id][:desc] }
|
||||
optional :currency,
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Currency.codes).blank? }, message: 'account.currency.doesnt_exist' },
|
||||
desc: 'Beneficiary currency code'
|
||||
optional :state,
|
||||
type: Array[Integer],
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Beneficiary::STATES_MAPPING.values).blank? }, message: 'account.beneficiary.invalid_state' },
|
||||
desc: 'Beneficiary state',
|
||||
coerce_with: lambda { |val|
|
||||
val.map { |s| Beneficiary::STATES_MAPPING[s.to_sym] }
|
||||
}
|
||||
end
|
||||
|
||||
get do
|
||||
admin_authorize! :read, Beneficiary
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:id)
|
||||
.in(:state)
|
||||
.translate_in(currency: :currency_id)
|
||||
.translate(uid: :member_uid)
|
||||
.build
|
||||
|
||||
search = Beneficiary.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::Beneficiary
|
||||
end
|
||||
|
||||
desc 'Take an action on the beneficiary',
|
||||
success: API::V2::Admin::Entities::Beneficiary
|
||||
|
||||
params do
|
||||
requires :id,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Admin::Entities::Beneficiary.documentation[:id][:desc] }
|
||||
requires :action,
|
||||
type: String,
|
||||
values: { value: -> { ::Beneficiary.aasm.events.map(&:name).map(&:to_s) }, message: 'admin.beneficiary.invalid_action' },
|
||||
desc: "Valid actions are #{::Beneficiary.aasm.events.map(&:name)}."
|
||||
end
|
||||
|
||||
post '/actions' do
|
||||
admin_authorize! :update, Beneficiary
|
||||
|
||||
beneficiary = Beneficiary.find(params[:id])
|
||||
|
||||
if beneficiary.public_send("may_#{params[:action]}?")
|
||||
beneficiary.public_send("#{params[:action]}!")
|
||||
present beneficiary, with: API::V2::Admin::Entities::Beneficiary
|
||||
else
|
||||
body errors: ["admin.beneficiary.cannot_#{params[:action]}"]
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
201
app/api/v2/admin/blockchains.rb
Normal file
201
app/api/v2/admin/blockchains.rb
Normal file
@@ -0,0 +1,201 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Blockchains < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
namespace :blockchains do
|
||||
desc 'Get all blockchains, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Blockchain
|
||||
params do
|
||||
optional :key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.blockchain.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:key][:desc] }
|
||||
optional :client,
|
||||
values: { value: -> { ::Blockchain.clients.map(&:to_s) }, message: 'admin.blockchain.blockchain_client_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:client][:desc] }
|
||||
optional :status,
|
||||
values: { value: -> { %w[active disabled] }, message: 'admin.blockchain.blockchain_status_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:status][:desc] }
|
||||
optional :name,
|
||||
values: { value: -> { ::Blockchain.pluck(:name) }, message: 'admin.blockchain.blockchain_name_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:name][:desc] }
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get do
|
||||
admin_authorize! :read, Blockchain
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:key, :client, :status, :name)
|
||||
.build
|
||||
|
||||
search = ::Blockchain.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::Blockchain
|
||||
end
|
||||
|
||||
desc 'Get available blockchain clients.',
|
||||
is_array: true
|
||||
get '/clients' do
|
||||
Blockchain.clients
|
||||
end
|
||||
|
||||
desc 'Get a blockchain.' do
|
||||
success API::V2::Admin::Entities::Blockchain
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:id][:desc] }
|
||||
end
|
||||
get '/:id' do
|
||||
admin_authorize! :read, Blockchain
|
||||
|
||||
present Blockchain.find(params[:id]), with: API::V2::Admin::Entities::Blockchain
|
||||
end
|
||||
|
||||
desc 'Get a latest blockchain block.'
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:id][:desc] }
|
||||
end
|
||||
get '/:id/latest_block' do
|
||||
admin_authorize! :read, Blockchain
|
||||
|
||||
Blockchain.find(params[:id])&.blockchain_api.latest_block_number
|
||||
rescue
|
||||
error!({ errors: ['admin.blockchain.latest_block'] }, 422)
|
||||
end
|
||||
|
||||
desc 'Create new blockchain.' do
|
||||
success API::V2::Admin::Entities::Blockchain
|
||||
end
|
||||
params do
|
||||
requires :key,
|
||||
values: { value: -> (v){ v && v.length < 255 }, message: 'admin.blockchain.key_too_long' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:key][:desc] }
|
||||
requires :name,
|
||||
values: { value: -> (v){ v && v.length < 255 }, message: 'admin.blockchain.name_too_long' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:name][:desc] }
|
||||
requires :client,
|
||||
values: { value: -> { ::Blockchain.clients.map(&:to_s) }, message: 'admin.blockchain.invalid_client' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:client][:desc] }
|
||||
requires :height,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_height' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'admin.blockchain.non_positive_height' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:height][:desc] }
|
||||
optional :explorer_transaction,
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:explorer_transaction][:desc] }
|
||||
optional :explorer_address,
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:explorer_address][:desc] }
|
||||
optional :server,
|
||||
regexp: { value: URI::regexp, message: 'admin.blockchain.invalid_server' },
|
||||
desc: -> { 'Blockchain server url' }
|
||||
optional :status,
|
||||
values: { value: %w(active disabled), message: 'admin.blockchain.invalid_status' },
|
||||
default: 'active',
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:status][:desc] }
|
||||
optional :min_confirmations,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_min_confirmations' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'admin.blockchain.non_positive_min_confirmations' },
|
||||
default: 6,
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:min_confirmations][:desc] }
|
||||
end
|
||||
post '/new' do
|
||||
admin_authorize! :create, Blockchain
|
||||
|
||||
blockchain = Blockchain.new(declared(params))
|
||||
if blockchain.save
|
||||
present blockchain, with: API::V2::Admin::Entities::Blockchain
|
||||
status 201
|
||||
else
|
||||
body errors: blockchain.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update blockchain.' do
|
||||
success API::V2::Admin::Entities::Blockchain
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:id][:desc] }
|
||||
optional :key,
|
||||
type: String,
|
||||
values: { value: -> (v){ v.length < 255 }, message: 'admin.blockchain.key_too_long' },
|
||||
coerce_with: ->(v) { v.strip.downcase },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:key][:desc] }
|
||||
optional :name,
|
||||
values: { value: -> (v){ v.length < 255 }, message: 'admin.blockchain.name_too_long' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:name][:desc] }
|
||||
optional :client,
|
||||
values: { value: -> { ::Blockchain.clients.map(&:to_s) }, message: 'admin.blockchain.invalid_client' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:client][:desc] }
|
||||
optional :server,
|
||||
regexp: { value: URI::regexp, message: 'admin.blockchain.invalid_server' },
|
||||
desc: -> { 'Blockchain server url' }
|
||||
optional :height,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_height' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'admin.blockchain.non_positive_height' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:height][:desc] }
|
||||
optional :explorer_transaction,
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:explorer_transaction][:desc] }
|
||||
optional :explorer_address,
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:explorer_address][:desc] }
|
||||
optional :status,
|
||||
values: { value: %w(active disabled), message: 'admin.blockchain.invalid_status' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:status][:desc] }
|
||||
optional :min_confirmations,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_min_confirmations' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'admin.blockchain.non_positive_min_confirmations' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:min_confirmations][:desc] }
|
||||
end
|
||||
post '/update' do
|
||||
admin_authorize! :update, Blockchain, params.except(:id)
|
||||
|
||||
blockchain = Blockchain.find(params[:id])
|
||||
if blockchain.update(declared(params, include_missing: false))
|
||||
present blockchain, with: API::V2::Admin::Entities::Blockchain
|
||||
else
|
||||
body errors: blockchain.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Process blockchain\'s block.' do
|
||||
success API::V2::Admin::Entities::Blockchain
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:id][:desc] }
|
||||
requires :block_number,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_block_number' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'admin.blockchain.non_positive_block_number' },
|
||||
desc: -> { 'The id of a particular block on blockchain' }
|
||||
end
|
||||
post '/process_block' do
|
||||
admin_authorize! :update, Blockchain
|
||||
|
||||
blockchain = Blockchain.find(params[:id])
|
||||
begin
|
||||
blockchain.blockchain_api.process_block(params[:block_number])
|
||||
present blockchain, with: API::V2::Admin::Entities::Blockchain
|
||||
status 201
|
||||
rescue StandardError => e
|
||||
Rails.logger.error { "Error: #{e} while processing block #{params[:block_number]} of blockchain id: #{params[:id]}" }
|
||||
error!({ errors: ['admin.blockchain.process_block'] }, 422)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
234
app/api/v2/admin/currencies.rb
Normal file
234
app/api/v2/admin/currencies.rb
Normal file
@@ -0,0 +1,234 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Currencies < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
helpers do
|
||||
# Collection of shared params, used to
|
||||
# generate required/optional Grape params.
|
||||
OPTIONAL_CURRENCY_PARAMS ||= {
|
||||
name: { desc: -> { API::V2::Admin::Entities::Currency.documentation[:name][:desc] } },
|
||||
deposit_fee: {
|
||||
type: { value: BigDecimal, message: 'admin.currency.non_decimal_deposit_fee' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.currency.invalid_deposit_fee' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:deposit_fee][:desc] }
|
||||
},
|
||||
min_deposit_amount: {
|
||||
type: { value: BigDecimal, message: 'admin.currency.min_deposit_amount' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.currency.min_deposit_amount' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:min_deposit_amount][:desc] }
|
||||
},
|
||||
min_collection_amount: {
|
||||
type: { value: BigDecimal, message: 'admin.currency.non_decimal_min_collection_amount' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.currency.invalid_min_collection_amount' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:min_collection_amount][:desc] }
|
||||
},
|
||||
withdraw_fee: {
|
||||
type: { value: BigDecimal, message: 'admin.currency.non_decimal_withdraw_fee' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.currency.ivalid_withdraw_fee' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:withdraw_fee][:desc] }
|
||||
},
|
||||
min_withdraw_amount: {
|
||||
type: { value: BigDecimal, message: 'admin.currency.non_decimal_min_withdraw_amount' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.currency.invalid_min_withdraw_amount' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:min_withdraw_amount][:desc] }
|
||||
},
|
||||
withdraw_limit_24h: {
|
||||
type: { value: BigDecimal, message: 'admin.currency.non_decimal_withdraw_limit_24h' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.currency.invalid_withdraw_limit_24h' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:withdraw_limit_24h][:desc] }
|
||||
},
|
||||
withdraw_limit_72h: {
|
||||
type: { value: BigDecimal, message: 'admin.currency.non_decimal_withdraw_limit_72h' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.currency.invalid_withdraw_limit_72h' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:withdraw_limit_72h][:desc] }
|
||||
},
|
||||
options: {
|
||||
type: { value: JSON, message: 'admin.currency.non_json_options' },
|
||||
default: {},
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:options][:desc] }
|
||||
},
|
||||
visible: {
|
||||
type: { value: Boolean, message: 'admin.currency.non_boolean_visible' },
|
||||
default: true,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:visible][:desc] }
|
||||
},
|
||||
deposit_enabled: {
|
||||
type: { value: Boolean, message: 'admin.currency.non_boolean_deposit_enabled' },
|
||||
default: true,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:deposit_enabled][:desc] }
|
||||
},
|
||||
withdrawal_enabled: {
|
||||
type: { value: Boolean, message: 'admin.currency.non_boolean_withdrawal_enabled' },
|
||||
default: true,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:withdrawal_enabled][:desc] }
|
||||
},
|
||||
precision: {
|
||||
type: { value: Integer, message: 'admin.currency.non_integer_base_precision' },
|
||||
default: 8,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:precision][:desc] }
|
||||
},
|
||||
price: {
|
||||
type: { value: BigDecimal, message: 'admin.currency.non_decimal_price' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:price][:desc] }
|
||||
},
|
||||
icon_url: { desc: -> { API::V2::Admin::Entities::Currency.documentation[:icon_url][:desc] } },
|
||||
description: { desc: -> { API::V2::Admin::Entities::Currency.documentation[:description][:desc] } },
|
||||
homepage: { desc: -> { API::V2::Admin::Entities::Currency.documentation[:homepage][:desc] } },
|
||||
}
|
||||
|
||||
params :create_currency_params do
|
||||
OPTIONAL_CURRENCY_PARAMS.each do |key, params|
|
||||
optional key, params
|
||||
end
|
||||
end
|
||||
|
||||
params :update_currency_params do
|
||||
OPTIONAL_CURRENCY_PARAMS.each do |key, params|
|
||||
optional key, params.except(:default)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get list of currencies',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Currency
|
||||
params do
|
||||
use :currency_type
|
||||
use :pagination
|
||||
optional :ordering,
|
||||
values: { value: %w(asc desc), message: 'admin.pagination.invalid_ordering' },
|
||||
default: 'asc',
|
||||
desc: 'If set, returned values will be sorted in specific order, defaults to \'asc\'.'
|
||||
optional :order_by,
|
||||
default: 'position',
|
||||
desc: 'Name of the field, which result will be ordered by.'
|
||||
optional :deposit_enabled,
|
||||
type: { value: Boolean, message: 'admin.currency.non_boolean_deposit_enabled' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:deposit_enabled][:desc] }
|
||||
optional :withdrawal_enabled,
|
||||
type: { value: Boolean, message: 'admin.currency.non_boolean_withdrawal_enabled' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:withdrawal_enabled][:desc] }
|
||||
optional :visible,
|
||||
type: { value: Boolean, message: 'admin.currency.non_boolean_visible' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:visible][:desc] }
|
||||
end
|
||||
get '/currencies' do
|
||||
admin_authorize! :read, Currency
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:type, :deposit_enabled, :withdrawal_enabled, :visible)
|
||||
.with_daterange
|
||||
.build
|
||||
|
||||
search = Currency.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::Currency
|
||||
end
|
||||
|
||||
desc 'Get a currency.' do
|
||||
success API::V2::Admin::Entities::Currency
|
||||
end
|
||||
params do
|
||||
requires :code,
|
||||
type: String,
|
||||
values: { value: -> { Currency.codes(bothcase: true) }, message: 'admin.currency.doesnt_exist'},
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:code][:desc] }
|
||||
end
|
||||
get '/currencies/:code', requirements: { code: /[\w\.\-]+/ } do
|
||||
admin_authorize! :read, Currency
|
||||
|
||||
present Currency.find(params[:code]), with: API::V2::Admin::Entities::Currency
|
||||
end
|
||||
|
||||
desc 'Create new currency.' do
|
||||
success API::V2::Admin::Entities::Currency
|
||||
end
|
||||
params do
|
||||
use :create_currency_params
|
||||
requires :code,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:code][:desc] }
|
||||
optional :type,
|
||||
values: { value: ::Currency.types.map(&:to_s), message: 'admin.currency.invalid_type' },
|
||||
default: 'coin',
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:type][:desc] }
|
||||
optional :base_factor,
|
||||
type: { value: Integer, message: 'admin.currency.non_integer_base_factor' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:base_factor][:desc] }
|
||||
optional :position,
|
||||
type: { value: Integer, message: 'admin.currency.non_integer_position' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:position][:desc] }
|
||||
optional :subunits,
|
||||
type: { value: Integer, message: 'admin.currency.non_integer_subunits' },
|
||||
values: { value: (0..18), message: 'admin.currency.invalid_subunits' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:subunits][:desc] }
|
||||
given type: ->(val) { val == 'coin' } do
|
||||
optional :blockchain_key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.currency.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:blockchain_key][:desc] }
|
||||
optional :parent_id,
|
||||
values: { value: -> { Currency.coins_without_tokens.pluck(:id).map(&:to_s) }, message: 'admin.currency.parent_id_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:parent_id][:desc] }
|
||||
end
|
||||
mutually_exclusive :base_factor, :subunits, message: 'admin.currency.one_of_base_factor_subunits_fields'
|
||||
end
|
||||
post '/currencies/new' do
|
||||
admin_authorize! :create, Currency
|
||||
|
||||
currency = Currency.new(declared(params, include_missing: false))
|
||||
if currency.save
|
||||
present currency, with: API::V2::Admin::Entities::Currency
|
||||
status 201
|
||||
else
|
||||
body errors: currency.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update currency.' do
|
||||
success API::V2::Admin::Entities::Currency
|
||||
end
|
||||
params do
|
||||
use :update_currency_params
|
||||
requires :code,
|
||||
values: { value: -> { ::Currency.codes }, message: 'admin.currency.doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:code][:desc] }
|
||||
optional :position,
|
||||
type: { value: Integer, message: 'admin.currency.non_integer_position' },
|
||||
values: { value: -> (p){ p >= ::Currency::TOP_POSITION }, message: 'admin.currency.invalid_position' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:position][:desc] }
|
||||
optional :blockchain_key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.currency.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:blockchain_key][:desc] }
|
||||
given code: -> (val) { val.in?(Currency.coins.pluck(:code).map(&:to_s)) } do
|
||||
optional :parent_id,
|
||||
values: { value: -> { Currency.coins_without_tokens.pluck(:id).map(&:to_s) }, message: 'admin.currency.parent_id_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:parent_id][:desc] }
|
||||
end
|
||||
end
|
||||
post '/currencies/update' do
|
||||
admin_authorize! :update, Currency, params.except(:code)
|
||||
|
||||
currency = Currency.find(params[:code])
|
||||
if currency.update(declared(params, include_missing: false))
|
||||
present currency, with: API::V2::Admin::Entities::Currency
|
||||
else
|
||||
body errors: currency.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
133
app/api/v2/admin/deposit_limits.rb
Normal file
133
app/api/v2/admin/deposit_limits.rb
Normal file
@@ -0,0 +1,133 @@
|
||||
# frozen_string_literal: true
|
||||
#
|
||||
# this file follow withdraw_limits.rb
|
||||
#
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class DepositLimits < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
# list of deposit limits
|
||||
desc 'Returns deposit limits table as paginated collection',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::DepositLimit
|
||||
params do
|
||||
optional :group,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:group][:desc] },
|
||||
coerce_with: ->(c) { c.strip.downcase }
|
||||
optional :kyc_level,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:kyc_level][:desc] }
|
||||
optional :kind,
|
||||
values: { value: ->(v) { (Array.wrap(v) - DepositLimit.kind.values).blank? }, message: 'limit.kind.invalid' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:kind][:desc] }
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/deposit_limits' do
|
||||
admin_authorize! :read, DepositLimit
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:group, :kyc_level, :kind)
|
||||
.build
|
||||
|
||||
search = DepositLimit.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result), with: API::V2::Entities::DepositLimit
|
||||
end
|
||||
|
||||
# create deposit limit
|
||||
desc 'It creates deposit limits record',
|
||||
success: API::V2::Entities::DepositLimit
|
||||
params do
|
||||
requires :limit_24_hour,
|
||||
type: { value: BigDecimal, message: 'admin.deposit_limit.non_decimal_limit_24_hour' },
|
||||
values: { value: ->(p) { p && p >= 0 }, message: 'admin.deposit_limit.invalid_limit_24_hour' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:limit_24_hour][:desc] }
|
||||
requires :limit_1_month,
|
||||
type: { value: BigDecimal, message: 'admin.deposit_limit.non_decimal_limit_1_month' },
|
||||
values: { value: ->(p) { p && p >= 0 }, message: 'admin.deposit_limit.invalid_limit_1_month' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:limit_1_month][:desc] }
|
||||
requires :kind,
|
||||
values: { value: ->(v) { (Array.wrap(v) - DepositLimit.kind.values).blank? }, message: 'limit.kind.invalid' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:kind][:desc] }
|
||||
optional :group,
|
||||
type: String,
|
||||
default: ::DepositLimit::ANY,
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:group][:desc] }
|
||||
optional :kyc_level,
|
||||
type: String,
|
||||
default: ::DepositLimit::ANY,
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:kyc_level][:desc] }
|
||||
end
|
||||
post '/deposit_limits' do
|
||||
admin_authorize! :create, DepositLimit
|
||||
|
||||
deposit_limit = ::DepositLimit.new(declared(params))
|
||||
if deposit_limit.save
|
||||
present deposit_limit, with: API::V2::Entities::DepositLimit
|
||||
status 201
|
||||
else
|
||||
body errors: deposit_limit.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
# update deposit limit
|
||||
desc 'It updates deposit limits record',
|
||||
success: API::V2::Entities::DepositLimit
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.deposit_limit.non_integer_id' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:id][:desc] }
|
||||
optional :limit_24_hour,
|
||||
type: { value: BigDecimal, message: 'admin.deposit_limit.non_decimal_limit_24_hour' },
|
||||
values: { value: ->(p) { p && p >= 0 }, message: 'admin.deposit_limit.invalid_limit_24_hour' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:limit_24_hour][:desc] }
|
||||
optional :limit_1_month,
|
||||
type: { value: BigDecimal, message: 'admin.deposit_limit.non_decimal_limit_1_month' },
|
||||
values: { value: ->(p) { p && p >= 0 }, message: 'admin.deposit_limit.invalid_limit_1_month' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:limit_1_month][:desc] }
|
||||
optional :kind,
|
||||
values: { value: ->(v) { (Array.wrap(v) - DepositLimit.kind.values).blank? }, message: 'limit.kind.invalid' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:kind][:desc] }
|
||||
optional :kyc_level,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:kyc_level][:desc] }
|
||||
optional :group,
|
||||
type: String,
|
||||
coerce_with: ->(c) { c.strip.downcase },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:group][:desc] }
|
||||
end
|
||||
put '/deposit_limits' do
|
||||
admin_authorize! :update, DepositLimit
|
||||
|
||||
deposit_limit = ::DepositLimit.find(params[:id])
|
||||
if deposit_limit.update(declared(params, include_missing: false))
|
||||
present deposit_limit, with: API::V2::Entities::DepositLimit
|
||||
else
|
||||
body errors: deposit_limit.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
# delete deposit limit
|
||||
desc 'It deletes deposit limits record',
|
||||
success: API::V2::Entities::DepositLimit
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.deposit_limit.non_integer_id' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:id][:desc] }
|
||||
end
|
||||
delete '/deposit_limits/:id' do
|
||||
admin_authorize! :delete, DepositLimit
|
||||
|
||||
present DepositLimit.destroy(params[:id]), with: API::V2::Entities::DepositLimit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
180
app/api/v2/admin/deposits.rb
Normal file
180
app/api/v2/admin/deposits.rb
Normal file
@@ -0,0 +1,180 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Deposits < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
desc 'Get all deposits, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Deposit
|
||||
params do
|
||||
optional :state,
|
||||
values: { value: -> { ::Deposit.aasm.states.map(&:name).map(&:to_s) }, message: 'admin.deposit.invalid_state' },
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:state][:desc] }
|
||||
optional :id,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:id][:desc] }
|
||||
optional :txid,
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:txid][:desc] }
|
||||
optional :address,
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:address][:desc] }
|
||||
optional :tid,
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:tid][:desc] }
|
||||
optional :email,
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:email][:desc] }
|
||||
use :uid
|
||||
use :currency
|
||||
use :currency_type
|
||||
use :date_picker
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/deposits' do
|
||||
admin_authorize! :read, Deposit
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:id, :txid, :tid, :address)
|
||||
.translate(state: :aasm_state, uid: :member_uid, currency: :currency_id, email: :member_email)
|
||||
.with_daterange
|
||||
.merge(type_eq: params[:type].present? ? "Deposits::#{params[:type].capitalize}" : nil)
|
||||
.build
|
||||
|
||||
search = Deposit.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::Deposit
|
||||
end
|
||||
|
||||
desc 'Take an action on the deposit.',
|
||||
success: API::V2::Admin::Entities::Deposit
|
||||
params do
|
||||
requires :id,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:id][:desc] }
|
||||
requires :action,
|
||||
type: String,
|
||||
values: { value: -> { ::Deposit.aasm.events.map(&:name).map(&:to_s) }, message: 'admin.deposit.invalid_action' },
|
||||
desc: "Valid actions are #{::Deposit.aasm.events.map(&:name)}."
|
||||
given action: ->(val) { val == 'process' } do
|
||||
optional :fees,
|
||||
type: Boolean,
|
||||
default: false,
|
||||
desc: 'Process deposit collection with collecting fees or not'
|
||||
end
|
||||
end
|
||||
post '/deposits/actions' do
|
||||
admin_authorize! :update, Deposit
|
||||
|
||||
deposit = Deposit.find(params[:id])
|
||||
|
||||
if deposit.public_send("may_#{params[:action]}?")
|
||||
deposit.public_send("#{params[:action]}!")
|
||||
present deposit, with: API::V2::Admin::Entities::Deposit
|
||||
else
|
||||
body errors: ["admin.depodit.cannot_#{params[:action]}"]
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Creates new fiat deposit .',
|
||||
success: API::V2::Admin::Entities::Deposit
|
||||
params do
|
||||
requires :uid,
|
||||
values: { value: -> (v) { Member.exists?(uid: v) }, message: 'admin.deposit.user_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:uid][:desc] }
|
||||
requires :currency,
|
||||
values: { value: -> { Currency.fiats.codes(bothcase: true) }, message: 'admin.deposit.currency_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:currency][:desc] }
|
||||
requires :amount,
|
||||
type: { value: BigDecimal, message: 'admin.deposit.non_decimal_amount' },
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:amount][:desc] }
|
||||
optional :tid,
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:tid][:desc] }
|
||||
end
|
||||
post '/deposits/new' do
|
||||
admin_authorize! :create, ::Deposits::Fiat
|
||||
|
||||
declared_params = declared(params, include_missing: false)
|
||||
member = Member.find_by(uid: declared_params[:uid])
|
||||
currency = Currency.find(declared_params[:currency])
|
||||
data = { member: member, currency: currency }.merge!(declared_params.slice(:amount, :tid))
|
||||
deposit = ::Deposits::Fiat.new(data)
|
||||
|
||||
if deposit.save
|
||||
present deposit, with: API::V2::Admin::Entities::Deposit
|
||||
status 201
|
||||
else
|
||||
body errors: deposit.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Creates new crypto refund',
|
||||
success: API::V2::Admin::Entities::Refund
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.deposit.non_integer_type' },
|
||||
desc: -> { 'Deposit id' }
|
||||
requires :address,
|
||||
desc: -> { API::V2::Admin::Entities::Refund.documentation[:address][:desc] }
|
||||
end
|
||||
post '/deposits/:id/refund' do
|
||||
admin_authorize! :wrrie, Deposit
|
||||
|
||||
deposit = Deposit.find(params[:id])
|
||||
|
||||
refund = ::Refund.new(deposit: deposit, address: params[:address])
|
||||
|
||||
if refund.save
|
||||
present refund, with: API::V2::Admin::Entities::Refund
|
||||
else
|
||||
body errors: refund.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Returns deposit address for account you want to deposit to by currency and uid.',
|
||||
success: API::V2::Admin::Entities::Deposit
|
||||
params do
|
||||
requires :uid,
|
||||
values: { value: -> (v) { Member.exists?(uid: v) }, message: 'admin.deposit.user_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:uid][:desc] }
|
||||
requires :currency,
|
||||
values: { value: -> { Currency.codes }, message: 'admin.deposit.currency_doesnt_exist' },
|
||||
as: :currency_id,
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:currency][:desc] }
|
||||
given :currency_id do
|
||||
optional :address_format,
|
||||
type: String,
|
||||
values: { value: -> { %w[legacy cash] }, message: 'admin.deposit.invalid_address_format' },
|
||||
validate_currency_address_format: { value: true, prefix: 'admin.deposit' },
|
||||
desc: 'Address format legacy/cash'
|
||||
end
|
||||
end
|
||||
post '/deposit_address' do
|
||||
admin_authorize! :create, PaymentAddress
|
||||
|
||||
member = Member.find_by!(uid: params[:uid])
|
||||
currency = Currency.find_by!(id: params[:currency_id])
|
||||
wallet = Wallet.deposit_wallet(currency.id)
|
||||
|
||||
unless wallet.present?
|
||||
error!({ errors: ['admin.deposit.wallet_not_found'] }, 422)
|
||||
end
|
||||
|
||||
if currency.deposit_enabled
|
||||
payment_address = member.payment_address(wallet.id)
|
||||
present payment_address, with: API::V2::Entities::PaymentAddress, address_format: params[:address_format]
|
||||
status 201
|
||||
else
|
||||
body errors: ["admin.deposit.deposit_disabled"]
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
101
app/api/v2/admin/engines.rb
Normal file
101
app/api/v2/admin/engines.rb
Normal file
@@ -0,0 +1,101 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Engines < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
desc 'Get all engine, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Engine
|
||||
params do
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/engines' do
|
||||
admin_authorize! :read, ::Engine
|
||||
|
||||
result = ::Engine.order(params[:order_by] => params[:ordering])
|
||||
present paginate(result), with: API::V2::Admin::Entities::Engine
|
||||
end
|
||||
|
||||
desc 'Get engine.' do
|
||||
success API::V2::Admin::Entities::Engine
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: String,
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:id][:desc] }
|
||||
end
|
||||
get '/engines/:id' do
|
||||
admin_authorize! :read, ::Engine
|
||||
|
||||
present ::Engine.find(params[:id]), with: API::V2::Admin::Entities::Engine
|
||||
end
|
||||
|
||||
desc 'Create new engine.' do
|
||||
success API::V2::Admin::Entities::Engine
|
||||
end
|
||||
params do
|
||||
requires :name,
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:name][:desc] },
|
||||
values: { value: ->(v) { !v.in?(::Engine.pluck(:name)) }, message: 'admin.engine.duplicate_name' }
|
||||
requires :driver,
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:driver][:desc] }
|
||||
optional :uid,
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:uid][:desc] }
|
||||
optional :key,
|
||||
desc: -> { 'Credentials for remote engine' }
|
||||
optional :secret,
|
||||
desc: -> { 'Credentials for remote engine' }
|
||||
optional :data,
|
||||
desc: -> { 'Metadata for engine' }
|
||||
end
|
||||
post '/engines/new' do
|
||||
admin_authorize! :create, ::Engine
|
||||
|
||||
engine = ::Engine.new(declared(params))
|
||||
if engine.save
|
||||
present engine, with: API::V2::Admin::Entities::Engine
|
||||
status 201
|
||||
else
|
||||
body errors: engine.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update engine' do
|
||||
success API::V2::Admin::Entities::Engine
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:id][:desc] }
|
||||
optional :name,
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:name][:desc] }
|
||||
optional :driver,
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:driver][:desc] }
|
||||
optional :key,
|
||||
desc: -> { 'Credentials for remote engine' }
|
||||
optional :secret,
|
||||
desc: -> { 'Credentials for remote engine' }
|
||||
optional :state,
|
||||
values: { value: ::Engine::STATES.values, message: 'admin.engine.invalid_state' },
|
||||
default: 1,
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:state][:desc] }
|
||||
end
|
||||
post '/engines/update' do
|
||||
admin_authorize! :update, ::Engine
|
||||
|
||||
engine = ::Engine.find(params[:id])
|
||||
if engine.update(declared(params, include_missing: false))
|
||||
present engine, with: API::V2::Admin::Entities::Engine
|
||||
else
|
||||
body errors: engine.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
169
app/api/v2/admin/entities/adjustment.rb
Normal file
169
app/api/v2/admin/entities/adjustment.rb
Normal file
@@ -0,0 +1,169 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Adjustment < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Unique adjustment identifier in database.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:reason,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Adjustment reason.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:description,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Adjustment description.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:category,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Adjustment category'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:amount,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Adjustment amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:validator_uid,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Unique adjustment validator identifier in database.'
|
||||
},
|
||||
if: ->(adjustment, _options) { adjustment.validator }
|
||||
) do |adjustment, _options|
|
||||
adjustment.validator.uid
|
||||
end
|
||||
|
||||
expose(
|
||||
:creator_uid,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Unique adjustment creator identifier in database.'
|
||||
},
|
||||
) do |adjustment, _options|
|
||||
adjustment.creator.uid
|
||||
end
|
||||
|
||||
expose(
|
||||
:currency_id,
|
||||
as: :currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Adjustment currency ID.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:asset,
|
||||
using: API::V2::Admin::Entities::Operation,
|
||||
if: ->(adjustment, _options) { adjustment.fetch_asset }
|
||||
) do |adjustment, _options|
|
||||
adjustment.fetch_asset
|
||||
end
|
||||
|
||||
expose(
|
||||
:liability,
|
||||
using: API::V2::Admin::Entities::Operation,
|
||||
if: ->(adjustment, _options) { adjustment.fetch_liability }
|
||||
) do |adjustment, _options|
|
||||
adjustment.fetch_liability
|
||||
end
|
||||
|
||||
expose(
|
||||
:revenue,
|
||||
using: API::V2::Admin::Entities::Operation,
|
||||
if: ->(adjustment, _options) { adjustment.fetch_revenue }
|
||||
) do |adjustment, _options|
|
||||
adjustment.fetch_revenue
|
||||
end
|
||||
|
||||
expose(
|
||||
:expense,
|
||||
using: API::V2::Admin::Entities::Operation,
|
||||
if: ->(adjustment, _options) { adjustment.fetch_expense }
|
||||
) do |adjustment, _options|
|
||||
adjustment.fetch_expense
|
||||
end
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Adjustment\'s state.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:asset_account_code,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Adjustment asset account code.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:receiving_account_code,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Adjustment receiving account code.'
|
||||
}
|
||||
) do |adjustment, _options|
|
||||
::Operations.split_account_number(account_number: adjustment.receiving_account_number)[:code]
|
||||
end
|
||||
|
||||
expose(
|
||||
:receiving_member_uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Adjustment receiving member uid.'
|
||||
},
|
||||
if: ->(adjustment, _options) { adjustment.fetch_liability.present? }
|
||||
) do |adjustment, _options|
|
||||
::Operations.split_account_number(account_number: adjustment.receiving_account_number)[:member_uid]
|
||||
end
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetime when operation was created.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetime when operation was updated.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
20
app/api/v2/admin/entities/beneficiary.rb
Normal file
20
app/api/v2/admin/entities/beneficiary.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Beneficiary < API::V2::Entities::Beneficiary
|
||||
expose(
|
||||
:data,
|
||||
documentation: {
|
||||
desc: 'Bank Account details for fiat Beneficiary in JSON format.'\
|
||||
'For crypto it\'s blockchain address.',
|
||||
type: JSON
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
102
app/api/v2/admin/entities/blockchain.rb
Normal file
102
app/api/v2/admin/entities/blockchain.rb
Normal file
@@ -0,0 +1,102 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Blockchain < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation:{
|
||||
type: Integer,
|
||||
desc: 'Unique blockchain identifier in database.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:key,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'Unique key to identify blockchain.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:name,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'A name to identify blockchain.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:client,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'Integrated blockchain client.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:height,
|
||||
documentation:{
|
||||
type: Integer,
|
||||
desc: 'The number of blocks preceding a particular block on blockchain.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:explorer_address,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'Blockchain explorer address template.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:explorer_transaction,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'Blockchain explorer transaction template.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:min_confirmations,
|
||||
documentation:{
|
||||
type: Integer,
|
||||
desc: 'Minimum number of confirmations.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:status,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'Blockchain status (active/disabled).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Blockchain created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Blockchain updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
123
app/api/v2/admin/entities/currency.rb
Normal file
123
app/api/v2/admin/entities/currency.rb
Normal file
@@ -0,0 +1,123 @@
|
||||
# enco ding: UTF-8
|
||||
# froz en_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Currency < API::V2::Entities::Currency
|
||||
unexpose(:id)
|
||||
|
||||
expose(
|
||||
:code,
|
||||
documentation: {
|
||||
desc: 'Unique currency code.',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:blockchain_key,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Associated blockchain key which will perform transactions synchronization for currency.'
|
||||
},
|
||||
if: -> (currency){ currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:parent_id,
|
||||
documentation: {
|
||||
desc: 'Parent currency id.',
|
||||
type: String
|
||||
},
|
||||
if: -> (currency){ currency.token? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:min_collection_amount,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Minimal collection amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:position,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Currency position.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:visible,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency display status (true/false).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:base_factor,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Currency base factor.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:subunits,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Fraction of the basic monetary unit.'
|
||||
}
|
||||
) { |currency| currency.subunits }
|
||||
|
||||
expose(
|
||||
:options,
|
||||
documentation: {
|
||||
type: JSON,
|
||||
desc: 'Currency options.'
|
||||
},
|
||||
if: -> (currency){ currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:precision,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Currency precision.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Currency price.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
108
app/api/v2/admin/entities/deposit.rb
Normal file
108
app/api/v2/admin/entities/deposit.rb
Normal file
@@ -0,0 +1,108 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Deposit < API::V2::Entities::Deposit
|
||||
expose(
|
||||
:member_id,
|
||||
as: :member,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The member id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit member uid.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:email,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The deposit member email.'
|
||||
}
|
||||
) { |d| d.member.email }
|
||||
|
||||
expose(
|
||||
:address,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit blockchain address.'
|
||||
},
|
||||
if: ->(deposit) { deposit.currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:txout,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Deposit blockchain transaction output.'
|
||||
},
|
||||
if: ->(deposit) { deposit.currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:block_number,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Deposit blockchain block number.'
|
||||
},
|
||||
if: ->(deposit) { deposit.currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:type,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit type (fiat or coin).'
|
||||
}
|
||||
) { |d| d.currency.fiat? ? :fiat : :coin }
|
||||
|
||||
expose(
|
||||
:tid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit tid.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:spread,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit collection spread.'
|
||||
},
|
||||
if: -> (deposit) { !deposit.spread.empty? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetime when deposit was updated.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:completed_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetime when deposit was completed.'
|
||||
},
|
||||
if: ->(deposit) { deposit.completed? }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
51
app/api/v2/admin/entities/engine.rb
Normal file
51
app/api/v2/admin/entities/engine.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Engine < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Engine uniq id'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:name,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Engine name'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:driver,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Engine driver'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Owner of a engine'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Engine state'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
20
app/api/v2/admin/entities/internal_transfer.rb
Normal file
20
app/api/v2/admin/entities/internal_transfer.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class InternalTransfer < API::V2::Entities::InternalTransfer
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Internal transfer uniq id'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
54
app/api/v2/admin/entities/market.rb
Normal file
54
app/api/v2/admin/entities/market.rb
Normal file
@@ -0,0 +1,54 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Market < API::V2::Entities::Market
|
||||
expose(
|
||||
:engine_id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Engine id for this market.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:position,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Market position.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:data,
|
||||
documentation: {
|
||||
type: JSON,
|
||||
desc: 'Market additional data.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Market created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Market updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
104
app/api/v2/admin/entities/member.rb
Normal file
104
app/api/v2/admin/entities/member.rb
Normal file
@@ -0,0 +1,104 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Member < API::V2::Entities::Member
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Unique member identifier in database.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:level,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Member\'s level.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:role,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Member\'s role.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:group,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Member\'s group.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Member\'s state.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Member created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Member updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:beneficiaries,
|
||||
using: API::V2::Admin::Entities::Beneficiary,
|
||||
documentation: {
|
||||
type: 'API::V2::Admin::Entities::Beneficiary',
|
||||
is_array: true,
|
||||
desc: 'Member Beneficiary.'
|
||||
}
|
||||
) do |m|
|
||||
m.beneficiaries
|
||||
end
|
||||
|
||||
expose(
|
||||
:accounts,
|
||||
using: API::V2::Entities::Account,
|
||||
documentation: {
|
||||
type: 'API::V2::Entities::Account',
|
||||
is_array: true,
|
||||
desc: 'Member accounts.'
|
||||
}
|
||||
) do |m|
|
||||
m.accounts.includes(:currency)
|
||||
end
|
||||
|
||||
expose(
|
||||
:payment_addresses,
|
||||
as: :deposit_addresses,
|
||||
using: API::V2::Entities::PaymentAddress,
|
||||
documentation: {
|
||||
type: 'API::V2::Entities::PaymentAddress',
|
||||
is_array: true,
|
||||
desc: 'Member deposits addresses'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
94
app/api/v2/admin/entities/operation.rb
Normal file
94
app/api/v2/admin/entities/operation.rb
Normal file
@@ -0,0 +1,94 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Operation < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Unique operation identifier in database.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:code,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The Account code which this operation related to.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:currency_id,
|
||||
as: :currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Operation currency ID.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:credit,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Operation credit amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:debit,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Operation debit amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:uid,
|
||||
if: ->(operation) { operation.try(:member).try(:uid) },
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
}) { |operation| operation.try(:member).try(:uid) }
|
||||
|
||||
expose(
|
||||
:account_kind,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Operation\'s account kind (locked or main).'
|
||||
}
|
||||
) { |operation| operation.account.kind }
|
||||
|
||||
expose(
|
||||
:reference_id,
|
||||
as: :rid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The id of operation reference.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:reference_type,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The type of operations.'
|
||||
}) { |operation| operation.reference_type.downcase if operation.reference_type.present? }
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetime when operation was created.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
30
app/api/v2/admin/entities/order.rb
Normal file
30
app/api/v2/admin/entities/order.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Order < API::V2::Entities::Order
|
||||
unexpose(:trades)
|
||||
|
||||
expose(
|
||||
:email,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The shared user email.'
|
||||
}
|
||||
) { |w| w.member.email }
|
||||
|
||||
expose(
|
||||
:uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
}
|
||||
) { |w| w.member.uid }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
33
app/api/v2/admin/entities/refund.rb
Normal file
33
app/api/v2/admin/entities/refund.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Refund < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'The refund id'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:address,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Refund address'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:deposit,
|
||||
using: API::V2::Admin::Entities::Deposit
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
112
app/api/v2/admin/entities/trade.rb
Normal file
112
app/api/v2/admin/entities/trade.rb
Normal file
@@ -0,0 +1,112 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Trade < API::V2::Entities::Trade
|
||||
unexpose(:side)
|
||||
unexpose(:order_id)
|
||||
unexpose(:fee_currency)
|
||||
unexpose(:fee)
|
||||
unexpose(:fee_amount)
|
||||
|
||||
expose(
|
||||
:maker_order_email,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade maker member email.'
|
||||
}
|
||||
) { |trade| trade.maker.email }
|
||||
|
||||
expose(
|
||||
:maker_uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade maker member uid.'
|
||||
}
|
||||
) { |trade| trade.maker.uid }
|
||||
|
||||
expose(
|
||||
:maker_fee,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade maker fee percentage.',
|
||||
},
|
||||
if: ->(object, options) { options[:extended] }
|
||||
) { |trade| trade.maker_order.maker_fee }
|
||||
|
||||
expose(
|
||||
:maker_fee_amount,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade maker fee amount.',
|
||||
}
|
||||
) { |trade| fee_amount(trade, trade.maker_order) }
|
||||
|
||||
expose(
|
||||
:maker_fee_currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade maker fee currency code.'
|
||||
}
|
||||
) { |trade| fee_currency(trade.maker_order) }
|
||||
|
||||
expose(
|
||||
:maker_order,
|
||||
using: API::V2::Admin::Entities::Order,
|
||||
if: ->(object, options) { options[:extended] }
|
||||
)
|
||||
|
||||
expose(
|
||||
:taker_order_email,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade taker member email.'
|
||||
}
|
||||
) { |trade| trade.taker.email }
|
||||
|
||||
expose(
|
||||
:taker_uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade taker member uid.'
|
||||
}
|
||||
) { |trade| trade.taker.uid }
|
||||
|
||||
expose(
|
||||
:taker_fee_currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade taker fee currency code.'
|
||||
}
|
||||
) { |trade| fee_currency(trade.taker_order) }
|
||||
|
||||
expose(
|
||||
:taker_fee,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade taker fee percentage.',
|
||||
},
|
||||
if: ->(object, options) { options[:extended] }
|
||||
) { |trade| trade.taker_order.taker_fee }
|
||||
|
||||
expose(
|
||||
:taker_fee_amount,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade taker fee amount.',
|
||||
}
|
||||
) { |trade| fee_amount(trade, trade.taker_order) }
|
||||
|
||||
expose(
|
||||
:taker_order,
|
||||
using: API::V2::Admin::Entities::Order,
|
||||
if: ->(object, options) { options[:extended] }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
112
app/api/v2/admin/entities/wallet.rb
Normal file
112
app/api/v2/admin/entities/wallet.rb
Normal file
@@ -0,0 +1,112 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Wallet < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation:{
|
||||
type: Integer,
|
||||
desc: 'Unique wallet identifier in database.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:name,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Wallet name.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:kind,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Kind of wallet 'deposit','fee','hot','warm' or 'cold'."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:currency_ids,
|
||||
as: :currencies,
|
||||
documentation: {
|
||||
is_array: true,
|
||||
desc: 'Wallet currency code.',
|
||||
example: -> { ::Currency.visible.codes }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:address,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Wallet address.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:gateway,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Wallet gateway.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:max_balance,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Wallet max balance.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:balance,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Wallet balance'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:blockchain_key,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Wallet blockchain key.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:status,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Wallet status (active/disabled).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Wallet created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Wallet updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
69
app/api/v2/admin/entities/whitelisted_smart_contract.rb
Normal file
69
app/api/v2/admin/entities/whitelisted_smart_contract.rb
Normal file
@@ -0,0 +1,69 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class WhitelistedSmartContract < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Unique whitelisted smart contract identifier in database.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:address,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Whitelisted smart contract address.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:description,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Whitelisted smart contract description.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:blockchain_key,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Whitelisted smart contract blockchain key.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Whitelisted smart contract status (active/disabled).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Whitelisted smart contract created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Whitelisted smart contract updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
87
app/api/v2/admin/entities/withdraw.rb
Normal file
87
app/api/v2/admin/entities/withdraw.rb
Normal file
@@ -0,0 +1,87 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Withdraw < API::V2::Entities::Withdraw
|
||||
expose(
|
||||
:member_id,
|
||||
as: :member,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The member id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:beneficiary,
|
||||
using: API::V2::Entities::Beneficiary,
|
||||
if: ->(withdraw, options) do
|
||||
options[:with_beneficiary] && withdraw.beneficiary.present?
|
||||
end
|
||||
)
|
||||
|
||||
expose(
|
||||
:uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The withdrawal member uid.'
|
||||
}
|
||||
) { |w| w.member.uid }
|
||||
|
||||
expose(
|
||||
:email,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The withdrawal member email.'
|
||||
}
|
||||
) { |w| w.member.email }
|
||||
|
||||
expose(
|
||||
:account,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The account code.'
|
||||
}
|
||||
) { |w| w.account.id }
|
||||
|
||||
expose(
|
||||
:block_number,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'The withdrawal block_number.'
|
||||
},
|
||||
if: ->(w) { w.currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:tid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Withdraw tid.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:error,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Withdraw error.'
|
||||
},
|
||||
unless: ->(w) { w.succeed? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:metadata,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Optional metadata to be applied to the transaction.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
115
app/api/v2/admin/helpers.rb
Normal file
115
app/api/v2/admin/helpers.rb
Normal file
@@ -0,0 +1,115 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Helpers
|
||||
extend ::Grape::API::Helpers
|
||||
|
||||
class RansackBuilder
|
||||
# RansackBuilder creates a hash in a format ransack accepts
|
||||
# eq(:column) generetes a pair column_eq: params[:column]
|
||||
# translate(:column1 => :column2) generates a pair column2_eq: params[:column1]
|
||||
# merge allows to append additional selectors in
|
||||
# build returns prepared hash
|
||||
|
||||
attr_reader :build
|
||||
|
||||
def initialize(params)
|
||||
@params = params
|
||||
@build = {}
|
||||
end
|
||||
|
||||
def merge(opt)
|
||||
@build.merge!(opt)
|
||||
self
|
||||
end
|
||||
|
||||
def with_daterange
|
||||
@build.merge!("#{@params[:range]}_at_gteq" => @params[:from])
|
||||
@build.merge!("#{@params[:range]}_at_lteq" => @params[:to])
|
||||
self
|
||||
end
|
||||
|
||||
def translate(opt)
|
||||
opt.each { |k, v| @build.merge!("#{v}_eq" => @params[k]) }
|
||||
self
|
||||
end
|
||||
|
||||
def translate_in(opt)
|
||||
opt.each { |k, v| @build.merge!("#{v}_in" => @params[k]) }
|
||||
self
|
||||
end
|
||||
|
||||
def in(*keys)
|
||||
keys.each { |k| @build.merge!("#{k}_in" => @params[k]) }
|
||||
self
|
||||
end
|
||||
|
||||
def eq(*keys)
|
||||
keys.each { |k| @build.merge!("#{k}_eq" => @params[k]) }
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
params :currency_type do
|
||||
optional :type,
|
||||
type: String,
|
||||
values: { value: ::Currency.types.map(&:to_s), message: 'admin.currency.invalid_type' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:type][:desc] }
|
||||
end
|
||||
|
||||
params :currency do
|
||||
optional :currency,
|
||||
values: { value: -> { Currency.codes(bothcase: true) }, message: 'admin.currency.doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:currency][:desc] }
|
||||
end
|
||||
|
||||
params :uid do
|
||||
optional :uid,
|
||||
values: { value: -> (v) { Member.exists?(uid: v) }, message: 'admin.user.doesnt_exist' },
|
||||
desc: -> { API::V2::Entities::Member.documentation[:uid][:desc] }
|
||||
end
|
||||
|
||||
params :pagination do
|
||||
optional :limit,
|
||||
type: { value: Integer, message: 'admin.pagination.non_integer_limit' },
|
||||
values: { value: 1..1000, message: 'admin.pagination.invalid_limit' },
|
||||
default: 100,
|
||||
desc: 'Limit the number of returned paginations. Defaults to 100.'
|
||||
optional :page,
|
||||
type: { value: Integer, message: 'admin.pagination.non_integer_page' },
|
||||
allow_blank: false,
|
||||
default: 1,
|
||||
desc: 'Specify the page of paginated results.'
|
||||
end
|
||||
|
||||
params :ordering do
|
||||
optional :ordering,
|
||||
values: { value: %w(asc desc), message: 'admin.pagination.invalid_ordering' },
|
||||
default: 'asc',
|
||||
desc: 'If set, returned values will be sorted in specific order, defaults to \'asc\'.'
|
||||
optional :order_by,
|
||||
default: 'id',
|
||||
desc: 'Name of the field, which result will be ordered by.'
|
||||
end
|
||||
|
||||
params :date_picker do
|
||||
optional :range,
|
||||
default: 'created',
|
||||
values: { value: -> { %w[created updated completed] } },
|
||||
desc: 'Date range picker, defaults to \'created\'.'
|
||||
optional :from,
|
||||
type: { value: Time, message: 'admin.filter.range_from_invalid' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'\
|
||||
'If set, only entities FROM the time will be retrieved.'
|
||||
optional :to,
|
||||
type: { value: Time, message: 'admin.filter.range_to_invalid' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'\
|
||||
'If set, only entities BEFORE the time will be retrieved.'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
51
app/api/v2/admin/internal_transfers.rb
Normal file
51
app/api/v2/admin/internal_transfers.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class InternalTransfers < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
desc 'Get all internal transfers.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::InternalTransfer
|
||||
params do
|
||||
optional :sender,
|
||||
values: { value: -> (v) { Member.where('uid = ? OR username = ?', v, v).present? }, message: 'admin.receiver.doesnt_exist' },
|
||||
desc: 'Sender uid or username.'
|
||||
optional :receiver,
|
||||
values: { value: -> (v) { Member.where('uid = ? OR username = ?', v, v).present? }, message: 'admin.receiver.doesnt_exist' },
|
||||
desc: 'Receiver uid or username.'
|
||||
use :currency
|
||||
use :pagination
|
||||
use :date_picker
|
||||
use :ordering
|
||||
end
|
||||
get '/internal_transfers' do
|
||||
admin_authorize! :read, InternalTransfer
|
||||
|
||||
if params[:sender].present?
|
||||
sender = Member.find_by('uid = ? OR username = ?', params[:sender], params[:sender])
|
||||
params.except!(:sender).merge!(sender_id: sender.id) if sender.present?
|
||||
end
|
||||
|
||||
if params[:receiver].present?
|
||||
receiver = Member.find_by('uid = ? OR username = ?', params[:receiver], params[:receiver])
|
||||
params.except!(:receiver).merge!(receiver_id: receiver.id) if receiver.present?
|
||||
end
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:sender_id, :receiver_id)
|
||||
.translate_in(currency: :currency_id)
|
||||
.with_daterange
|
||||
.build
|
||||
|
||||
search = ::InternalTransfer.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::InternalTransfer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
171
app/api/v2/admin/markets.rb
Normal file
171
app/api/v2/admin/markets.rb
Normal file
@@ -0,0 +1,171 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Markets < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
helpers do
|
||||
# Collection of shared params, used to
|
||||
# generate required/optional Grape params.
|
||||
OPTIONAL_MARKET_PARAMS ||= {
|
||||
amount_precision: {
|
||||
type: { value: Integer, message: 'admin.market.non_integer_amount_precision' },
|
||||
values: { value: -> (p){ p && p >= 0 }, message: 'admin.market.invalid_amount_precision' },
|
||||
default: 4,
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:amount_precision][:desc] }
|
||||
},
|
||||
price_precision: {
|
||||
type: { value: Integer, message: 'admin.market.non_integer_price_precision' },
|
||||
values: { value: -> (p){ p && p >= 0 }, message: 'admin.market.invalid_price_precision' },
|
||||
default: 4,
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:price_precision][:desc] }
|
||||
},
|
||||
max_price: {
|
||||
type: { value: BigDecimal, message: 'admin.market.non_decimal_max_price' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.market.invalid_max_price' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:max_price][:desc] }
|
||||
},
|
||||
data: {
|
||||
type: { value: JSON, message: 'admin.market.invalid_data' },
|
||||
default: {},
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:data][:desc] }
|
||||
},
|
||||
state: {
|
||||
values: { value: ::Market::STATES, message: 'admin.market.invalid_state' },
|
||||
default: 'enabled',
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:state][:desc] }
|
||||
},
|
||||
}
|
||||
|
||||
params :create_market_params do
|
||||
OPTIONAL_MARKET_PARAMS.each do |key, params|
|
||||
optional key, params
|
||||
end
|
||||
end
|
||||
|
||||
params :update_market_params do
|
||||
OPTIONAL_MARKET_PARAMS.each do |key, params|
|
||||
optional key, params.except(:default)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get all markets, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Market
|
||||
params do
|
||||
use :pagination
|
||||
optional :ordering,
|
||||
values: { value: %w(asc desc), message: 'admin.pagination.invalid_ordering' },
|
||||
default: 'asc',
|
||||
desc: 'If set, returned values will be sorted in specific order, defaults to \'asc\'.'
|
||||
optional :order_by,
|
||||
default: 'position',
|
||||
desc: 'Name of the field, which result will be ordered by.'
|
||||
end
|
||||
get '/markets' do
|
||||
admin_authorize! :read, ::Market
|
||||
|
||||
result = ::Market.order(params[:order_by] => params[:ordering])
|
||||
present paginate(result), with: API::V2::Admin::Entities::Market
|
||||
end
|
||||
|
||||
desc 'Get market.' do
|
||||
success API::V2::Admin::Entities::Market
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: String,
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:id][:desc] }
|
||||
end
|
||||
get '/markets/:id', requirements: { id: /[\w\.\-]+/ } do
|
||||
admin_authorize! :read, ::Market
|
||||
|
||||
present ::Market.find(params[:id]), with: API::V2::Admin::Entities::Market
|
||||
end
|
||||
|
||||
desc 'Create new market.' do
|
||||
success API::V2::Admin::Entities::Market
|
||||
end
|
||||
params do
|
||||
use :create_market_params
|
||||
requires :base_currency,
|
||||
values: { value: -> { ::Currency.ids }, message: 'admin.market.currency_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:base_unit][:desc] }
|
||||
requires :quote_currency,
|
||||
values: { value: -> { ::Currency.ids }, message: 'admin.market.currency_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:quote_unit][:desc] }
|
||||
requires :min_price,
|
||||
type: { value: BigDecimal, message: 'admin.market.non_decimal_min_price' },
|
||||
values: { value: -> (p){ p && p >= 0 }, message: 'admin.market.invalid_min_price' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:min_price][:desc] }
|
||||
requires :min_amount,
|
||||
type: { value: BigDecimal, message: 'admin.market.non_decimal_min_amount' },
|
||||
values: { value: -> (p){ p && p >= 0 }, message: 'admin.market.invalid_min_amount' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:min_amount][:desc] }
|
||||
optional :engine_id,
|
||||
type: { value: Integer, message: 'admin.market.non_integer_engine_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:engine_id][:desc] }
|
||||
optional :position,
|
||||
type: { value: Integer, message: 'admin.market.non_integer_position' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:position][:desc] }
|
||||
optional :engine_name,
|
||||
values: { value: -> { ::Engine.pluck(:name) }, message: 'admin.market.engine_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:name][:desc] }
|
||||
exactly_one_of :engine_id, :engine_name, message: 'admin.market.one_of_engine_id_engine_name_fields'
|
||||
end
|
||||
post '/markets/new' do
|
||||
admin_authorize! :create, ::Market
|
||||
|
||||
market = ::Market.new(declared(params, include_missing: false))
|
||||
if market.save
|
||||
present market, with: API::V2::Admin::Entities::Market
|
||||
status 201
|
||||
else
|
||||
body errors: market.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update market.' do
|
||||
success API::V2::Admin::Entities::Market
|
||||
end
|
||||
params do
|
||||
use :update_market_params
|
||||
requires :id,
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:id][:desc] }
|
||||
optional :engine_id,
|
||||
type: { value: Integer, message: 'admin.market.non_integer_engine_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:engine_id][:desc] }
|
||||
optional :position,
|
||||
type: { value: Integer, message: 'admin.market.non_integer_position' },
|
||||
values: { value: -> (p){ p >= ::Market::TOP_POSITION }, message: 'admin.market.invalid_position' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:position][:desc] }
|
||||
optional :min_price,
|
||||
type: { value: BigDecimal, message: 'admin.market.non_decimal_min_price' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.market.invalid_min_price' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:min_price][:desc] }
|
||||
optional :min_amount,
|
||||
type: { value: BigDecimal, message: 'admin.market.non_decimal_min_amount' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.market.invalid_min_amount' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:min_amount][:desc] }
|
||||
|
||||
end
|
||||
post '/markets/update' do
|
||||
admin_authorize! :update, ::Market
|
||||
|
||||
market = ::Market.find(params[:id])
|
||||
if market.update(declared(params, include_missing: false))
|
||||
present market, with: API::V2::Admin::Entities::Market
|
||||
else
|
||||
body errors: market.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
90
app/api/v2/admin/members.rb
Normal file
90
app/api/v2/admin/members.rb
Normal file
@@ -0,0 +1,90 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Members < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
desc 'Get all members, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Member
|
||||
params do
|
||||
optional :state,
|
||||
desc: 'Filter order by state.'
|
||||
optional :role,
|
||||
values: { value: -> { ::Ability.roles }, message: 'admin.member.invalid_role' }
|
||||
optional :group,
|
||||
values: { value: -> { ::Member.groups }, message: 'admin.member.invalid_group' }
|
||||
optional :email,
|
||||
desc: -> { API::V2::Entities::Member.documentation[:email][:desc] }
|
||||
use :uid
|
||||
use :date_picker
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/members' do
|
||||
admin_authorize! :read, Member
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:uid, :email, :state, :role, :group)
|
||||
.with_daterange
|
||||
.build
|
||||
|
||||
search = Member.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::Member
|
||||
end
|
||||
|
||||
desc 'Get available members groups.',
|
||||
is_array: true
|
||||
get '/members/groups' do
|
||||
admin_authorize! :read, Member
|
||||
|
||||
Member.groups
|
||||
end
|
||||
|
||||
desc 'Get a member.' do
|
||||
success API::V2::Admin::Entities::Member
|
||||
end
|
||||
params do
|
||||
requires :uid,
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
end
|
||||
get '/members/:uid' do
|
||||
admin_authorize! :read, Member
|
||||
|
||||
present Member.find_by!(uid: params[:uid]), with: API::V2::Admin::Entities::Member
|
||||
end
|
||||
|
||||
desc 'Set user group.' do
|
||||
success API::V2::Admin::Entities::Member
|
||||
end
|
||||
params do
|
||||
requires :uid,
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
requires :group,
|
||||
type: String,
|
||||
coerce_with: ->(v) { v.strip.downcase },
|
||||
desc: 'User gruop'
|
||||
end
|
||||
put '/members/:uid' do
|
||||
admin_authorize! :update, Member
|
||||
declared_params = declared(params)
|
||||
|
||||
member = Member.find_by!(uid: declared_params[:uid])
|
||||
if member.update(group: declared_params[:group])
|
||||
present member, with: API::V2::Admin::Entities::Member
|
||||
status 201
|
||||
else
|
||||
body errors: member.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
67
app/api/v2/admin/mount.rb
Normal file
67
app/api/v2/admin/mount.rb
Normal file
@@ -0,0 +1,67 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Mount < Grape::API
|
||||
PREFIX = '/admin'
|
||||
|
||||
before { authenticate! unless request.path == '/api/v2/admin/swagger' }
|
||||
|
||||
formatter :csv, CSVFormatter
|
||||
|
||||
mount Admin::Orders
|
||||
mount Admin::Blockchains
|
||||
mount Admin::Currencies
|
||||
mount Admin::Markets
|
||||
mount Admin::Wallets
|
||||
mount Admin::Deposits
|
||||
mount Admin::Withdraws
|
||||
mount Admin::Trades
|
||||
mount Admin::Operations
|
||||
mount Admin::Members
|
||||
mount Admin::TradingFees
|
||||
mount Admin::Adjustments
|
||||
mount Admin::Engines
|
||||
mount Admin::Beneficiaries
|
||||
mount Admin::Abilities
|
||||
mount Admin::WithdrawLimits
|
||||
mount Admin::DepositLimits
|
||||
mount Admin::Airdrops
|
||||
mount Admin::InternalTransfers
|
||||
mount Admin::WhitelistedSmartContracts
|
||||
|
||||
# The documentation is accessible at http://localhost:3000/swagger?url=/api/v2/admin/swagger
|
||||
# Add swagger documentation for Peatio Admin API
|
||||
add_swagger_documentation base_path: File.join(API::Mount::PREFIX, API::V2::Mount::API_VERSION, PREFIX, 'peatio'),
|
||||
add_base_path: true,
|
||||
mount_path: '/swagger',
|
||||
api_version: API::V2::Mount::API_VERSION,
|
||||
doc_version: Peatio::Application::VERSION,
|
||||
info: {
|
||||
title: "Peatio Admin API #{API::V2::Mount::API_VERSION}",
|
||||
description: 'Admin API high privileged API with RBAC.',
|
||||
contact_name: 'openware.com',
|
||||
contact_email: 'hello@openware.com',
|
||||
contact_url: 'https://www.openware.com',
|
||||
licence: 'MIT',
|
||||
license_url: 'https://github.com/openware/peatio/blob/master/LICENSE.md'
|
||||
},
|
||||
models: [
|
||||
API::V2::Admin::Entities::Blockchain,
|
||||
API::V2::Admin::Entities::Currency,
|
||||
API::V2::Admin::Entities::Deposit,
|
||||
API::V2::Admin::Entities::Market,
|
||||
API::V2::Admin::Entities::Member,
|
||||
API::V2::Admin::Entities::Operation,
|
||||
API::V2::Admin::Entities::Order,
|
||||
API::V2::Admin::Entities::Trade,
|
||||
API::V2::Admin::Entities::Wallet,
|
||||
API::V2::Admin::Entities::Withdraw,
|
||||
API::V2::Admin::Entities::InternalTransfer,
|
||||
API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
84
app/api/v2/admin/operations.rb
Normal file
84
app/api/v2/admin/operations.rb
Normal file
@@ -0,0 +1,84 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Operations < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
helpers do
|
||||
params :get_operations_params do
|
||||
optional :reference_type,
|
||||
desc: 'The reference type for which operation was created.'
|
||||
optional :rid,
|
||||
type: Integer,
|
||||
desc: 'The unique id of operation\'s reference, for which operation was created.'
|
||||
optional :code,
|
||||
type: Integer,
|
||||
desc: 'Opeartion\'s code.'
|
||||
use :currency
|
||||
use :date_picker
|
||||
end
|
||||
|
||||
def ransack_params
|
||||
Helpers::RansackBuilder.new(params)
|
||||
.eq(:code, :reference_type)
|
||||
.translate(currency: :currency_id, rid: :reference_id)
|
||||
.with_daterange
|
||||
.build
|
||||
end
|
||||
end
|
||||
|
||||
# GET: api/v2/admin/assets
|
||||
# GET: api/v2/admin/expenses
|
||||
# GET: api/v2/admin/revenues
|
||||
::Operations::Account::PLATFORM_TYPES.each do |op_type|
|
||||
op_type_plural = op_type.to_s.pluralize
|
||||
|
||||
desc "Returns #{op_type_plural} as a paginated collection." do
|
||||
success API::V2::Admin::Entities::Operation
|
||||
end
|
||||
params do
|
||||
use :get_operations_params
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get op_type_plural do
|
||||
klass = ::Operations.const_get(op_type.capitalize)
|
||||
admin_authorize! :read, klass
|
||||
|
||||
search = klass.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result, false), with: API::V2::Admin::Entities::Operation
|
||||
end
|
||||
end
|
||||
|
||||
# Get: api/v2/admin/liabilities
|
||||
::Operations::Account::MEMBER_TYPES.each do |op_type|
|
||||
op_type_plural = op_type.to_s.pluralize
|
||||
|
||||
desc "Returns #{op_type_plural} as a paginated collection." do
|
||||
success API::V2::Admin::Entities::Operation
|
||||
end
|
||||
params do
|
||||
use :uid
|
||||
use :get_operations_params
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get op_type_plural do
|
||||
klass = ::Operations.const_get(op_type.capitalize)
|
||||
admin_authorize! :read, klass
|
||||
|
||||
member = Member.find_by(uid: params[:uid]) if params[:uid].present?
|
||||
search = klass.ransack(ransack_params.merge(member_id_eq: member&.id))
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result, false), with: API::V2::Admin::Entities::Operation
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
122
app/api/v2/admin/orders.rb
Normal file
122
app/api/v2/admin/orders.rb
Normal file
@@ -0,0 +1,122 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Orders < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
helpers ::API::V2::OrderHelpers
|
||||
|
||||
content_type :csv, 'text/csv'
|
||||
|
||||
desc 'Get all orders, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Order
|
||||
params do
|
||||
optional :market,
|
||||
values: { value: -> { ::Market.ids }, message: 'admin.market.doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:id][:desc] }
|
||||
optional :state,
|
||||
values: { value: -> { ::Order.state.values }, message: 'admin.order.invalid_state' },
|
||||
desc: 'Filter order by state.'
|
||||
optional :ord_type,
|
||||
values: { value: ::Order::TYPES, message: 'admin.order.invalid_ord_type' },
|
||||
desc: 'Filter order by ord_type.'
|
||||
optional :price,
|
||||
type: { value: BigDecimal, message: 'admin.order.non_decimal_price' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'admin.order.non_positive_price' },
|
||||
desc: -> { API::V2::Admin::Entities::Order.documentation[:price][:desc] }
|
||||
optional :origin_volume,
|
||||
type: { value: BigDecimal, message: 'admin.order.non_decimal_price' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'admin.order.non_positive_origin_volume' },
|
||||
desc: -> { API::V2::Admin::Entities::Order.documentation[:origin_volume][:desc] }
|
||||
optional :type,
|
||||
values: { value: %w(sell buy), message: 'admin.order.invalid_type' },
|
||||
desc: 'Filter order by type.'
|
||||
optional :email,
|
||||
desc: -> { API::V2::Entities::Member.documentation[:email][:desc] }
|
||||
use :uid
|
||||
use :date_picker
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/orders' do
|
||||
admin_authorize! :read, Order
|
||||
|
||||
if params[:uid].present? || params[:email].present?
|
||||
member = Member.find_by('uid = ? OR email = ?', params[:uid], params[:email])
|
||||
params.except!(:uid, :email).merge!(member_id: member.id) if member.present?
|
||||
end
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:price, :origin_volume, :ord_type, :state, :member_id)
|
||||
.translate(market: :market_id)
|
||||
.with_daterange
|
||||
.merge({
|
||||
type_eq: params[:type].present? ? params[:type] == 'buy' ? 'OrderBid' : 'OrderAsk' : nil
|
||||
}).build
|
||||
|
||||
search = Order.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
if params[:format] == 'csv'
|
||||
search.result
|
||||
else
|
||||
present paginate(search.result, false), with: API::V2::Admin::Entities::Order
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Cancel an order.'
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.order.non_integer_id' },
|
||||
allow_blank: false,
|
||||
desc: -> { API::V2::Admin::Entities::Order.documentation[:id][:desc] }
|
||||
end
|
||||
post '/orders/:id/cancel' do
|
||||
admin_authorize! :update, ::Order
|
||||
|
||||
begin
|
||||
order = Order.find(params[:id])
|
||||
order.trigger_cancellation
|
||||
present order, with: API::V2::Admin::Entities::Order
|
||||
rescue ActiveRecord::RecordNotFound => e
|
||||
# RecordNotFound in rescued by ExceptionsHandler.
|
||||
raise(e)
|
||||
rescue
|
||||
error!({ errors: ['admin.order.cancel_error'] }, 422)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Cancel all orders.'
|
||||
params do
|
||||
requires :market,
|
||||
values: { value: -> { ::Market.active.ids }, message: 'admin.order.market_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Order.documentation[:id][:desc] }
|
||||
optional :side,
|
||||
values: { value: %w(sell buy), message: 'admin.order.invalid_side' },
|
||||
desc: 'If present, only sell orders (asks) or buy orders (bids) will be cancelled.'
|
||||
end
|
||||
post '/orders/cancel' do
|
||||
admin_authorize! :update, ::Order
|
||||
|
||||
begin
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(state: 'wait')
|
||||
.translate(market: :market_id)
|
||||
.merge({
|
||||
type_eq: params[:side].present? ? params[:side] == 'buy' ? 'OrderBid' : 'OrderAsk' : nil,
|
||||
}).build
|
||||
|
||||
orders = Order.ransack(ransack_params)
|
||||
orders.result.map(&:trigger_cancellation)
|
||||
present orders.result, with: API::V2::Entities::Order
|
||||
rescue
|
||||
error!({ errors: ['admin.order.cancel_error'] }, 422)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
68
app/api/v2/admin/trades.rb
Normal file
68
app/api/v2/admin/trades.rb
Normal file
@@ -0,0 +1,68 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'csv'
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Trades < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
content_type :csv, 'text/csv'
|
||||
|
||||
desc 'Get all trades, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Trade
|
||||
params do
|
||||
optional :market,
|
||||
values: { value: -> { ::Market.ids }, message: 'admin.market.doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:id][:desc] }
|
||||
optional :order_id,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Entities::Order.documentation[:id][:desc] }
|
||||
use :uid
|
||||
use :date_picker
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/trades' do
|
||||
admin_authorize! :read, Trade
|
||||
|
||||
member = Member.find_by(uid: params[:uid]) if params[:uid].present?
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params.except!(:uid))
|
||||
.translate(market: :market_id)
|
||||
.with_daterange
|
||||
.merge(g: [
|
||||
{ maker_id_eq: member&.id, taker_id_eq: member&.id, m: 'or' },
|
||||
{ maker_order_id_eq: params[:order_id], taker_order_id_eq: params[:order_id], m: 'or' },
|
||||
]).build
|
||||
|
||||
search = Trade.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
if params[:format] == 'csv'
|
||||
search.result
|
||||
else
|
||||
present paginate(search.result, false), with: API::V2::Admin::Entities::Trade
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get a trade with detailed information.' do
|
||||
success API::V2::Admin::Entities::Blockchain
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.trade.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Trade.documentation[:id][:desc] }
|
||||
end
|
||||
get '/trades/:id' do
|
||||
admin_authorize! :read, Trade
|
||||
|
||||
present Trade.find(params[:id]), with: API::V2::Admin::Entities::Trade, extended: true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
125
app/api/v2/admin/trading_fees.rb
Normal file
125
app/api/v2/admin/trading_fees.rb
Normal file
@@ -0,0 +1,125 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class TradingFees < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
desc 'Returns trading_fees table as paginated collection',
|
||||
is_array: true,
|
||||
success: API::V2::Entities::TradingFee
|
||||
params do
|
||||
optional :group,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:group][:desc] },
|
||||
coerce_with: ->(c) { c.strip.downcase }
|
||||
optional :market_id,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:market_id][:desc] },
|
||||
values: { value: -> { ::Market.ids.append(::TradingFee::ANY) },
|
||||
message: 'admin.trading_fee.market_doesnt_exist' }
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/trading_fees' do
|
||||
admin_authorize! :read, TradingFee
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:group, :market_id)
|
||||
.build
|
||||
|
||||
search = TradingFee.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result), with: API::V2::Entities::TradingFee
|
||||
end
|
||||
|
||||
desc 'It creates trading fees record',
|
||||
success: API::V2::Entities::TradingFee
|
||||
params do
|
||||
requires :maker,
|
||||
type: { value: BigDecimal, message: 'admin.trading_fee.non_decimal_maker' },
|
||||
values: { value: -> (p){ p && p >= 0 }, message: 'admin.trading_fee.invalid_maker' },
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:maker][:desc] }
|
||||
requires :taker,
|
||||
type: { value: BigDecimal, message: 'admin.trading_fee.non_decimal_taker' },
|
||||
values: { value: -> (p){ p && p >= 0 }, message: 'admin.trading_fee.invalid_taker' },
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:taker][:desc] }
|
||||
optional :group,
|
||||
type: String,
|
||||
default: ::TradingFee::ANY,
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:group][:desc] }
|
||||
optional :market_id,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:market_id][:desc] },
|
||||
default: ::TradingFee::ANY,
|
||||
values: { value: -> { ::Market.ids.append(::TradingFee::ANY) },
|
||||
message: 'admin.trading_fee.market_doesnt_exist' }
|
||||
end
|
||||
post '/trading_fees/new' do
|
||||
admin_authorize! :create, TradingFee
|
||||
|
||||
trading_fee = ::TradingFee.new(declared(params))
|
||||
if trading_fee.save
|
||||
present trading_fee, with: API::V2::Entities::TradingFee
|
||||
status 201
|
||||
else
|
||||
body errors: trading_fee.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'It updates trading fees record',
|
||||
success: API::V2::Entities::TradingFee
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.trading_fee.non_integer_id' },
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:id][:desc] }
|
||||
optional :maker,
|
||||
type: { value: BigDecimal, message: 'admin.trading_fee.non_decimal_maker' },
|
||||
values: { value: -> (p){ p && p >= 0 }, message: 'admin.trading_fee.invalid_maker' },
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:maker][:desc] }
|
||||
optional :taker,
|
||||
type: { value: BigDecimal, message: 'admin.trading_fee.non_decimal_taker' },
|
||||
values: { value: -> (p){ p && p >= 0 }, message: 'admin.trading_fee.invalid_taker' },
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:taker][:desc] }
|
||||
optional :group,
|
||||
type: String,
|
||||
coerce_with: ->(c) { c.strip.downcase },
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:group][:desc] }
|
||||
optional :market_id,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:market_id][:desc] },
|
||||
values: { value: -> { ::Market.ids.append(::TradingFee::ANY) },
|
||||
message: 'admin.trading_fee.market_doesnt_exist' }
|
||||
end
|
||||
post '/trading_fees/update' do
|
||||
admin_authorize! :update, TradingFee
|
||||
|
||||
trading_fee = ::TradingFee.find(params[:id])
|
||||
if trading_fee.update(declared(params, include_missing: false))
|
||||
present trading_fee, with: API::V2::Entities::TradingFee
|
||||
else
|
||||
body errors: trading_fee.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'It deletes trading fees record',
|
||||
success: API::V2::Entities::TradingFee
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.trading_fee.non_integer_id' },
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:id][:desc] }
|
||||
end
|
||||
post '/trading_fees/delete' do
|
||||
admin_authorize! :delete, TradingFee
|
||||
|
||||
present TradingFee.destroy(params[:id]), with: API::V2::Entities::TradingFee
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
249
app/api/v2/admin/wallets.rb
Normal file
249
app/api/v2/admin/wallets.rb
Normal file
@@ -0,0 +1,249 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Wallets < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
helpers do
|
||||
# Collection of shared params, used to
|
||||
# generate required/optional Grape params.
|
||||
OPTIONAL_WALLET_PARAMS ||= {
|
||||
max_balance: {
|
||||
type: { value: BigDecimal, message: 'admin.blockchain.non_decimal_max_balance' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.wallet.invalid_max_balance' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:max_balance][:desc] }
|
||||
},
|
||||
status: {
|
||||
values: { value: %w(active disabled), message: 'admin.wallet.invalid_status' },
|
||||
default: 'active',
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:status][:desc] }
|
||||
},
|
||||
}
|
||||
|
||||
params :create_wallet_params do
|
||||
OPTIONAL_WALLET_PARAMS.each do |key, params|
|
||||
optional key, params
|
||||
end
|
||||
end
|
||||
|
||||
params :update_wallet_params do
|
||||
OPTIONAL_WALLET_PARAMS.each do |key, params|
|
||||
optional key, params.except(:default)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get all wallets, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Wallet
|
||||
params do
|
||||
optional :blockchain_key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.currency.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:blockchain_key][:desc] }
|
||||
optional :kind,
|
||||
values: { value: -> { Wallet.kind.values }, message: 'admin.wallet.invalid_kind' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:kind][:desc] }
|
||||
optional :currencies,
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Currency.codes).blank? }, message: 'admin.wallet.currency_doesnt_exist' },
|
||||
types: [String, Array], coerce_with: ->(c) { Array.wrap(c) },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:currencies][:desc] }
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/wallets' do
|
||||
admin_authorize! :read, Wallet
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:blockchain_key)
|
||||
.translate_in(currencies: :currencies_id)
|
||||
.merge(kind_eq: params[:kind].present? ? Wallet.kinds[params[:kind].to_sym] : nil)
|
||||
.build
|
||||
|
||||
search = ::Wallet.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
present paginate(search.result.includes(:currencies).distinct), with: API::V2::Admin::Entities::Wallet
|
||||
end
|
||||
|
||||
desc 'List wallet kinds.'
|
||||
get '/wallets/kinds' do
|
||||
::Wallet.kind.values
|
||||
end
|
||||
|
||||
desc 'List wallet gateways.'
|
||||
get '/wallets/gateways' do
|
||||
::Wallet.gateways.map(&:to_s)
|
||||
end
|
||||
|
||||
desc 'Get a wallet.' do
|
||||
success API::V2::Admin::Entities::Wallet
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.wallet.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:id][:desc] }
|
||||
end
|
||||
get '/wallets/:id' do
|
||||
admin_authorize! :read, Wallet
|
||||
|
||||
present ::Wallet.find(params[:id]), with: API::V2::Admin::Entities::Wallet
|
||||
end
|
||||
|
||||
desc 'Creates new wallet.' do
|
||||
success API::V2::Admin::Entities::Wallet
|
||||
end
|
||||
params do
|
||||
use :create_wallet_params
|
||||
requires :blockchain_key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.wallet.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:blockchain_key][:desc] }
|
||||
requires :name,
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:name][:desc] }
|
||||
optional :address,
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:address][:desc] }
|
||||
optional :currencies,
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Currency.codes).blank? }, message: 'admin.wallet.currency_doesnt_exist' },
|
||||
types: [String, Array], coerce_with: ->(c) { Array.wrap(c) },
|
||||
as: :currency_ids,
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:currencies][:desc] }
|
||||
# @deprecated Please use `currencies` field
|
||||
optional :currency,
|
||||
values: { value: -> { ::Currency.codes }, message: 'admin.wallet.currency_doesnt_exist' },
|
||||
as: :currency_ids,
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:currencies][:desc] }
|
||||
requires :kind,
|
||||
values: { value: ::Wallet.kind.values, message: 'admin.wallet.invalid_kind' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:kind][:desc] }
|
||||
requires :gateway,
|
||||
values: { value: -> { ::Wallet.gateways.map(&:to_s) }, message: 'admin.wallet.gateway_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:gateway][:desc] }
|
||||
optional :settings, type: JSON,
|
||||
default: {},
|
||||
desc: -> { 'Wallet settings (uri, secret)' } do
|
||||
optional :uri,
|
||||
values: { value: ->(v) { URI.parse(v).is_a?(URI::HTTP) || URI.parse(v).is_a?(URI::HTTPS) }, message: 'admin.wallet.invalid_uri_setting' },
|
||||
desc: -> { 'Wallet uri setting' }
|
||||
optional :secret,
|
||||
desc: -> { 'Wallet secret setting' }
|
||||
end
|
||||
exactly_one_of :currencies, :currency, message: 'admin.wallet.currencies_field_is_missing'
|
||||
end
|
||||
post '/wallets/new' do
|
||||
admin_authorize! :create, Wallet
|
||||
|
||||
wallet = ::Wallet.new(declared(params))
|
||||
if wallet.save
|
||||
present wallet, with: API::V2::Admin::Entities::Wallet
|
||||
status 201
|
||||
else
|
||||
body errors: wallet.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update wallet.' do
|
||||
success API::V2::Admin::Entities::Wallet
|
||||
end
|
||||
params do
|
||||
use :update_wallet_params
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.wallet.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:id][:desc] }
|
||||
optional :blockchain_key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.wallet.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:blockchain_key][:desc] }
|
||||
optional :name,
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:name][:desc] }
|
||||
optional :address,
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:address][:desc] }
|
||||
optional :kind,
|
||||
values: { value: ::Wallet.kind.values, message: 'admin.wallet.invalid_kind' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:kind][:desc] }
|
||||
optional :gateway,
|
||||
values: { value: -> { ::Wallet.gateways.map(&:to_s) }, message: 'admin.wallet.gateway_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:gateway][:desc] }
|
||||
optional :currencies,
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Currency.codes).blank? }, message: 'admin.wallet.currency_doesnt_exist' },
|
||||
types: [String, Array], coerce_with: ->(c) { Array.wrap(c) },
|
||||
as: :currency_ids,
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:currencies][:desc] }
|
||||
optional :settings, type: JSON,
|
||||
desc: -> { 'Wallet settings' } do
|
||||
optional :uri,
|
||||
values: { value: ->(v) { URI.parse(v).is_a?(URI::HTTP) || URI.parse(v).is_a?(URI::HTTPS) }, message: 'admin.wallet.invalid_uri_setting' },
|
||||
desc: -> { 'Wallet uri setting' }
|
||||
optional :secret,
|
||||
desc: -> { 'Wallet secret setting' }
|
||||
end
|
||||
end
|
||||
post '/wallets/update' do
|
||||
admin_authorize! :update, Wallet
|
||||
wallet = ::Wallet.find(params[:id])
|
||||
|
||||
declared_params = declared(params, include_missing: false)
|
||||
declared_params.merge!(settings: params[:settings]) if params[:settings].present?
|
||||
if wallet.update(declared_params)
|
||||
present wallet, with: API::V2::Admin::Entities::Wallet
|
||||
else
|
||||
body errors: wallet.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Add currency to the wallet' do
|
||||
success API::V2::Admin::Entities::Wallet
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.wallet.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:id][:desc] }
|
||||
requires :currencies,
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Currency.codes).blank? }, message: 'admin.wallet.currency_doesnt_exist' },
|
||||
types: [String, Array], coerce_with: ->(c) { Array.wrap(c) },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:currencies][:desc] }
|
||||
end
|
||||
post '/wallets/currencies' do
|
||||
wallet = Wallet.find(params[:id])
|
||||
|
||||
wallet.transaction do
|
||||
params[:currencies].each do |c_id|
|
||||
c_w = CurrencyWallet.new(currency_id: c_id, wallet_id: params[:id])
|
||||
error!({ errors: c_w.errors.full_messages }, 422) unless c_w.save
|
||||
end
|
||||
end
|
||||
|
||||
present wallet, with: API::V2::Admin::Entities::Wallet
|
||||
status 201
|
||||
end
|
||||
|
||||
desc 'Delete currency from the wallet' do
|
||||
success API::V2::Admin::Entities::Wallet
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.wallet.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:id][:desc] }
|
||||
requires :currencies,
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Currency.codes).blank? }, message: 'admin.wallet.currency_doesnt_exist' },
|
||||
types: [String, Array], coerce_with: ->(c) { Array.wrap(c) },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:currencies][:desc] }
|
||||
end
|
||||
delete '/wallets/currencies' do
|
||||
wallet = Wallet.find(params[:id])
|
||||
wallet.transaction do
|
||||
params[:currencies].each do |c_id|
|
||||
# Check if exist (will return error)
|
||||
CurrencyWallet.find_by!(currency_id: c_id, wallet_id: params[:id])
|
||||
# Delete relation
|
||||
CurrencyWallet.where(currency_id: c_id, wallet_id: params[:id]).delete_all
|
||||
end
|
||||
end
|
||||
|
||||
present wallet, with: API::V2::Admin::Entities::Wallet
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
137
app/api/v2/admin/whitelisted_smart_contracts.rb
Normal file
137
app/api/v2/admin/whitelisted_smart_contracts.rb
Normal file
@@ -0,0 +1,137 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class WhitelistedSmartContracts < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
content_type :csv, 'text/csv'
|
||||
|
||||
desc 'Get all whitelisted addresses, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
params do
|
||||
optional :blockchain_key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.whitelistedsmartcontract.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:blockchain_key][:desc] }
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/whitelisted_smart_contracts' do
|
||||
admin_authorize! :read, WhitelistedSmartContract
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params).eq(:blockchain_key).build
|
||||
|
||||
search = ::WhitelistedSmartContract.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
if params[:format] == 'csv'
|
||||
search.result
|
||||
else
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get a whitelisted address.' do
|
||||
success API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.whitelistedsmartcontract.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:id][:desc] }
|
||||
end
|
||||
get '/whitelisted_smart_contract/:id' do
|
||||
admin_authorize! :read, WhitelistedSmartContract
|
||||
|
||||
present ::WhitelistedSmartContract.find(params[:id]), with: API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
end
|
||||
|
||||
desc 'Creates new whitelisted address.' do
|
||||
success API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
end
|
||||
params do
|
||||
requires :blockchain_key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.whitelistedsmartcontract.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:blockchain_key][:desc] }
|
||||
requires :address,
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:address][:desc] }
|
||||
optional :description,
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:description][:desc] }
|
||||
optional :state,
|
||||
values: { value: %w[active disabled], message: 'admin.whitelistedsmartcontract.invalid_state' },
|
||||
default: 'active',
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:state][:desc] }
|
||||
end
|
||||
post '/whitelisted_smart_contracts' do
|
||||
admin_authorize! :create, WhitelistedSmartContract
|
||||
|
||||
whitelisted_address = ::WhitelistedSmartContract.new(declared(params))
|
||||
if whitelisted_address.save
|
||||
present whitelisted_address, with: API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
else
|
||||
body errors: whitelisted_address.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update whitelisted_smart_contract.' do
|
||||
success API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.whitelistedsmartcontract.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:id][:desc] }
|
||||
optional :blockchain_key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.whitelistedsmartcontract.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:blockchain_key][:desc] }
|
||||
optional :description,
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:description][:desc] }
|
||||
optional :address,
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:address][:desc] }
|
||||
optional :state,
|
||||
values: { value: %w[active disabled], message: 'admin.whitelistedsmartcontract.invalid_state' },
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:state][:desc] }
|
||||
end
|
||||
put '/whitelisted_smart_contracts' do
|
||||
admin_authorize! :update, WhitelistedSmartContract
|
||||
whitelisted_address = ::WhitelistedSmartContract.find(params[:id])
|
||||
declared_params = declared(params, include_missing: false)
|
||||
|
||||
if whitelisted_address.update(declared_params)
|
||||
present whitelisted_address, with: API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
else
|
||||
body errors: whitelisted_address.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Process whitelisted smart contracts from csv' do
|
||||
success API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
end
|
||||
params do
|
||||
requires :file,
|
||||
type: File,
|
||||
desc: -> {'CSV file with whitelisted smart contracts data'}
|
||||
end
|
||||
post '/whitelisted_smart_contracts/csv' do
|
||||
admin_authorize! :create, WhitelistedSmartContract
|
||||
count = 0
|
||||
|
||||
CSV.parse(params[:file][:tempfile], headers: true, quote_empty: false).each do |row|
|
||||
row = row.to_h.compact.symbolize_keys!
|
||||
address = row[:address]
|
||||
blockchain_key = row[:blockchain_key]
|
||||
description = row[:description]
|
||||
next if address.blank? || blockchain_key.blank? || ::Blockchain.pluck(:key).exclude?(blockchain_key)
|
||||
|
||||
::WhitelistedSmartContract.create!(description: description, address: address,
|
||||
blockchain_key: blockchain_key, state: 'active')
|
||||
count += 1
|
||||
end
|
||||
|
||||
present ::WhitelistedSmartContract.last(count), with: API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
127
app/api/v2/admin/withdraw_limits.rb
Normal file
127
app/api/v2/admin/withdraw_limits.rb
Normal file
@@ -0,0 +1,127 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class WithdrawLimits < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
desc 'Returns withdraw limits table as paginated collection',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::WithdrawLimit
|
||||
params do
|
||||
optional :group,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:group][:desc] },
|
||||
coerce_with: ->(c) { c.strip.downcase }
|
||||
optional :kyc_level,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:kyc_level][:desc] }
|
||||
optional :kind,
|
||||
values: { value: ->(v) { (Array.wrap(v) - WithdrawLimit.kind.values).blank? }, message: 'limit.kind.invalid' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:kind][:desc] }
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/withdraw_limits' do
|
||||
admin_authorize! :read, WithdrawLimit
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:group, :kyc_level, :kind)
|
||||
.build
|
||||
|
||||
search = WithdrawLimit.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result), with: API::V2::Entities::WithdrawLimit
|
||||
end
|
||||
|
||||
desc 'It creates withdraw limits record',
|
||||
success: API::V2::Entities::WithdrawLimit
|
||||
params do
|
||||
requires :limit_24_hour,
|
||||
type: { value: BigDecimal, message: 'admin.withdraw_limit.non_decimal_limit_24_hour' },
|
||||
values: { value: ->(p) { p && p >= 0 }, message: 'admin.withdraw_limit.invalid_limit_24_hour' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:limit_24_hour][:desc] }
|
||||
requires :limit_1_month,
|
||||
type: { value: BigDecimal, message: 'admin.withdraw_limit.non_decimal_limit_1_month' },
|
||||
values: { value: ->(p) { p && p >= 0 }, message: 'admin.withdraw_limit.invalid_limit_1_month' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:limit_1_month][:desc] }
|
||||
requires :kind,
|
||||
values: { value: ->(v) { (Array.wrap(v) - WithdrawLimit.kind.values).blank? }, message: 'limit.kind.invalid' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:kind][:desc] }
|
||||
optional :group,
|
||||
type: String,
|
||||
default: ::WithdrawLimit::ANY,
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:group][:desc] }
|
||||
optional :kyc_level,
|
||||
type: String,
|
||||
default: ::WithdrawLimit::ANY,
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:kyc_level][:desc] }
|
||||
end
|
||||
post '/withdraw_limits' do
|
||||
admin_authorize! :create, WithdrawLimit
|
||||
|
||||
withdraw_limit = ::WithdrawLimit.new(declared(params))
|
||||
if withdraw_limit.save
|
||||
present withdraw_limit, with: API::V2::Entities::WithdrawLimit
|
||||
status 201
|
||||
else
|
||||
body errors: withdraw_limit.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'It updates withdraw limits record',
|
||||
success: API::V2::Entities::WithdrawLimit
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.withdraw_limit.non_integer_id' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:id][:desc] }
|
||||
optional :limit_24_hour,
|
||||
type: { value: BigDecimal, message: 'admin.withdraw_limit.non_decimal_limit_24_hour' },
|
||||
values: { value: ->(p) { p && p >= 0 }, message: 'admin.withdraw_limit.invalid_limit_24_hour' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:limit_24_hour][:desc] }
|
||||
optional :limit_1_month,
|
||||
type: { value: BigDecimal, message: 'admin.withdraw_limit.non_decimal_limit_1_month' },
|
||||
values: { value: ->(p) { p && p >= 0 }, message: 'admin.withdraw_limit.invalid_limit_1_month' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:limit_1_month][:desc] }
|
||||
optional :kind,
|
||||
values: { value: ->(v) { (Array.wrap(v) - WithdrawLimit.kind.values).blank? }, message: 'limit.kind.invalid' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:kind][:desc] }
|
||||
optional :kyc_level,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:kyc_level][:desc] }
|
||||
optional :group,
|
||||
type: String,
|
||||
coerce_with: ->(c) { c.strip.downcase },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:group][:desc] }
|
||||
end
|
||||
put '/withdraw_limits' do
|
||||
admin_authorize! :update, WithdrawLimit
|
||||
|
||||
withdraw_limit = ::WithdrawLimit.find(params[:id])
|
||||
if withdraw_limit.update(declared(params, include_missing: false))
|
||||
present withdraw_limit, with: API::V2::Entities::WithdrawLimit
|
||||
else
|
||||
body errors: withdraw_limit.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'It deletes withdraw limits record',
|
||||
success: API::V2::Entities::WithdrawLimit
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.withdraw_limit.non_integer_id' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:id][:desc] }
|
||||
end
|
||||
delete '/withdraw_limits/:id' do
|
||||
admin_authorize! :delete, WithdrawLimit
|
||||
|
||||
present WithdrawLimit.destroy(params[:id]), with: API::V2::Entities::WithdrawLimit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
149
app/api/v2/admin/withdraws.rb
Normal file
149
app/api/v2/admin/withdraws.rb
Normal file
@@ -0,0 +1,149 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Withdraws < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
desc 'Get all withdraws, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Withdraw
|
||||
params do
|
||||
optional :state,
|
||||
values: { value: ->(v) { (Array.wrap(v) - Withdraw::STATES.map(&:to_s)).blank? }, message: 'admin.withdraw.invalid_state' },
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:state][:desc] }
|
||||
optional :id,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:id][:desc] }
|
||||
optional :txid,
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:blockchain_txid][:desc] }
|
||||
optional :tid,
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:tid][:desc] }
|
||||
optional :confirmations,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:confirmations][:desc] }
|
||||
optional :rid,
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:rid][:desc] }
|
||||
optional :wallet_type,
|
||||
values: { value: ->(v) { (Array.wrap(v.to_sym) - Wallet.gateways).blank? }, message: 'admin.withdraw.invalid_wallet_type' },
|
||||
desc: -> { 'Select withdraw that can be processed from wallets with given type e.g. patiry' }
|
||||
use :uid
|
||||
use :currency
|
||||
use :currency_type
|
||||
use :date_picker
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/withdraws' do
|
||||
admin_authorize! :read, Withdraw
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:id, :txid, :rid, :tid)
|
||||
.translate(uid: :member_uid, currency: :currency_id)
|
||||
.with_daterange
|
||||
.merge(type_eq: params[:type].present? ? "Withdraws::#{params[:type].capitalize}" : nil)
|
||||
.merge(aasm_state_in: params[:state])
|
||||
.build
|
||||
|
||||
search = Withdraw.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
if params[:wallet_type].present?
|
||||
present paginate(search.result
|
||||
.where(currency: Currency.joins(:wallets)
|
||||
.where(wallets: { id: Wallet.where(gateway: params[:wallet_type]) }))),
|
||||
with: API::V2::Admin::Entities::Withdraw
|
||||
else
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::Withdraw
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get withdraw by ID.',
|
||||
success: API::V2::Admin::Entities::Withdraw
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.withdraw.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:id][:desc] }
|
||||
end
|
||||
get '/withdraws/:id' do
|
||||
admin_authorize! :read, Withdraw
|
||||
|
||||
withdraw = Withdraw.find_by!(id: params[:id])
|
||||
present withdraw,
|
||||
with: API::V2::Admin::Entities::Withdraw,
|
||||
with_beneficiary: true
|
||||
end
|
||||
|
||||
desc 'Take an action on the withdrawal.',
|
||||
success: API::V2::Admin::Entities::Withdraw
|
||||
params do
|
||||
requires :id,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:id][:desc] }
|
||||
requires :action,
|
||||
type: String,
|
||||
values: { value: -> { ::Withdraw.aasm.events.map(&:name).map(&:to_s) }, message: 'admin.withdraw.invalid_action' },
|
||||
desc: "Valid actions are #{::Withdraw.aasm.events.map(&:name)}."
|
||||
given action: ->(action) { %w[load dispatch success].include?(action) } do
|
||||
optional :txid,
|
||||
type: String,
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:blockchain_txid][:desc] }
|
||||
end
|
||||
end
|
||||
post '/withdraws/actions' do
|
||||
admin_authorize! :update, Withdraw
|
||||
|
||||
declared_params = declared(params, include_missing: false)
|
||||
|
||||
withdraw = Withdraw.find(declared_params[:id])
|
||||
|
||||
if withdraw.currency.fiat? && declared_params[:txid].present?
|
||||
error!({ errors: ['admin.withdraw.redundant_txid'] }, 422)
|
||||
end
|
||||
|
||||
transited = withdraw.transaction do
|
||||
withdraw.update!(txid: declared_params[:txid]) if declared_params[:txid].present?
|
||||
withdraw.public_send("#{declared_params[:action]}!").tap do |success|
|
||||
raise ActiveRecord::Rollback unless success
|
||||
end
|
||||
rescue StandardError
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
if transited
|
||||
present withdraw, with: API::V2::Admin::Entities::Withdraw
|
||||
else
|
||||
body errors: ["admin.withdraw.cannot_#{declared_params[:action]}"]
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update withdraw request',
|
||||
success: API::V2::Admin::Entities::Withdraw
|
||||
params do
|
||||
requires :id,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:id][:desc] }
|
||||
optional :metadata,
|
||||
type: JSON,
|
||||
desc: 'Optional metadata to be applied to the transaction.'
|
||||
end
|
||||
put '/withdraws' do
|
||||
admin_authorize! :update, Withdraw
|
||||
|
||||
declared_params = declared(params, include_missing: false)
|
||||
withdraw = Withdraw.find(declared_params[:id])
|
||||
|
||||
declared_params[:metadata] = withdraw.metadata.merge(declared_params[:metadata]) if declared_params[:metadata].present?
|
||||
if withdraw.update(declared_params)
|
||||
present withdraw, with: API::V2::Admin::Entities::Withdraw
|
||||
else
|
||||
body errors: withdraw.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
17
app/api/v2/auth/utils.rb
Normal file
17
app/api/v2/auth/utils.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Auth
|
||||
module Utils
|
||||
class << self
|
||||
def cache
|
||||
# Simply use rack-attack cache wrapper
|
||||
@cache ||= Rack::Attack::Cache.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
49
app/api/v2/coin_gecko/entities/orderbook.rb
Normal file
49
app/api/v2/coin_gecko/entities/orderbook.rb
Normal file
@@ -0,0 +1,49 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
module Entities
|
||||
class Orderbook < API::V2::Entities::Base
|
||||
expose(
|
||||
:ticker_id,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'A pair such as "BTC_ETH", with delimiter between different cryptoassets.'
|
||||
}
|
||||
) do |orderbook|
|
||||
orderbook.market.underscore_name
|
||||
end
|
||||
|
||||
expose(
|
||||
:timestamp,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Unix timestamp in milliseconds for when the last updated time occurred'
|
||||
}
|
||||
) do
|
||||
DateTime.now.strftime('%Q').to_i
|
||||
end
|
||||
|
||||
expose(
|
||||
:asks,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
is_array: true,
|
||||
desc: 'An array containing 2 elements. The offer price and quantity for each bid order.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:bids,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
is_array: true,
|
||||
desc: 'An array containing 2 elements. The ask price and quantity for each ask order.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
41
app/api/v2/coin_gecko/entities/pair.rb
Normal file
41
app/api/v2/coin_gecko/entities/pair.rb
Normal file
@@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
module Entities
|
||||
class Pair < API::V2::Entities::Base
|
||||
expose(
|
||||
:ticker_id,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Identifier of a ticker with delimiter to separate base/target, eg. BTC_ETH.'
|
||||
}
|
||||
) do |market|
|
||||
market.underscore_name
|
||||
end
|
||||
|
||||
expose(
|
||||
:base,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Symbol/currency code of a the base cryptoasset, eg. BTC.'
|
||||
}
|
||||
) do |market|
|
||||
market[:base_unit].upcase
|
||||
end
|
||||
|
||||
expose(
|
||||
:target,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Symbol/currency code of the target cryptoasset, eg. ETH.'
|
||||
}
|
||||
) do |market|
|
||||
market[:quote_unit].upcase
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
111
app/api/v2/coin_gecko/entities/ticker.rb
Normal file
111
app/api/v2/coin_gecko/entities/ticker.rb
Normal file
@@ -0,0 +1,111 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
module Entities
|
||||
class Ticker < API::V2::Entities::Base
|
||||
expose(
|
||||
:ticker_id,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Identifier of a ticker with delimiter to separate base/target, eg. BTC_ETH.'
|
||||
}
|
||||
) do |ticker|
|
||||
ticker[:market].underscore_name
|
||||
end
|
||||
|
||||
expose(
|
||||
:base_currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Symbol/currency code of base pair, eg. BTC.'
|
||||
}
|
||||
) do |ticker|
|
||||
ticker[:market][:base_unit].upcase
|
||||
end
|
||||
|
||||
expose(
|
||||
:target_currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Symbol/currency code of target pair, eg. ETH.'
|
||||
}
|
||||
) do |ticker|
|
||||
ticker[:market][:quote_unit].upcase
|
||||
end
|
||||
|
||||
expose(
|
||||
:last_price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Last transacted price of base currency based on given target currency.'
|
||||
}
|
||||
) do |ticker|
|
||||
ticker[:last]
|
||||
end
|
||||
|
||||
expose(
|
||||
:base_volume,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: '24 hour trading volume in base pair volume.'
|
||||
}
|
||||
) do |ticker|
|
||||
ticker[:amount]
|
||||
end
|
||||
|
||||
expose(
|
||||
:target_volume,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: '24 hour trading volume in base pair volume.'
|
||||
}
|
||||
) do |ticker|
|
||||
ticker[:volume]
|
||||
end
|
||||
|
||||
expose(
|
||||
:bid,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Current highest bid price.'
|
||||
}
|
||||
) do |ticker|
|
||||
OrderBid.get_depth(ticker[:market].id).flatten.first.to_d
|
||||
end
|
||||
|
||||
expose(
|
||||
:ask,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Current lowest ask price.'
|
||||
}
|
||||
) do |ticker|
|
||||
OrderAsk.get_depth(ticker[:market].id).flatten.first.to_d
|
||||
end
|
||||
|
||||
expose(
|
||||
:high,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'The highest trade price during last 24 hours (0.0 if no trades executed during last 24 hours).'
|
||||
}
|
||||
) do |ticker|
|
||||
ticker[:high]
|
||||
end
|
||||
|
||||
expose(
|
||||
:low,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'The lowest trade price during last 24 hours (0.0 if no trades executed during last 24 hours).'
|
||||
}
|
||||
) do |ticker|
|
||||
ticker[:low]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
22
app/api/v2/coin_gecko/helpers.rb
Normal file
22
app/api/v2/coin_gecko/helpers.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
module Helpers
|
||||
MILLISECONDS_IN_SECOND = 1000
|
||||
|
||||
def format_trade(trade)
|
||||
{
|
||||
trade_id: trade[:id],
|
||||
price: trade[:price],
|
||||
base_volume: trade[:amount],
|
||||
target_volume: trade[:total],
|
||||
trade_timestamp: trade[:created_at] * MILLISECONDS_IN_SECOND,
|
||||
type: trade[:taker_type]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
43
app/api/v2/coin_gecko/historical_trades.rb
Normal file
43
app/api/v2/coin_gecko/historical_trades.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
class HistoricalTrades < Grape::API
|
||||
desc 'Get recent trades on market'
|
||||
params do
|
||||
requires :ticker_id,
|
||||
type: String,
|
||||
desc: 'A pair such as "LTC_BTC"',
|
||||
coerce_with: ->(name) { name.strip.split('_').join.downcase }
|
||||
optional :type,
|
||||
type: String,
|
||||
values: { value: %w(buy sell), message: 'coingecko.historical_trades.invalid_type' },
|
||||
desc: 'To indicate nature of trade - buy/sell'
|
||||
optional :limit,
|
||||
type: Integer,
|
||||
values: { value: 0..1000, message: 'coingecko.historical_trades.invalid_limit' },
|
||||
desc: 'Number of historical trades to retrieve from time of query. [0, 200, 500...]. 0 returns full history'
|
||||
optional :start_time,
|
||||
type: Integer,
|
||||
desc: '',
|
||||
coerce_with: ->(start_time) { Time.parse(start_time).to_i }
|
||||
optional :end_time,
|
||||
type: Integer,
|
||||
desc: '',
|
||||
coerce_with: ->(end_time) { Time.parse(end_time).to_i }
|
||||
end
|
||||
get '/historical_trades' do
|
||||
market = ::Market.find(params[:ticker_id])
|
||||
|
||||
filters = declared(params, include_missing: false)
|
||||
.except(:ticker_id, :limit)
|
||||
|
||||
Trade.public_from_influx(market.id, params[:limit], filters).each_with_object({'buy' => [], 'sell' => []}) do |trade, hash|
|
||||
hash[trade[:taker_type]] << format_trade(trade)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
20
app/api/v2/coin_gecko/mount.rb
Normal file
20
app/api/v2/coin_gecko/mount.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
class Mount < Grape::API
|
||||
PREFIX = '/coingecko'
|
||||
|
||||
before { set_ets_context! }
|
||||
|
||||
helpers CoinGecko::Helpers
|
||||
|
||||
mount CoinGecko::Pairs
|
||||
mount CoinGecko::Tickers
|
||||
mount CoinGecko::Orderbook
|
||||
mount CoinGecko::HistoricalTrades
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
39
app/api/v2/coin_gecko/orderbook.rb
Normal file
39
app/api/v2/coin_gecko/orderbook.rb
Normal file
@@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
class Orderbook < Grape::API
|
||||
class OrderBook < Struct.new(:market, :asks, :bids); end
|
||||
|
||||
desc 'Get depth or specified market'
|
||||
params do
|
||||
requires :ticker_id,
|
||||
type: String,
|
||||
desc: 'A pair such as "LTC_BTC"',
|
||||
coerce_with: ->(name) { name.strip.split('_').join.downcase }
|
||||
optional :depth,
|
||||
type: { value: Integer, message: 'coingecko.market_depth.non_integer_depth' },
|
||||
values: { value: 0..1000, message: 'coingecko.market_depth.invalid_depth' },
|
||||
desc: 'Orders depth quantity: [0, 100, 200, 500...]'
|
||||
end
|
||||
|
||||
get '/orderbook' do
|
||||
market = ::Market.find(params[:ticker_id])
|
||||
asks = OrderAsk.get_depth(market.id)
|
||||
bids = OrderBid.get_depth(market.id)
|
||||
|
||||
# Depth = 100 means 50 for each bid/ask side
|
||||
# Not defined or 0 = full order book
|
||||
unless params[:depth].to_d.zero?
|
||||
asks = asks[0, params[:depth]/2]
|
||||
bids = bids[0, params[:depth]/2]
|
||||
end
|
||||
|
||||
orderbook = OrderBook.new market, asks, bids
|
||||
present orderbook, with: API::V2::CoinGecko::Entities::Orderbook
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
16
app/api/v2/coin_gecko/pairs.rb
Normal file
16
app/api/v2/coin_gecko/pairs.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
class Pairs < Grape::API
|
||||
desc 'Get list of all available trading pairs'
|
||||
get "/pairs" do
|
||||
present ::Rails.cache.fetch(:markets_coingecko, expires_in: 60) { ::Market.enabled.ordered },
|
||||
with: API::V2::CoinGecko::Entities::Pair
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
21
app/api/v2/coin_gecko/tickers.rb
Normal file
21
app/api/v2/coin_gecko/tickers.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
class Tickers < Grape::API
|
||||
desc 'Get list of all available trading pairs'
|
||||
get "/tickers" do
|
||||
tickers = ::Rails.cache.fetch(:markets_tickers_coingecko, expires_in: 60) do
|
||||
::Market.enabled.ordered.inject([]) do |hash, market|
|
||||
hash << TickersService[market].ticker.merge(market: market)
|
||||
end
|
||||
end
|
||||
|
||||
present tickers, with: API::V2::CoinGecko::Entities::Ticker
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
16
app/api/v2/coin_market_cap/assets.rb
Normal file
16
app/api/v2/coin_market_cap/assets.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinMarketCap
|
||||
class Assets < Grape::API
|
||||
desc 'Details on crypto currencies available on the exchange'
|
||||
get '/assets' do
|
||||
::Rails.cache.fetch(:currencies_cmc, expires_in: 600) do
|
||||
format_currencies(Currency.visible.coins.ordered.map)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
93
app/api/v2/coin_market_cap/helpers.rb
Normal file
93
app/api/v2/coin_market_cap/helpers.rb
Normal file
@@ -0,0 +1,93 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinMarketCap
|
||||
module Helpers
|
||||
MILLISECONDS_IN_SECOND = 1000
|
||||
|
||||
def format_summary(ticker, market)
|
||||
lowest_ask = OrderAsk.get_depth(market.id)
|
||||
highest_bid = OrderBid.get_depth(market.id)
|
||||
{
|
||||
trading_pairs: market.underscore_name, # mandatory [string]: Identifier of a ticker with delimiter to separate base/quote
|
||||
base_currency: market.base_unit.upcase, # recommended [string]: Symbol/currency code of base currency
|
||||
quote_currency: market.quote_unit.upcase, # recommended [string]: Symbol/currency code of base currency
|
||||
last_price: ticker[:last], # mandatory [decimal]: Last transacted price of base currency based on given quote currency
|
||||
lowest_ask: lowest_ask.flatten.first.to_d, # mandatory [decimal]: Lowest Ask price of base currency based on given quote currency
|
||||
highest_bid: highest_bid.flatten.first.to_d, # mandatory [decimal]: Highest bid price of base currency based on given quote currency
|
||||
base_volume: ticker[:amount], # mandatory [decimal]: 24-hr volume of market pair denoted in BASE currency
|
||||
quote_volume: ticker[:volume], # mandatory [decimal]: 24-hr volume of market pair denoted in QUOTE currency
|
||||
price_change_percent_24h: price_change_percent_24h(ticker), # mandatory [decimal]: 24-hr % price change of market pair
|
||||
highest_price_24h: ticker[:high], # mandatory [decimal]: Highest price of base currency based on given quote currency in the last 24-hrs
|
||||
lowest_price_24h: ticker[:low] # mandatory [decimal]: Lowest price of base currency based on given quote currency in the last 24-hrs
|
||||
}
|
||||
end
|
||||
|
||||
def format_tickers(markets)
|
||||
markets.each_with_object({}) do |market, h|
|
||||
ticker = TickersService[market].ticker
|
||||
unified_base_crypto_id = market.base.coin? ? unified_cryptoasset_id(market.base_unit) : nil
|
||||
unified_quote_crypto_id = market.quote.coin? ? unified_cryptoasset_id(market.quote_unit) : nil
|
||||
h[market.underscore_name.to_s] = {
|
||||
base_id: unified_base_crypto_id, # recommended [integer]: The quote pair Unified Cryptoasset ID
|
||||
quote_id: unified_quote_crypto_id, # recommended [integer]: The base pair Unified Cryptoasset ID
|
||||
last_price: ticker[:last], # mandatory [decimal]: Last transacted price of base currency based on given quote currenc
|
||||
base_volume: ticker[:amount], # mandatory [decimal]: 24-hour trading volume denoted in BASE currency
|
||||
quote_volume: ticker[:volume], # mandatory [decimal]: 24 hour trading volume denoted in QUOTE currency
|
||||
isFrozen: market.state == 'enabled' ? 0 : 1 # recommended [integer]: Indicates if the market is currently enabled (0) or disabled (1)
|
||||
}.compact
|
||||
end
|
||||
end
|
||||
|
||||
def format_trade(trade)
|
||||
{
|
||||
trade_id: trade[:id], # mandatory [integer]: A unique ID associated with the trade for the currency pair transaction
|
||||
price: trade[:price], # mandatory [decimal]: Last transacted price of base currency based on given quote currency
|
||||
base_volume: trade[:amount], # mandatory [decimal]: Transaction amount in BASE currency
|
||||
quote_volume: trade[:total], # mandatory [decimal]: Transaction amount in QUOTE currency
|
||||
timestamp: trade[:created_at] * MILLISECONDS_IN_SECOND, # mandatory [integer]: Unix timestamp in milliseconds for when the transaction occurred
|
||||
type: trade[:taker_type] # mandatory [string]: Used to determine whether or not the transaction originated as a buy or sell
|
||||
}
|
||||
end
|
||||
|
||||
def format_orderbook(asks, bids)
|
||||
{
|
||||
timestamp: DateTime.now.strftime('%Q').to_i, # mandotory [decimal]: Unix timestamp in milliseconds for when the last updated time occurred
|
||||
asks: asks, # mandotory [decimal]: The offer price and quantity for each bid order
|
||||
bids: bids # mandotory [decimal]: The ask price and quantity for each ask order.
|
||||
}
|
||||
end
|
||||
|
||||
def format_currencies(currencies)
|
||||
currencies.each_with_object({}) do |currency, h|
|
||||
h[currency.id.upcase.to_s] = {
|
||||
name: currency.name, # recommended [string]: Full name of cryptocurrency
|
||||
unified_cryptoasset_id: unified_cryptoasset_id(currency.id), # recommended [integer]: Unique ID of cryptocurrency assigned by Unified Cryptoasset ID
|
||||
can_withdraw: currency.withdrawal_enabled, # recommended [boolean]: Identifies whether withdrawals are enabled or disabled
|
||||
can_deposit: currency.deposit_enabled, # recommended [boolean]: Identifies whether deposits are enabled or disabled
|
||||
min_withdraw: currency.min_withdraw_amount # recommended [decimal]: Identifies the single minimum withdrawal amount of a cryptocurrency
|
||||
}.compact
|
||||
end
|
||||
end
|
||||
|
||||
# Only for crypto currencies
|
||||
def unified_cryptoasset_id(currency_id)
|
||||
# System will get response in such format [{:id=>1,:name=>"Bitcoin",:symbol=>"BTC"}]
|
||||
::CoinMarketCap.default_client.get(symbol: currency_id)[0][:id]
|
||||
rescue ::Faraday::Error, ::CoinMarketCap::Error => e
|
||||
Rails.logger.warn e
|
||||
# In format methods we skipped all nil values with compact method
|
||||
# As unified_cryptoasset_id is only recommended field
|
||||
nil
|
||||
end
|
||||
|
||||
# System use this methods instead of ticker[:price_change_percent]
|
||||
# because we dont need percentage symbols here
|
||||
def price_change_percent_24h(ticker)
|
||||
ticker[:open].to_d.zero? ? '0.0' : (ticker[:last].to_d - ticker[:open].to_d) / ticker[:open].to_d
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
19
app/api/v2/coin_market_cap/mount.rb
Normal file
19
app/api/v2/coin_market_cap/mount.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinMarketCap
|
||||
class Mount < Grape::API
|
||||
before { set_ets_context! }
|
||||
|
||||
helpers CoinMarketCap::Helpers
|
||||
|
||||
mount CoinMarketCap::Summary
|
||||
mount CoinMarketCap::Assets
|
||||
mount CoinMarketCap::Ticker
|
||||
mount CoinMarketCap::Trades
|
||||
mount CoinMarketCap::Orderbook
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
35
app/api/v2/coin_market_cap/orderbook.rb
Normal file
35
app/api/v2/coin_market_cap/orderbook.rb
Normal file
@@ -0,0 +1,35 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinMarketCap
|
||||
class Orderbook < Grape::API
|
||||
desc 'Get depth or specified market'
|
||||
params do
|
||||
requires :market_pair,
|
||||
type: String,
|
||||
desc: 'A pair such as "LTC_BTC"',
|
||||
coerce_with: ->(name) { name.strip.split('_').join.downcase }
|
||||
optional :depth,
|
||||
type: { value: Integer, message: 'coinmarketcap.market_depth.non_integer_depth' },
|
||||
values: { value: 0..500, message: 'coinmarketcap.market_depth.invalid_depth' },
|
||||
desc: 'Orders depth quantity: [0,5,10,20,50,100,500]'
|
||||
end
|
||||
get "/orderbook/:market_pair" do
|
||||
market = ::Market.find(params[:market_pair])
|
||||
asks = OrderAsk.get_depth(market.id)
|
||||
bids = OrderBid.get_depth(market.id)
|
||||
|
||||
# Depth = 100 means 50 for each bid/ask side
|
||||
# Not defined or 0 = full order book
|
||||
unless params[:depth].to_d.zero?
|
||||
asks = asks[0, params[:depth]/2]
|
||||
bids = bids[0, params[:depth]/2]
|
||||
end
|
||||
|
||||
format_orderbook(asks, bids)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
18
app/api/v2/coin_market_cap/summary.rb
Normal file
18
app/api/v2/coin_market_cap/summary.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinMarketCap
|
||||
class Summary < Grape::API
|
||||
desc 'Overview of market data for all tickers and all market pairs on the exchange'
|
||||
get '/summary' do
|
||||
::Rails.cache.fetch(:markets_summary_cmc, expires_in: 60) do
|
||||
::Market.enabled.ordered.map do |market|
|
||||
format_summary(TickersService[market].ticker, market)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
16
app/api/v2/coin_market_cap/ticker.rb
Normal file
16
app/api/v2/coin_market_cap/ticker.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinMarketCap
|
||||
class Ticker < Grape::API
|
||||
desc 'Get 24-hour pricing and volume summary for each market pair'
|
||||
get '/ticker' do
|
||||
::Rails.cache.fetch(:markets_tickers_cmc, expires_in: 60) do
|
||||
format_tickers(::Market.enabled.ordered)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
21
app/api/v2/coin_market_cap/trades.rb
Normal file
21
app/api/v2/coin_market_cap/trades.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinMarketCap
|
||||
class Trades < Grape::API
|
||||
desc 'Get recent trades on market'
|
||||
params do
|
||||
requires :market_pair,
|
||||
type: String,
|
||||
desc: 'A pair such as "LTC_BTC"',
|
||||
coerce_with: ->(name) { name.strip.split('_').join.downcase }
|
||||
end
|
||||
get "/trades/:market_pair" do
|
||||
market = ::Market.find(params[:market_pair])
|
||||
Trade.public_from_influx(market.id).map { |trade| format_trade(trade) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
22
app/api/v2/constraints.rb
Normal file
22
app/api/v2/constraints.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Constraints
|
||||
class << self
|
||||
def included(base)
|
||||
rate_limit = ENV.fetch("PEATIO_RATE_LIMIT_5MIN", 6000).to_i
|
||||
apply_rules!(rate_limit)
|
||||
base.use Rack::Attack
|
||||
end
|
||||
|
||||
def apply_rules!(rate_limit)
|
||||
Rack::Attack.throttle 'Limit number of calls to API', limit: rate_limit, period: 5.minutes do |req|
|
||||
req.env['api_v2.authentic_member_email']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
50
app/api/v2/entities/account.rb
Normal file
50
app/api/v2/entities/account.rb
Normal file
@@ -0,0 +1,50 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Account < Base
|
||||
expose(
|
||||
:currency_id,
|
||||
as: :currency,
|
||||
documentation: {
|
||||
desc: 'Currency code.',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:balance,
|
||||
format_with: :decimal,
|
||||
documentation: {
|
||||
desc: 'Account balance.',
|
||||
type: BigDecimal
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:locked,
|
||||
format_with: :decimal,
|
||||
documentation: {
|
||||
desc: 'Account locked funds.',
|
||||
type: BigDecimal
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:deposit_address,
|
||||
if: ->(account, _options) { account.currency.coin? },
|
||||
using: API::V2::Entities::PaymentAddress,
|
||||
documentation: {
|
||||
desc: 'User deposit address',
|
||||
type: String
|
||||
}
|
||||
) do |account, options|
|
||||
wallet = Wallet.deposit_wallet(account.currency_id)
|
||||
::PaymentAddress.find_by(wallet: wallet, member: options[:current_user], remote: false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/api/v2/entities/base.rb
Normal file
13
app/api/v2/entities/base.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Base < Grape::Entity
|
||||
format_with(:iso8601) {|t| t.to_time.in_time_zone(::Rails.configuration.time_zone).iso8601 if t }
|
||||
format_with(:decimal) {|d| d.to_s('F') if d }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
88
app/api/v2/entities/beneficiary.rb
Normal file
88
app/api/v2/entities/beneficiary.rb
Normal file
@@ -0,0 +1,88 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Beneficiary < Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
desc: 'Beneficiary Identifier in Database',
|
||||
type: Integer
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:currency_id,
|
||||
as: :currency,
|
||||
documentation: {
|
||||
desc: 'Beneficiary currency code.',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:uid,
|
||||
documentation: {
|
||||
desc: 'Beneficiary owner',
|
||||
type: String
|
||||
}
|
||||
) { |b| b.member.uid }
|
||||
|
||||
expose(
|
||||
:name,
|
||||
documentation: {
|
||||
desc: 'Human rememberable name which refer beneficiary.',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:description,
|
||||
documentation: {
|
||||
desc: 'Human rememberable description of beneficiary.',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
|
||||
# expose(
|
||||
# :data,
|
||||
# documentation: {
|
||||
# desc: 'Bank Account details for fiat Beneficiary in JSON format.'\
|
||||
# 'For crypto it\'s blockchain address.',
|
||||
# type: JSON
|
||||
# }
|
||||
# ) do |beneficiary|
|
||||
# beneficiary.currency.fiat? ? beneficiary.masked_data : beneficiary.data
|
||||
# end
|
||||
expose(
|
||||
:data,
|
||||
documentation: {
|
||||
desc: 'Bank Account details for fiat Beneficiary in JSON format.'\
|
||||
'For crypto it\'s blockchain address.',
|
||||
type: JSON
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
desc: 'Defines either beneficiary active - user can use it to withdraw money'\
|
||||
'or pending - requires beneficiary activation with pin.',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:sent_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
desc: 'Time when last pin was sent',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
38
app/api/v2/entities/bonus.rb
Normal file
38
app/api/v2/entities/bonus.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Bonus < Base
|
||||
expose(
|
||||
:bonus_member_id,
|
||||
as: :uid,
|
||||
documentation: {
|
||||
desc: 'user_uid.',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:amount,
|
||||
format_with: :decimal,
|
||||
documentation: {
|
||||
desc: 'Bonus account',
|
||||
type: BigDecimal
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
as: :date,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
desc: 'created date',
|
||||
type: Date
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
188
app/api/v2/entities/currency.rb
Normal file
188
app/api/v2/entities/currency.rb
Normal file
@@ -0,0 +1,188 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Currency < Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
desc: 'Currency code.',
|
||||
type: String,
|
||||
values: -> { ::Currency.visible.codes },
|
||||
example: -> { ::Currency.visible.first.id }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:name,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency name',
|
||||
example: -> { ::Currency.visible.first.name }
|
||||
},
|
||||
if: -> (currency){ currency.name.present? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:description,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency description',
|
||||
example: -> { ::Currency.visible.first.id }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:homepage,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency homepage',
|
||||
example: -> { ::Currency.visible.first.id }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:price,
|
||||
documentation: {
|
||||
desc: 'Currency current price'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:explorer_transaction,
|
||||
documentation: {
|
||||
desc: 'Currency transaction exprorer url template',
|
||||
example: 'https://testnet.blockchain.info/tx/'
|
||||
},
|
||||
if: -> (currency){ currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:explorer_address,
|
||||
documentation: {
|
||||
desc: 'Currency address exprorer url template',
|
||||
example: 'https://testnet.blockchain.info/address/'
|
||||
},
|
||||
if: -> (currency){ currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:type,
|
||||
documentation: {
|
||||
type: String,
|
||||
values: -> { ::Currency.types },
|
||||
desc: 'Currency type',
|
||||
example: -> { ::Currency.visible.first.type }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:deposit_enabled,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency deposit possibility status (true/false).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:withdrawal_enabled,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency withdrawal possibility status (true/false).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:deposit_fee,
|
||||
documentation: {
|
||||
desc: 'Currency deposit fee',
|
||||
example: -> { ::Currency.visible.first.deposit_fee }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:min_deposit_amount,
|
||||
documentation: {
|
||||
desc: 'Minimal deposit amount',
|
||||
example: -> { ::Currency.visible.first.min_deposit_amount }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:withdraw_fee,
|
||||
documentation: {
|
||||
desc: 'Currency withdraw fee',
|
||||
example: -> { ::Currency.visible.first.withdraw_fee }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:min_withdraw_amount,
|
||||
documentation: {
|
||||
desc: 'Minimal withdraw amount',
|
||||
example: -> { ::Currency.visible.first.min_withdraw_amount }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:withdraw_limit_24h,
|
||||
documentation: {
|
||||
desc: 'Currency 24h withdraw limit',
|
||||
example: -> { ::Currency.visible.first.withdraw_limit_24h }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:withdraw_limit_72h,
|
||||
documentation: {
|
||||
desc: 'Currency 72h withdraw limit',
|
||||
example: -> { ::Currency.visible.first.withdraw_limit_72h }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:base_factor,
|
||||
documentation: {
|
||||
desc: 'Currency base factor',
|
||||
example: -> { ::Currency.visible.first.base_factor }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:precision,
|
||||
documentation: {
|
||||
desc: 'Currency precision',
|
||||
example: -> { ::Currency.visible.first.precision }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:position,
|
||||
documentation: {
|
||||
desc: 'Position used for defining currencies order',
|
||||
example: -> { ::Currency.visible.first.precision }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:icon_url,
|
||||
documentation: {
|
||||
desc: 'Currency icon',
|
||||
example: 'https://upload.wikimedia.org/wikipedia/commons/0/05/Ethereum_logo_2014.svg'
|
||||
},
|
||||
if: -> (currency){ currency.icon_url.present? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:min_confirmations,
|
||||
if: ->(currency) { currency.coin? },
|
||||
documentation: {
|
||||
desc: 'Number of confirmations required for confirming deposit or withdrawal'
|
||||
}
|
||||
) { |c| c.blockchain.min_confirmations }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
104
app/api/v2/entities/deposit.rb
Normal file
104
app/api/v2/entities/deposit.rb
Normal file
@@ -0,0 +1,104 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Deposit < Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Unique deposit id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:currency_id,
|
||||
as: :currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit currency id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:amount,
|
||||
format_with: :decimal,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Deposit amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:fee,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Deposit fee.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:txid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit transaction id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:confirmations,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Number of deposit confirmations.'
|
||||
},
|
||||
if: ->(deposit) { deposit.currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:aasm_state,
|
||||
as: :state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit state.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:transfer_type,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit transfer type'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetime when deposit was created.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:completed_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetime when deposit was completed..'
|
||||
}
|
||||
)
|
||||
expose(
|
||||
:tid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The shared transaction ID'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
73
app/api/v2/entities/deposit_limit.rb
Normal file
73
app/api/v2/entities/deposit_limit.rb
Normal file
@@ -0,0 +1,73 @@
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class DepositLimit < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation:{
|
||||
type: Integer,
|
||||
desc: 'Unique deposit limit table identifier in database.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:group,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'Member group for define deposit limits.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:kyc_level,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'KYC level for define deposit limits.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:kind,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'kind for define deposit limits.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:limit_24_hour,
|
||||
documentation:{
|
||||
type: BigDecimal,
|
||||
desc: '24 hours deposit limit.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:limit_1_month,
|
||||
documentation:{
|
||||
type: BigDecimal,
|
||||
desc: '1 month deposit limit.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'deposit limit table created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'deposit limit table updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
98
app/api/v2/entities/internal_transfer.rb
Normal file
98
app/api/v2/entities/internal_transfer.rb
Normal file
@@ -0,0 +1,98 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class InternalTransfer < Base
|
||||
|
||||
expose(
|
||||
:currency_id,
|
||||
as: :currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The currency code.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:sender_username,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The internal transfer sender.'
|
||||
}
|
||||
) do |transfer|
|
||||
transfer.sender&.username
|
||||
end
|
||||
|
||||
expose(
|
||||
:receiver_username,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The internal transfer receiver.'
|
||||
}
|
||||
) do |transfer|
|
||||
transfer.receiver&.username
|
||||
end
|
||||
|
||||
expose(
|
||||
:sender_uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The internal transfer sender.'
|
||||
}
|
||||
) do |transfer|
|
||||
transfer.sender.uid
|
||||
end
|
||||
|
||||
expose(
|
||||
:receiver_uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The internal transfer receiver.'
|
||||
}
|
||||
) do |transfer|
|
||||
transfer.receiver.uid
|
||||
end
|
||||
|
||||
expose(
|
||||
:direction, ## call method from model
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The internal transfer direction (incoming or outcoming internal transfer).'
|
||||
}
|
||||
) do |transfer, options|
|
||||
transfer.direction(options[:current_user])
|
||||
end
|
||||
|
||||
expose(
|
||||
:amount,
|
||||
format_with: :decimal,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Internal transfer Amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
as: :status,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The internal transfer state.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetimes for the internal transfer.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
93
app/api/v2/entities/market.rb
Normal file
93
app/api/v2/entities/market.rb
Normal file
@@ -0,0 +1,93 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Market < Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Unique market id. It's always in the form of xxxyyy,"\
|
||||
"where xxx is the base currency code, yyy is the quote"\
|
||||
"currency code, e.g. 'btcusd'. All available markets can"\
|
||||
"be found at /api/v2/markets."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:name,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Market name.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:base_unit,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Market Base unit."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:quote_unit,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Market Quote unit."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:min_price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Minimum order price."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:max_price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Maximum order price."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:min_amount,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Minimum order amount."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:amount_precision,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Precision for order amount."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:price_precision,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Precision for order price."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Market state defines if user can see/trade on current market."
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
38
app/api/v2/entities/member.rb
Normal file
38
app/api/v2/entities/member.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Member < Base
|
||||
expose(
|
||||
:uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Member UID.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:email,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Member email.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:accounts,
|
||||
using: API::V2::Entities::Account,
|
||||
documentation: {
|
||||
type: 'API::V2::Entities::Account',
|
||||
is_array: true,
|
||||
desc: 'Member accounts.'
|
||||
}
|
||||
) do |m|
|
||||
m.accounts.includes(:currency)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
166
app/api/v2/entities/order.rb
Normal file
166
app/api/v2/entities/order.rb
Normal file
@@ -0,0 +1,166 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Order < Base
|
||||
expose(
|
||||
:id,
|
||||
documentation:{
|
||||
type: Integer,
|
||||
desc: "Unique order id."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:uuid,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: "Unique order UUID."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:side,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Either 'sell' or 'buy'."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:ord_type,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Type of order, either 'limit' or 'market'."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Price for each unit. e.g."\
|
||||
"If you want to sell/buy 1 btc at 3000 usd, the price is '3000.0'"
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:avg_price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Average execution price, average of price in trades."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "One of 'wait', 'done', or 'cancel'."\
|
||||
"An order in 'wait' is an active order, waiting fulfillment;"\
|
||||
"a 'done' order is an order fulfilled;"\
|
||||
"'cancel' means the order has been canceled."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:market_id,
|
||||
as: :market,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "The market in which the order is placed, e.g. 'btcusd'."\
|
||||
"All available markets can be found at /api/v2/markets."
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Order create time in iso8601 format."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Order updated time in iso8601 format."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:origin_volume,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "The amount user want to sell/buy."\
|
||||
"An order could be partially executed,"\
|
||||
"e.g. an order sell 5 btc can be matched with a buy 3 btc order,"\
|
||||
"left 2 btc to be sold; in this case the order's volume would be '5.0',"\
|
||||
"its remaining_volume would be '2.0', its executed volume is '3.0'."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:volume,
|
||||
as: :remaining_volume,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "The remaining volume, see 'volume'."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:executed_volume,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "The executed volume, see 'volume'."
|
||||
}
|
||||
) do |order, _options|
|
||||
order.origin_volume - order.volume
|
||||
end
|
||||
|
||||
expose(
|
||||
:maker_fee,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Fee for maker."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:taker_fee,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Fee for taker."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:trades_count,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: "Count of trades."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:trades,
|
||||
documentation: {
|
||||
type: 'API::V2::Entities::Trade',
|
||||
is_array: true,
|
||||
desc: "Trades wiht this order."
|
||||
},
|
||||
if: { type: :full }
|
||||
) do |order, _options|
|
||||
API::V2::Entities::Trade.represent order.trades, side: order.side
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
32
app/api/v2/entities/order_book.rb
Normal file
32
app/api/v2/entities/order_book.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_dependency 'v2/entities/order'
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class OrderBook < Base
|
||||
expose(
|
||||
:asks,
|
||||
using: Order,
|
||||
documentation: {
|
||||
type: 'API::V2::Entities::Order',
|
||||
is_array: true,
|
||||
desc: 'Asks in orderbook'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:bids,
|
||||
using: Order,
|
||||
documentation: {
|
||||
type: 'API::V2::Entities::Order',
|
||||
is_array: true,
|
||||
desc: 'Bids in orderbook'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
41
app/api/v2/entities/payment_address.rb
Normal file
41
app/api/v2/entities/payment_address.rb
Normal file
@@ -0,0 +1,41 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class PaymentAddress < Base
|
||||
expose(
|
||||
:currencies,
|
||||
documentation: {
|
||||
desc: 'Currencies codes.',
|
||||
is_array: true,
|
||||
example: -> { ::Currency.visible.codes }
|
||||
}
|
||||
) do |pa|
|
||||
pa.wallet.currencies.codes
|
||||
end
|
||||
|
||||
expose(
|
||||
:address,
|
||||
documentation: {
|
||||
desc: 'Payment address.',
|
||||
type: String
|
||||
}
|
||||
) do |pa, options|
|
||||
options[:address_format] ? pa.format_address(options[:address_format]) : pa.address
|
||||
end
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
desc: 'Payment address state.',
|
||||
type: String
|
||||
}
|
||||
) do |pa|
|
||||
pa.address.present? ? 'active' : 'pending'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
91
app/api/v2/entities/pnl.rb
Normal file
91
app/api/v2/entities/pnl.rb
Normal file
@@ -0,0 +1,91 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Pnl < Base
|
||||
expose(
|
||||
:currency_id,
|
||||
as: :currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency code.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:pnl_currency_id,
|
||||
as: :pnl_currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'PnL currency code.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:total_credit,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Total credit amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:total_debit,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Total debit amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:total_credit_value,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Total credit value in pnl currency.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:total_debit_value,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Total debit value in pnl currency.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:average_buy_price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Average buy price.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:average_sell_price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Average sell price.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:average_balance_price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Average balance price.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:total_balance_value,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Total balance value in pnl currency.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
66
app/api/v2/entities/public_trade.rb
Normal file
66
app/api/v2/entities/public_trade.rb
Normal file
@@ -0,0 +1,66 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class PublicTrade < Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade ID.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade price.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:amount,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:total,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade total (Amount * Price).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:market,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade market id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade create time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:taker_type,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade taker order type (sell or buy).'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
111
app/api/v2/entities/ticker.rb
Normal file
111
app/api/v2/entities/ticker.rb
Normal file
@@ -0,0 +1,111 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Ticker < Base
|
||||
class TickerEntry < Base
|
||||
expose(
|
||||
:low,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'The lowest trade price during last 24 hours (0.0 if no trades executed during last 24 hours)'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:high,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'The highest trade price during last 24 hours (0.0 if no trades executed during last 24 hours)'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:open,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Price of the first trade executed 24 hours ago or less'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:last,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'The last executed trade price'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:volume,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Total volume of trades executed during last 24 hours'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:amount,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Total amount of trades executed during last 24 hours'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:vol,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Alias to volume'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:avg_price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Average price more precisely VWAP is calculated by adding up the total traded for every transaction'\
|
||||
'(price multiplied by the number of shares traded) and then dividing by the total shares traded'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:price_change_percent,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Price change in the next format +3.19%.'\
|
||||
'Price change is calculated using next formula (last - open) / open * 100%'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:at,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Timestamp of ticker'
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
expose(
|
||||
:at,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Timestamp of ticker'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:ticker,
|
||||
using: TickerEntry,
|
||||
documentation: {
|
||||
type: TickerEntry,
|
||||
desc: 'Ticker entry for specified time'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
133
app/api/v2/entities/trade.rb
Normal file
133
app/api/v2/entities/trade.rb
Normal file
@@ -0,0 +1,133 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Trade < Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade ID.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade price.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:amount,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:total,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade total (Amount * Price).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:fee_currency,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Currency user\'s fees were charged in.'
|
||||
},
|
||||
if: ->(_, options) { options[:current_user] }
|
||||
) do |trade, options|
|
||||
fee_currency(trade.order_for_member(options[:current_user]))
|
||||
end
|
||||
|
||||
expose(
|
||||
:fee,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Percentage of fee user was charged for performed trade.'
|
||||
},
|
||||
if: ->(_, options) { options[:current_user] }
|
||||
) do |trade, options|
|
||||
trade.order_fee(trade.order_for_member(options[:current_user]))
|
||||
end
|
||||
|
||||
expose(
|
||||
:fee_amount,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Amount of fee user was charged for performed trade.'
|
||||
},
|
||||
if: ->(_, options) { options[:current_user] }
|
||||
) do |trade, options|
|
||||
fee_amount(trade, trade.order_for_member(options[:current_user]))
|
||||
end
|
||||
|
||||
expose(
|
||||
:market_id,
|
||||
as: :market,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade market id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade create time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:taker_type,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade taker order type (sell or buy).'
|
||||
}
|
||||
) do |trade, _options|
|
||||
trade.taker_order.side
|
||||
end
|
||||
|
||||
expose(
|
||||
:side,
|
||||
if: ->(trade, options) { options[:side] || options[:current_user] },
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade side.'
|
||||
}
|
||||
) do |trade, options|
|
||||
options[:side] || trade.side(options[:current_user])
|
||||
end
|
||||
|
||||
expose(
|
||||
:order_id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Order id.'
|
||||
},
|
||||
if: ->(_, options) { options[:current_user] }
|
||||
) do |trade, options|
|
||||
trade.order_for_member(options[:current_user]).id
|
||||
end
|
||||
|
||||
def fee_amount(trade, order)
|
||||
trade.order_fee(order) * (order.side == 'buy' ? trade.amount : trade.total)
|
||||
end
|
||||
|
||||
def fee_currency(order)
|
||||
order.side == 'buy' ? order.ask : order.bid
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
65
app/api/v2/entities/trading_fee.rb
Normal file
65
app/api/v2/entities/trading_fee.rb
Normal file
@@ -0,0 +1,65 @@
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class TradingFee < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation:{
|
||||
type: Integer,
|
||||
desc: 'Unique trading fee table identifier in database.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:group,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'Member group for define maker/taker fee.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:market_id,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'Market id for define maker/taker fee.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:maker,
|
||||
documentation:{
|
||||
type: BigDecimal,
|
||||
desc: 'Market maker fee.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:taker,
|
||||
documentation:{
|
||||
type: BigDecimal,
|
||||
desc: 'Market taker fee.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trading fee table created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trading fee table updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
105
app/api/v2/entities/transactions.rb
Normal file
105
app/api/v2/entities/transactions.rb
Normal file
@@ -0,0 +1,105 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Transactions < Base
|
||||
expose(
|
||||
:address,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Recipient address of transaction.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:currency_id,
|
||||
as: :currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Transaction currency id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:amount,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Transaction amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:fee,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Transaction fee.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:txid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Transaction id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:aasm_state,
|
||||
as: :state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Transaction state.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:note,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Withdraw note.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:confirmations,
|
||||
expose_nil: false,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Number of confirmations.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Transaction created time in iso8601 format."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Transaction updated time in iso8601 format."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:type,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Type of transaction'
|
||||
}
|
||||
) do |transaction, _options|
|
||||
transaction[:type].constantize.superclass.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
36
app/api/v2/entities/version.rb
Normal file
36
app/api/v2/entities/version.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Version < Base
|
||||
expose(:git_sha,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Running Peatio git commit SHA.'
|
||||
}
|
||||
)
|
||||
expose(:git_tag,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Running Peatio git tag.'
|
||||
}
|
||||
)
|
||||
expose(:build_date,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Running Peatio build date in iso8601 format'
|
||||
}
|
||||
)
|
||||
expose(:version,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Running Peatio version'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
139
app/api/v2/entities/withdraw.rb
Normal file
139
app/api/v2/entities/withdraw.rb
Normal file
@@ -0,0 +1,139 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Withdraw < Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'The withdrawal id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:txid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The withdrawal Transaction id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:currency_id,
|
||||
as: :currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The currency code.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:type,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The withdrawal type'
|
||||
}
|
||||
) { |w| w.currency.fiat? ? :fiat : :coin }
|
||||
|
||||
expose(
|
||||
:sum,
|
||||
as: :amount,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The withdrawal amount'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:fee,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'The exchange fee.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:txid,
|
||||
as: :blockchain_txid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The withdrawal transaction id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:rid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The beneficiary ID or wallet address on the Blockchain.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:aasm_state,
|
||||
as: :state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The withdrawal state.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:confirmations,
|
||||
if: ->(withdraw) { withdraw.currency.coin? },
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Number of confirmations.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:note,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Withdraw note.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:transfer_type,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Withdraw transfer type'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetimes for the withdrawal.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:completed_at,
|
||||
as: :done_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetime when withdraw was completed'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:otp,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The active deactive 2fa'
|
||||
}
|
||||
) { |w| w.member.otp.present? ? :true : :false }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
73
app/api/v2/entities/withdraw_limit.rb
Normal file
73
app/api/v2/entities/withdraw_limit.rb
Normal file
@@ -0,0 +1,73 @@
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class WithdrawLimit < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation:{
|
||||
type: Integer,
|
||||
desc: 'Unique withdraw limit table identifier in database.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:group,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'Member group for define withdraw limits.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:kyc_level,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'KYC level for define withdraw limits.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:kind,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'kind for define deposit limits.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:limit_24_hour,
|
||||
documentation:{
|
||||
type: BigDecimal,
|
||||
desc: '24 hours withdraw limit.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:limit_1_month,
|
||||
documentation:{
|
||||
type: BigDecimal,
|
||||
desc: '1 month withdraw limit.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Withdraw limit table created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Withdraw limit table updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
39
app/api/v2/exception_handlers.rb
Normal file
39
app/api/v2/exception_handlers.rb
Normal file
@@ -0,0 +1,39 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API::V2
|
||||
module ExceptionHandlers
|
||||
def self.included(base)
|
||||
base.instance_eval do
|
||||
rescue_from Grape::Exceptions::ValidationErrors do |e|
|
||||
errors_array = e.full_messages.map do |err|
|
||||
err.split.last
|
||||
end
|
||||
error!({ errors: errors_array }, 422)
|
||||
end
|
||||
|
||||
rescue_from Grape::Exceptions::MethodNotAllowed do |_e|
|
||||
error!({ errors: 'server.method_not_allowed' }, 405)
|
||||
end
|
||||
|
||||
rescue_from Grape::Exceptions::InvalidMessageBody do |_e|
|
||||
error!({ errors: 'server.method.invalid_message_body' }, 400)
|
||||
end
|
||||
|
||||
rescue_from Peatio::Auth::Error do |e|
|
||||
report_exception(e)
|
||||
error!({ errors: ['jwt.decode_and_verify'] }, 401)
|
||||
end
|
||||
|
||||
rescue_from ActiveRecord::RecordNotFound do |_e|
|
||||
error!({ errors: ['record.not_found'] }, 404)
|
||||
end
|
||||
|
||||
rescue_from :all do |e|
|
||||
report_exception(e)
|
||||
error!({ errors: ['server.internal_error'] }, 500)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
121
app/api/v2/helpers.rb
Normal file
121
app/api/v2/helpers.rb
Normal file
@@ -0,0 +1,121 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Helpers
|
||||
extend Memoist
|
||||
|
||||
def admin_authorize!(action, model, attributes = {})
|
||||
if attributes.present?
|
||||
attributes.each do |k, _|
|
||||
AdminAbility.new(current_user).authorize!(action, model, k)
|
||||
end
|
||||
else
|
||||
AdminAbility.new(current_user).authorize!(action, model)
|
||||
end
|
||||
rescue StandardError
|
||||
error!({ errors: ['admin.ability.not_permitted'] }, 403)
|
||||
end
|
||||
|
||||
def user_authorize!(action, model, attributes = {})
|
||||
if attributes.present?
|
||||
attributes.each do |k, _|
|
||||
UserAbility.new(current_user).authorize!(action, model, k)
|
||||
end
|
||||
else
|
||||
UserAbility.new(current_user).authorize!(action, model)
|
||||
end
|
||||
rescue StandardError
|
||||
error!({ errors: ['user.ability.not_permitted'] }, 403)
|
||||
end
|
||||
|
||||
def authenticate!
|
||||
current_user || raise(Peatio::Auth::Error)
|
||||
end
|
||||
|
||||
def set_ets_context!
|
||||
return unless defined?(Raven)
|
||||
|
||||
if current_user
|
||||
Raven.user_context(
|
||||
email: current_user.email,
|
||||
uid: current_user.uid,
|
||||
role: current_user.role
|
||||
)
|
||||
end
|
||||
Raven.tags_context(
|
||||
peatio_version: Peatio::Application::VERSION
|
||||
)
|
||||
end
|
||||
|
||||
def deposits_must_be_permitted!
|
||||
if current_user.level < ENV.fetch('MINIMUM_MEMBER_LEVEL_FOR_DEPOSIT').to_i
|
||||
error!({ errors: ['account.deposit.not_permitted'] }, 403)
|
||||
end
|
||||
end
|
||||
|
||||
def withdraws_must_be_permitted!
|
||||
if current_user.level < ENV.fetch('MINIMUM_MEMBER_LEVEL_FOR_WITHDRAW').to_i
|
||||
error!({ errors: ['account.withdraw.not_permitted'] }, 403)
|
||||
end
|
||||
end
|
||||
|
||||
def trading_must_be_permitted!
|
||||
if current_user.level < ENV.fetch('MINIMUM_MEMBER_LEVEL_FOR_TRADING').to_i
|
||||
error!({ errors: ['market.trade.not_permitted'] }, 403)
|
||||
end
|
||||
end
|
||||
|
||||
def withdraw_api_must_be_enabled!
|
||||
error!({ errors: ['account.withdraw.disabled_api'] }, 422) if ENV.false?('ENABLE_ACCOUNT_WITHDRAWAL_API')
|
||||
end
|
||||
|
||||
def current_user
|
||||
# jwt.payload provided by rack-jwt
|
||||
if request.env.key?('jwt.payload')
|
||||
begin
|
||||
Member.from_payload(request.env['jwt.payload'].symbolize_keys)
|
||||
# Handle race conditions when creating member record.
|
||||
# We do not handle race condition for update operations.
|
||||
# http://api.rubyonrails.org/classes/ActiveRecord/Relation.html#method-i-find_or_create_by
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
retry
|
||||
end
|
||||
end
|
||||
end
|
||||
memoize :current_user
|
||||
|
||||
def current_market
|
||||
::Market.active.find_by_id(params[:market])
|
||||
end
|
||||
memoize :current_market
|
||||
|
||||
def format_ticker(ticker)
|
||||
permitted_keys = %i[low high open last volume amount
|
||||
avg_price price_change_percent]
|
||||
|
||||
# Add vol for compatibility with old API.
|
||||
formatted_ticker = ticker.slice(*permitted_keys)
|
||||
.merge(vol: ticker[:volume])
|
||||
{ at: ticker[:at],
|
||||
ticker: formatted_ticker }
|
||||
end
|
||||
|
||||
def paginate(collection, include_total = true)
|
||||
per_page = params[:limit] || Kaminari.config.default_per_page
|
||||
per_page = [per_page.to_i, Kaminari.config.max_per_page].compact.min
|
||||
|
||||
result = if collection.is_a?(::ActiveRecord::Relation)
|
||||
collection.page(params[:page].to_i).per(per_page)
|
||||
elsif collection.is_a?(Array)
|
||||
Kaminari.paginate_array(collection).page(params[:page].to_i).per(per_page)
|
||||
end
|
||||
result.tap do |data|
|
||||
header 'Total', data.total_count.to_s if include_total
|
||||
header 'Per-Page', data.limit_value.to_s
|
||||
header 'Page', data.current_page.to_s
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
48
app/api/v2/management/accounts.rb
Normal file
48
app/api/v2/management/accounts.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Accounts < Grape::API
|
||||
desc 'Queries the account balance for the given UID and currency.' do
|
||||
@settings[:scope] = :read_accounts
|
||||
success API::V2::Management::Entities::Balance
|
||||
end
|
||||
|
||||
params do
|
||||
requires :uid, type: String, desc: 'The shared user ID.'
|
||||
requires :currency, type: String, values: -> { Currency.codes(bothcase: true) }, desc: 'The currency code.'
|
||||
end
|
||||
|
||||
post '/accounts/balance' do
|
||||
member = Member.find_by!(uid: params[:uid])
|
||||
account = member.get_account(params[:currency])
|
||||
present account, with: API::V2::Management::Entities::Balance
|
||||
status 200
|
||||
end
|
||||
|
||||
desc 'Queries the non-zero balance accounts for the given currency.' do
|
||||
@settings[:scope] = :read_accounts
|
||||
success API::V2::Management::Entities::Balance
|
||||
end
|
||||
|
||||
params do
|
||||
requires :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: 1000, range: 1..100000, desc: 'The number of accounts per page (defaults to 100, maximum is 1000).'
|
||||
end
|
||||
|
||||
post '/accounts/balances' do
|
||||
accounts = ::Account.where("currency_id = ? AND (balance > 0 OR locked > 0)", params[:currency])
|
||||
accounts
|
||||
.order(id: :asc)
|
||||
.page(params[:page])
|
||||
.per(params[:limit])
|
||||
.tap { |q| present q, with: API::V2::Management::Entities::Balance }
|
||||
status 200
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
111
app/api/v2/management/beneficiaries.rb
Normal file
111
app/api/v2/management/beneficiaries.rb
Normal file
@@ -0,0 +1,111 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Beneficiaries < Grape::API
|
||||
namespace :beneficiaries do
|
||||
|
||||
desc 'Get list of user beneficiaries' do
|
||||
@settings[:scope] = :read_beneficiaries
|
||||
success API::V2::Management::Entities::Beneficiary
|
||||
end
|
||||
params do
|
||||
requires :uid,
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
optional :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'management.currency.doesnt_exist' },
|
||||
as: :currency_id,
|
||||
desc: 'Beneficiary currency code.'
|
||||
optional :state,
|
||||
type: String,
|
||||
values: { value: -> { ::Beneficiary::STATES_AVAILABLE_FOR_MEMBER.map(&:to_s) }, message: 'management.beneficiary.invalid_state'},
|
||||
desc: 'Defines either beneficiary active - user can use it to withdraw money'\
|
||||
'or pending - requires beneficiary activation with pin.'
|
||||
|
||||
end
|
||||
post '/list' do
|
||||
member = Member.find_by!(uid: params[:uid])
|
||||
|
||||
member
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.tap { |q| q.where!(currency_id: params[:currency_id]) if params[:currency_id].present? }
|
||||
.tap {|q| q.where!(state: params[:state]) if params[:state].present? }
|
||||
.yield_self { |b| present paginate(b), with: API::V2::Management::Entities::Beneficiary }
|
||||
|
||||
status 200
|
||||
end
|
||||
|
||||
desc 'Create new beneficiary' do
|
||||
@settings[:scope] = :write_beneficiaries
|
||||
success API::V2::Management::Entities::Beneficiary
|
||||
end
|
||||
params do
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'management.currency.doesnt_exist' },
|
||||
as: :currency_id,
|
||||
desc: 'Beneficiary currency code.'
|
||||
requires :name,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
values: { value: ->(v) { v.present? && v.size <= 64 }, message: 'management.beneficiary.too_long_name' },
|
||||
desc: 'Human rememberable name which refer beneficiary.'
|
||||
optional :description,
|
||||
type: String,
|
||||
values: { value: ->(v) { v.size <= 255 }, message: 'management.beneficiary.too_long_description' },
|
||||
desc: 'Human rememberable description which refer beneficiary.'
|
||||
requires :data,
|
||||
type: { value: JSON, message: 'management.beneficiary.non_json_data' },
|
||||
allow_blank: false,
|
||||
desc: 'Beneficiary data in JSON format'
|
||||
requires :uid,
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
optional :state,
|
||||
type: String,
|
||||
values: { value: -> { ::Beneficiary::STATES_AVAILABLE_FOR_MEMBER.map(&:to_s) }, message: 'management.beneficiary.invalid_state'},
|
||||
desc: 'Defines either beneficiary active - user can use it to withdraw money'\
|
||||
'or pending - requires beneficiary activation with pin.'
|
||||
end
|
||||
post do
|
||||
declared_params = declared(params)
|
||||
member = Member.find_by!(uid: params[:uid])
|
||||
currency = Currency.find_by!(id: params[:currency_id])
|
||||
|
||||
if !currency.withdrawal_enabled?
|
||||
error!({ errors: ['management.currency.withdrawal_disabled'] }, 422)
|
||||
elsif currency.coin? && declared_params.dig(:data, :address).blank?
|
||||
error!({ errors: ['management.beneficiary.missing_address_in_data'] }, 422)
|
||||
elsif currency.fiat? && declared_params.dig(:data, :full_name).blank?
|
||||
error!({ errors: ['management.beneficiary.missing_full_name_in_data'] }, 422)
|
||||
end
|
||||
|
||||
# Since data is stored in MySQL JSON format we iterate through all
|
||||
# beneficiaries one by one to detect duplicated address.
|
||||
if currency.coin? &&
|
||||
member
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.where(currency: currency)
|
||||
.any? { |b| b.data['address'] == declared_params.dig(:data, :address) }
|
||||
error!({ errors: ['management.beneficiary.duplicate_address'] }, 422)
|
||||
end
|
||||
|
||||
present member
|
||||
.beneficiaries
|
||||
.create!(declared_params.except(:uid)),
|
||||
with: API::V2::Management::Entities::Beneficiary
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
report_exception(e)
|
||||
error!({ errors: ['management.beneficiary.failed_to_create'] }, 422)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
124
app/api/v2/management/currencies.rb
Normal file
124
app/api/v2/management/currencies.rb
Normal file
@@ -0,0 +1,124 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Currencies < Grape::API
|
||||
# POST: api/v2/management/currencies/list
|
||||
desc 'Return currencies list.' do
|
||||
@settings[:scope] = :read_currencies
|
||||
success API::V2::Management::Entities::Currency
|
||||
end
|
||||
params do
|
||||
optional :type,
|
||||
type: String,
|
||||
values: { value: %w[fiat coin], message: 'management.currency.invalid_type' },
|
||||
desc: -> { API::V2::Entities::Currency.documentation[:type][:desc] }
|
||||
end
|
||||
post '/currencies/list' do
|
||||
currencies = Currency.all
|
||||
currencies = currencies.where(type: params[:type]).includes(:blockchain) if params[:type] == 'coin'
|
||||
currencies = currencies.where(type: params[:type]) if params[:type] == 'fiat'
|
||||
present currencies.ordered, with: API::V2::Entities::Currency
|
||||
|
||||
status 200
|
||||
end
|
||||
|
||||
# POST: api/v2/management/currencies
|
||||
desc 'Returns currency by code.' do
|
||||
@settings[:scope] = :read_currencies
|
||||
success API::V2::Management::Entities::Currency
|
||||
end
|
||||
|
||||
params do
|
||||
requires :code, type: String, desc: 'The currency code.'
|
||||
end
|
||||
post '/currencies/:code', requirements: { code: /[\w\.\-]+/ } do
|
||||
present Currency.find_by!(params.slice(:code)), with: API::V2::Management::Entities::Currency
|
||||
end
|
||||
|
||||
desc 'Update currency.' do
|
||||
@settings[:scope] = :write_currencies
|
||||
success API::V2::Management::Entities::Currency
|
||||
end
|
||||
|
||||
params do
|
||||
requires :id,
|
||||
type: String,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:id][:desc] }
|
||||
optional :name, desc: -> { API::V2::Management::Entities::Currency.documentation[:name][:desc] }
|
||||
optional :deposit_fee,
|
||||
type: { value: BigDecimal, message: 'management.currency.non_decimal_deposit_fee' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'management.currency.invalid_deposit_fee' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:deposit_fee][:desc] }
|
||||
optional :min_deposit_amount,
|
||||
type: { value: BigDecimal, message: 'management.currency.min_deposit_amount' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'management.currency.invalid_min_deposit_amount' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:min_deposit_amount][:desc] }
|
||||
optional :min_collection_amount,
|
||||
type: { value: BigDecimal, message: 'management.currency.non_decimal_min_collection_amount' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'management.currency.invalid_min_collection_amount' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:min_collection_amount][:desc] }
|
||||
optional :withdraw_fee,
|
||||
type: { value: BigDecimal, message: 'management.currency.non_decimal_withdraw_fee' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'management.currency.invalid_withdraw_fee' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:withdraw_fee][:desc] }
|
||||
optional :min_withdraw_amount,
|
||||
type: { value: BigDecimal, message: 'management.currency.non_decimal_min_withdraw_amount' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'management.currency.invalid_min_withdraw_amount' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:min_withdraw_amount][:desc] }
|
||||
optional :withdraw_limit_24h,
|
||||
type: { value: BigDecimal, message: 'management.currency.non_decimal_withdraw_limit_24h' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'management.currency.invalid_withdraw_limit_24h' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:withdraw_limit_24h][:desc] }
|
||||
optional :withdraw_limit_72h,
|
||||
type: { value: BigDecimal, message: 'management.currency.non_decimal_withdraw_limit_72h' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'management.currency.invalid_withdraw_limit_72h' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:withdraw_limit_72h][:desc] }
|
||||
optional :position,
|
||||
type: { value: Integer, message: 'management.currency.non_integer_position' },
|
||||
values: { value: -> (p){ p >= ::Currency::TOP_POSITION }, message: 'management.currency.invalid_position' },
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:position][:desc] }
|
||||
optional :options,
|
||||
type: { value: JSON, message: 'management.currency.non_json_options' },
|
||||
default: {},
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:options][:desc] }
|
||||
optional :visible,
|
||||
type: { value: Boolean, message: 'management.currency.non_boolean_visible' },
|
||||
default: true,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:visible][:desc] }
|
||||
optional :deposit_enabled,
|
||||
type: { value: Boolean, message: 'management.currency.non_boolean_deposit_enabled' },
|
||||
default: true,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:deposit_enabled][:desc] }
|
||||
optional :withdrawal_enabled,
|
||||
type: { value: Boolean, message: 'management.currency.non_boolean_withdrawal_enabled' },
|
||||
default: true,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:withdrawal_enabled][:desc] }
|
||||
optional :precision,
|
||||
type: { value: Integer, message: 'management.currency.non_integer_base_precision' },
|
||||
default: 8,
|
||||
desc: -> { API::V2::Management::Entities::Currency.documentation[:precision][:desc] }
|
||||
optional :icon_url, desc: -> { API::V2::Management::Entities::Currency.documentation[:icon_url][:desc] }
|
||||
end
|
||||
put '/currencies/update' do
|
||||
currency = ::Currency.find_by!(params.slice(:id))
|
||||
if currency.update(declared(params, include_missing: false))
|
||||
present currency, with: API::V2::Management::Entities::Currency
|
||||
else
|
||||
body errors: currency.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
104
app/api/v2/management/deposits.rb
Normal file
104
app/api/v2/management/deposits.rb
Normal file
@@ -0,0 +1,104 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Deposits < Grape::API
|
||||
|
||||
desc 'Returns deposits as paginated collection.' do
|
||||
@settings[:scope] = :read_deposits
|
||||
success API::V2::Management::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.aasm.states.map(&:name).map(&:to_s) }, desc: 'The state to filter by.'
|
||||
end
|
||||
post '/deposits' do
|
||||
currency = Currency.find(params[:currency]) if params[:currency].present?
|
||||
member = Member.find_by!(uid: params[:uid]) if params[:uid].present?
|
||||
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] }
|
||||
.includes(:member, :currency)
|
||||
.page(params[:page])
|
||||
.per(params[:limit])
|
||||
.tap { |q| present q, with: API::V2::Management::Entities::Deposit }
|
||||
status 200
|
||||
end
|
||||
|
||||
desc 'Returns deposit by TID.' do
|
||||
@settings[:scope] = :read_deposits
|
||||
success API::V2::Management::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: API::V2::Management::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 API::V2::Management::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]
|
||||
optional :transfer_type, type: String,
|
||||
values: { value: -> { Deposit::TRANSFER_TYPES.keys }, message: 'account.deposit.transfer_type_not_in_list' },
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:transfer_type][:desc] }
|
||||
end
|
||||
post '/deposits/new' do
|
||||
member = Member.find_by(uid: params[:uid])
|
||||
currency = Currency.find(params[:currency])
|
||||
|
||||
unless currency.deposit_enabled?
|
||||
error!({ errors: ['management.currency.deposit_disabled'] }, 422)
|
||||
end
|
||||
|
||||
data = { member: member, currency: currency }.merge!(params.slice(:amount, :tid, :transfer_type))
|
||||
deposit = ::Deposits::Fiat.new(data)
|
||||
if deposit.save
|
||||
deposit.charge! if params[:state] == 'accepted'
|
||||
present deposit, with: API::V2::Management::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 API::V2::Management::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!
|
||||
end
|
||||
present deposit, with: API::V2::Management::Entities::Deposit
|
||||
status 200
|
||||
else
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
99
app/api/v2/management/engines.rb
Normal file
99
app/api/v2/management/engines.rb
Normal file
@@ -0,0 +1,99 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
class Engines < Grape::API
|
||||
namespace :engines do
|
||||
desc 'Get all engine, result is paginated.' do
|
||||
@settings[:scope] = :read_engines
|
||||
success API::V2::Management::Entities::Engine
|
||||
end
|
||||
params do
|
||||
optional :limit,
|
||||
type: { value: Integer, message: 'management.pagination.non_integer_limit' },
|
||||
values: { value: 1..1000, message: 'management.pagination.invalid_limit' },
|
||||
default: 100,
|
||||
desc: 'Limit the number of returned paginations. Defaults to 100.'
|
||||
optional :page,
|
||||
type: { value: Integer, message: 'management.pagination.non_integer_page' },
|
||||
allow_blank: false,
|
||||
default: 1,
|
||||
desc: 'Specify the page of paginated results.'
|
||||
optional :ordering,
|
||||
values: { value: %w(asc desc), message: 'management.pagination.invalid_ordering' },
|
||||
default: 'asc',
|
||||
desc: 'If set, returned values will be sorted in specific order, defaults to \'asc\'.'
|
||||
optional :order_by,
|
||||
default: 'id',
|
||||
desc: 'Name of the field, which result will be ordered by.'
|
||||
end
|
||||
post '/get' do
|
||||
result = ::Engine.order(params[:order_by] => params[:ordering])
|
||||
present paginate(result), with: API::V2::Management::Entities::Engine
|
||||
end
|
||||
|
||||
desc 'Creates new engine' do
|
||||
@settings[:scope] = :write_engines
|
||||
success API::V2::Management::Entities::Engine
|
||||
end
|
||||
params do
|
||||
requires :name,
|
||||
desc: -> { API::V2::Management::Entities::Engine.documentation[:name][:desc] },
|
||||
values: { value: ->(v) { !v.in?(::Engine.pluck(:name)) }, message: 'management.engine.duplicate_name' }
|
||||
requires :driver,
|
||||
desc: -> { API::V2::Management::Entities::Engine.documentation[:driver][:desc] }
|
||||
optional :uid,
|
||||
desc: -> { API::V2::Management::Entities::Engine.documentation[:uid][:desc] }
|
||||
optional :key,
|
||||
desc: -> { 'Credentials for remote engine' }
|
||||
optional :secret,
|
||||
desc: -> { 'Credentials for remote engine' }
|
||||
optional :data,
|
||||
desc: -> { 'Metadata for engine' }
|
||||
end
|
||||
post do
|
||||
engine = ::Engine.new(declared(params))
|
||||
if engine.save
|
||||
present engine, with: API::V2::Management::Entities::Engine
|
||||
status 201
|
||||
else
|
||||
body errors: engine.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update engine' do
|
||||
@settings[:scope] = :write_engines
|
||||
success API::V2::Management::Entities::Engine
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
desc: -> { API::V2::Management::Entities::Engine.documentation[:id][:desc] }
|
||||
optional :name,
|
||||
desc: -> { API::V2::Management::Entities::Engine.documentation[:name][:desc] }
|
||||
optional :driver,
|
||||
desc: -> { API::V2::Management::Entities::Engine.documentation[:driver][:desc] }
|
||||
optional :key,
|
||||
desc: -> { 'Credentials for remote engine' }
|
||||
optional :secret,
|
||||
desc: -> { 'Credentials for remote engine' }
|
||||
optional :state,
|
||||
values: { value: ::Engine::STATES.values, message: 'management.engine.invalid_state' },
|
||||
default: 1,
|
||||
desc: -> { API::V2::Management::Entities::Engine.documentation[:state][:desc] }
|
||||
end
|
||||
post '/update' do
|
||||
engine = ::Engine.find(params[:id])
|
||||
if engine.update(declared(params, include_missing: false))
|
||||
present engine, with: API::V2::Management::Entities::Engine
|
||||
else
|
||||
body errors: engine.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
16
app/api/v2/management/entities/balance.rb
Normal file
16
app/api/v2/management/entities/balance.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Management
|
||||
module Entities
|
||||
class Balance < Base
|
||||
expose(:uid, documentation: { type: String, desc: 'The shared user ID.' }) { |w| w.member.uid }
|
||||
expose(:balance, documentation: { type: String, desc: 'The account balance.' }, format_with: :decimal)
|
||||
expose(:locked, documentation: { type: String, desc: 'The locked account balance.' }, format_with: :decimal)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user