Initial commit

This commit is contained in:
Yaser
2026-08-13 19:50:53 +03:30
commit 38084458fe
879 changed files with 95198 additions and 0 deletions

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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
View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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

View 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
View 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

View 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
View 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

View 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
View 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

View 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
View 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

View 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

View 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
View 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

View 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

View 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

View 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