Initial commit
This commit is contained in:
33
.dockerignore
Normal file
33
.dockerignore
Normal file
@@ -0,0 +1,33 @@
|
||||
# Ignore git
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# JetBrains IDE files
|
||||
.idea
|
||||
|
||||
# Ignore docker
|
||||
Dockerfile
|
||||
Dockerfile.plugin
|
||||
.dockerignore
|
||||
|
||||
# Ignore CI related files
|
||||
ci
|
||||
.travis.yml
|
||||
|
||||
# Ignore files for development
|
||||
.ruby-version
|
||||
.rspec
|
||||
|
||||
.DS_Store
|
||||
*.md
|
||||
docs
|
||||
tmp
|
||||
log
|
||||
.god
|
||||
|
||||
# Ignore bundler config
|
||||
/.bundle
|
||||
|
||||
# Ignore locally installed gems
|
||||
/vendor/bundle
|
||||
|
||||
553
.drone.yml
Normal file
553
.drone.yml
Normal file
@@ -0,0 +1,553 @@
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: test-mysql-5.7
|
||||
|
||||
services:
|
||||
- name: database
|
||||
image: mysql:5.7
|
||||
ports:
|
||||
- 3306
|
||||
environment:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
|
||||
|
||||
- name: influxdb
|
||||
image: influxdb:1.7.8
|
||||
ports:
|
||||
- 8086
|
||||
environment:
|
||||
INFLUXDB_ADMIN_ENABLED: 'true'
|
||||
|
||||
- name: rabbitmq
|
||||
image: rabbitmq:3.8.9
|
||||
ports:
|
||||
- 5672
|
||||
|
||||
- name: redis
|
||||
image: redis:4.0
|
||||
ports:
|
||||
- 6379
|
||||
|
||||
steps:
|
||||
- name: wait-for-services
|
||||
image: jwilder/dockerize
|
||||
commands:
|
||||
- dockerize -wait tcp://database:3306 -wait tcp://influxdb:8086 -wait tcp://rabbitmq:5672 -wait tcp://redis:6379 -timeout 1m
|
||||
|
||||
- name: init-influxdb
|
||||
image: influxdb:1.7.8
|
||||
commands:
|
||||
- cat db/influxdb.sql | influx -host influxdb
|
||||
|
||||
# Copy bundler cache from the host machine to your build environment.
|
||||
# Plugin description: http://plugins.drone.io/drillster/drone-volume-cache
|
||||
- name: restore-bundle-cache
|
||||
pull: if-not-exists
|
||||
image: drillster/drone-volume-cache
|
||||
settings:
|
||||
restore: true
|
||||
mount:
|
||||
- vendor/bundle
|
||||
volumes:
|
||||
- name: bundle-cache
|
||||
path: /cache
|
||||
|
||||
- name: run-rspec
|
||||
image: ruby:2.6.5
|
||||
environment:
|
||||
DATABASE_HOST: database
|
||||
INFLUXDB_HOST: influxdb
|
||||
RABBITMQ_HOST: rabbitmq
|
||||
EVENT_API_RABBITMQ_HOST: rabbitmq
|
||||
REDIS_URL: redis://redis:6379
|
||||
RAILS_ENV: test
|
||||
VAULT_ADDR: ''
|
||||
VAULT_TOKEN: ''
|
||||
commands:
|
||||
- bin/init_config
|
||||
- gem install bundler:2.1.4
|
||||
- bundle install --jobs=$(nproc) --retry=3 --path vendor/bundle
|
||||
- bundle exec rake db:create db:migrate
|
||||
- LOG_LEVEL=warn bundle exec rspec
|
||||
|
||||
- name: rebuild-bundle-cache
|
||||
pull: if-not-exists
|
||||
image: drillster/drone-volume-cache
|
||||
settings:
|
||||
rebuild: true
|
||||
mount:
|
||||
- vendor/bundle
|
||||
volumes:
|
||||
- name: bundle-cache
|
||||
path: /cache
|
||||
|
||||
- name: docs-generate
|
||||
image: quay.io/openware/ruby-node:2.6.5
|
||||
environment:
|
||||
DATABASE_HOST: database
|
||||
INFLUXDB_HOST: influxdb
|
||||
RABBITMQ_HOST: rabbitmq
|
||||
EVENT_API_RABBITMQ_HOST: rabbitmq
|
||||
REDIS_URL: redis://redis:6379
|
||||
VAULT_TOKEN: ''
|
||||
commands:
|
||||
- gem install bundler:2.1.4
|
||||
- bin/gendocs
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
- "*-stable"
|
||||
|
||||
volumes:
|
||||
- name: bundle-cache
|
||||
host:
|
||||
path: /tmp/bundle
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: test-mariadb-10.3
|
||||
|
||||
services:
|
||||
- name: mariadb
|
||||
image: mariadb:10.3
|
||||
ports:
|
||||
- 3306
|
||||
environment:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
|
||||
|
||||
- name: influxdb
|
||||
image: influxdb:1.7.8
|
||||
ports:
|
||||
- 8086
|
||||
environment:
|
||||
INFLUXDB_ADMIN_ENABLED: 'true'
|
||||
|
||||
- name: rabbitmq
|
||||
image: rabbitmq:3.8.9
|
||||
ports:
|
||||
- 5672
|
||||
|
||||
- name: redis
|
||||
image: redis:4.0
|
||||
ports:
|
||||
- 6379
|
||||
|
||||
steps:
|
||||
- name: wait-for-services
|
||||
image: jwilder/dockerize
|
||||
commands:
|
||||
- dockerize -wait tcp://mariadb:3306 -wait tcp://influxdb:8086 -wait tcp://rabbitmq:5672 -wait tcp://redis:6379 -timeout 1m
|
||||
|
||||
- name: init-influxdb
|
||||
image: influxdb:1.7.8
|
||||
commands:
|
||||
- cat db/influxdb.sql | influx -host influxdb
|
||||
|
||||
- name: restore-bundle-cache
|
||||
pull: if-not-exists
|
||||
image: drillster/drone-volume-cache
|
||||
settings:
|
||||
restore: true
|
||||
mount:
|
||||
- vendor/bundle
|
||||
volumes:
|
||||
- name: bundle-cache
|
||||
path: /cache
|
||||
|
||||
- name: run-rspec
|
||||
image: ruby:2.6.5
|
||||
environment:
|
||||
DATABASE_HOST: "mariadb"
|
||||
DATABASE_SUPPORT_JSON: "false"
|
||||
INFLUXDB_HOST: influxdb
|
||||
RABBITMQ_HOST: rabbitmq
|
||||
EVENT_API_RABBITMQ_HOST: rabbitmq
|
||||
REDIS_URL: redis://redis:6379
|
||||
RAILS_ENV: test
|
||||
VAULT_ADDR: ''
|
||||
VAULT_TOKEN: ''
|
||||
commands:
|
||||
- bin/init_config
|
||||
- gem install bundler:2.1.4
|
||||
- bundle install --jobs=$(nproc) --retry=3 --path vendor/bundle
|
||||
- bundle exec rake db:create db:migrate
|
||||
- LOG_LEVEL=warn bundle exec rspec
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
branch:
|
||||
- master
|
||||
- "*-stable"
|
||||
- "fix-db/*"
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: test-mysql-8.0
|
||||
|
||||
services:
|
||||
- name: mysql
|
||||
image: mysql:8.0
|
||||
commands:
|
||||
- /entrypoint.sh --default-authentication-plugin=mysql_native_password
|
||||
ports:
|
||||
- 3306
|
||||
environment:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
|
||||
|
||||
- name: influxdb
|
||||
image: influxdb:1.7.8
|
||||
ports:
|
||||
- 8086
|
||||
environment:
|
||||
INFLUXDB_ADMIN_ENABLED: 'true'
|
||||
|
||||
- name: rabbitmq
|
||||
image: rabbitmq:3.8.9
|
||||
ports:
|
||||
- 5672
|
||||
|
||||
- name: redis
|
||||
image: redis:4.0
|
||||
ports:
|
||||
- 6379
|
||||
|
||||
steps:
|
||||
- name: wait-for-services
|
||||
image: jwilder/dockerize
|
||||
commands:
|
||||
- dockerize -wait tcp://mysql:3306 -wait tcp://influxdb:8086 -wait tcp://rabbitmq:5672 -wait tcp://redis:6379 -timeout 1m
|
||||
|
||||
- name: init-influxdb
|
||||
image: influxdb:1.7.8
|
||||
commands:
|
||||
- cat db/influxdb.sql | influx -host influxdb
|
||||
|
||||
- name: restore-bundle-cache
|
||||
pull: if-not-exists
|
||||
image: drillster/drone-volume-cache
|
||||
settings:
|
||||
restore: true
|
||||
mount:
|
||||
- vendor/bundle
|
||||
volumes:
|
||||
- name: bundle-cache
|
||||
path: /cache
|
||||
|
||||
- name: run-rspec
|
||||
image: ruby:2.6.5
|
||||
environment:
|
||||
DATABASE_HOST: "mysql"
|
||||
INFLUXDB_HOST: influxdb
|
||||
RABBITMQ_HOST: rabbitmq
|
||||
EVENT_API_RABBITMQ_HOST: rabbitmq
|
||||
REDIS_URL: redis://redis:6379
|
||||
RAILS_ENV: test
|
||||
VAULT_ADDR: ''
|
||||
VAULT_TOKEN: ''
|
||||
commands:
|
||||
- bin/init_config
|
||||
- gem install bundler:2.1.4
|
||||
- bundle install --jobs=$(nproc) --retry=3 --path vendor/bundle
|
||||
- bundle exec rake db:create db:migrate
|
||||
- LOG_LEVEL=warn bundle exec rspec
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
branch:
|
||||
- master
|
||||
- "*-stable"
|
||||
- "fix-db/*"
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: test-postgresql-13.0
|
||||
|
||||
services:
|
||||
- name: postgres
|
||||
image: postgres:13.0
|
||||
ports:
|
||||
- 5432
|
||||
environment:
|
||||
POSTGRES_PASSWORD: changeme
|
||||
|
||||
- name: influxdb
|
||||
image: influxdb:1.7.8
|
||||
ports:
|
||||
- 8086
|
||||
environment:
|
||||
INFLUXDB_ADMIN_ENABLED: 'true'
|
||||
|
||||
- name: rabbitmq
|
||||
image: rabbitmq:3.8.9
|
||||
ports:
|
||||
- 5672
|
||||
|
||||
- name: redis
|
||||
image: redis:4.0
|
||||
ports:
|
||||
- 6379
|
||||
|
||||
steps:
|
||||
- name: wait-for-services
|
||||
image: jwilder/dockerize
|
||||
commands:
|
||||
- dockerize -wait tcp://postgres:5432 -wait tcp://influxdb:8086 -wait tcp://rabbitmq:5672 -wait tcp://redis:6379 -timeout 1m
|
||||
|
||||
- name: init-influxdb
|
||||
image: influxdb:1.7.8
|
||||
commands:
|
||||
- cat db/influxdb.sql | influx -host influxdb
|
||||
|
||||
- name: restore-bundle-cache
|
||||
pull: if-not-exists
|
||||
image: drillster/drone-volume-cache
|
||||
settings:
|
||||
restore: true
|
||||
mount:
|
||||
- vendor/bundle
|
||||
volumes:
|
||||
- name: bundle-cache
|
||||
path: /cache
|
||||
|
||||
- name: run-rspec
|
||||
image: ruby:2.6.5
|
||||
environment:
|
||||
DATABASE_HOST: "postgres"
|
||||
DATABASE_USER: "postgres"
|
||||
DATABASE_PASS: "changeme"
|
||||
DATABASE_ADAPTER: "postgresql"
|
||||
DATABASE_PORT: "5432"
|
||||
DATABASE_COLLATION: ""
|
||||
INFLUXDB_HOST: influxdb
|
||||
RABBITMQ_HOST: rabbitmq
|
||||
EVENT_API_RABBITMQ_HOST: rabbitmq
|
||||
REDIS_URL: redis://redis:6379
|
||||
RAILS_ENV: test
|
||||
VAULT_ADDR: ''
|
||||
VAULT_TOKEN: ''
|
||||
commands:
|
||||
- bin/init_config
|
||||
- gem install bundler:2.1.4
|
||||
- bundle install --jobs=$(nproc) --retry=3 --path vendor/bundle
|
||||
- bundle exec rake db:create db:migrate
|
||||
- LOG_LEVEL=warn bundle exec rspec
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
branch:
|
||||
- master
|
||||
- "*-stable"
|
||||
- "fix-db/*"
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-build-snapshot
|
||||
|
||||
steps:
|
||||
- name: git-tag-snapshot
|
||||
image: quay.io/openware/sdk-citools:2.6.7
|
||||
environment:
|
||||
BRANCH_NAME: master
|
||||
REPO_NAME: ${DRONE_REPO}
|
||||
GITHUB_API_KEY:
|
||||
from_secret: kite_bot_key
|
||||
commands:
|
||||
- BUNDLE_GEMFILE=/sdk/Gemfile bundle exec rake --rakefile=/sdk/Rakefile ci:prebuild
|
||||
- bin/genversion
|
||||
|
||||
# Build and push docker image using ".tags" file.
|
||||
- name: docker-build-and-push
|
||||
image: plugins/docker
|
||||
settings:
|
||||
username:
|
||||
from_secret: quay_username
|
||||
password:
|
||||
from_secret: quay_password
|
||||
repo: quay.io/openware/peatio
|
||||
registry: quay.io
|
||||
|
||||
# Redeploy on master.devkube.com
|
||||
- name: devkube-redeploy
|
||||
image: rubykube/microkube:0.2.0
|
||||
environment:
|
||||
WEBHOOK_JWT_SECRET:
|
||||
from_secret: devkube_webhook_secret
|
||||
commands:
|
||||
- export latest_image=quay.io/openware/peatio:$(cat .tags)
|
||||
- cd /home/app
|
||||
- bundle exec rake payload:send[peatio,$latest_image,http://master.devkube.com:1337]
|
||||
detach: true
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
|
||||
# Update changelog and API documentation, then push it to sdk repository and notify.
|
||||
- name: push-and-notify
|
||||
image: quay.io/openware/sdk-citools:2.6.7
|
||||
environment:
|
||||
BOT_USERNAME: kite-bot
|
||||
BOT_NAME: Kite Bot
|
||||
BOT_EMAIL: kite-bot@heliostech.fr
|
||||
REPO_NAME: ${DRONE_REPO}
|
||||
BRANCH_NAME: ${DRONE_BRANCH}
|
||||
SDK_BRANCH: ${DRONE_BRANCH}
|
||||
TELEGRAM_BOT_TOKEN:
|
||||
from_secret: telegram_bot_token
|
||||
TELEGRAM_CHAT_ID:
|
||||
from_secret: telegram_chat_id
|
||||
SLACK_TOKEN:
|
||||
from_secret: slack_token
|
||||
SLACK_CHANNEL:
|
||||
from_secret: slack_channel
|
||||
GITHUB_API_KEY:
|
||||
from_secret: kite_bot_key
|
||||
commands:
|
||||
- BUNDLE_GEMFILE=/sdk/Gemfile bundle exec rake --rakefile=/sdk/Rakefile ci:postbuild[/drone/src]
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
|
||||
# Trigger custom Peatio image build
|
||||
- name: build-custom-image
|
||||
image: curlimages/curl
|
||||
environment:
|
||||
SERVER: https://ci.openware.work
|
||||
REPO: openware/assembly-line
|
||||
BRANCH: master
|
||||
COMPONENT: peatio
|
||||
DRONE_TOKEN:
|
||||
from_secret: drone_token
|
||||
commands:
|
||||
- COMPONENT_VERSION=$(cat .tags)
|
||||
- "curl -s -XPOST -H \"Authorization: Bearer $DRONE_TOKEN\" -i \"$SERVER/api/repos/$REPO/builds?branch=$BRANCH&ASSEMBLY_COMPONENT_VERSION=$COMPONENT_VERSION&ASSEMBLY_COMPONENT=$COMPONENT\""
|
||||
detach: true
|
||||
when:
|
||||
branch:
|
||||
- master
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
branch:
|
||||
- master
|
||||
- "fix/*"
|
||||
- "integration/*"
|
||||
|
||||
image_pull_secrets:
|
||||
- dockerconfigjson
|
||||
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: docker-build-stable
|
||||
|
||||
steps:
|
||||
# Update application version
|
||||
- name: git-tag-stable
|
||||
image: quay.io/openware/sdk-citools:2.6.7
|
||||
environment:
|
||||
BOT_USERNAME: kite-bot
|
||||
BOT_NAME: Kite Bot
|
||||
BOT_EMAIL: kite-bot@heliostech.fr
|
||||
BRANCH_NAME: ${DRONE_BRANCH}
|
||||
REPO_NAME: ${DRONE_REPO}
|
||||
GITHUB_API_KEY:
|
||||
from_secret: kite_bot_key
|
||||
commands:
|
||||
- BUNDLE_GEMFILE=/sdk/Gemfile bundle exec rake --rakefile=/sdk/Rakefile ci:prebuild
|
||||
- bin/genversion
|
||||
|
||||
# Build and push docker image using ".tags" file.
|
||||
- name: docker-build-and-push
|
||||
image: plugins/docker
|
||||
settings:
|
||||
username:
|
||||
from_secret: quay_username
|
||||
password:
|
||||
from_secret: quay_password
|
||||
repo: quay.io/openware/peatio
|
||||
registry: quay.io
|
||||
|
||||
# Redeploy on master.devkube.com
|
||||
- name: devkube-redeploy
|
||||
image: rubykube/microkube:0.2.0
|
||||
environment:
|
||||
WEBHOOK_JWT_SECRET:
|
||||
from_secret: devkube_webhook_secret
|
||||
commands:
|
||||
- export latest_image=quay.io/openware/peatio:$(cat .tags)
|
||||
- cd /home/app
|
||||
- bundle exec rake payload:send[peatio,$latest_image,http://master.devkube.com:1337]
|
||||
detach: true
|
||||
|
||||
# Update changelog and API documentation, then push it to sdk repository and notify.
|
||||
- name: push-and-notify
|
||||
image: quay.io/openware/sdk-citools:2.6.7
|
||||
environment:
|
||||
BOT_USERNAME: kite-bot
|
||||
BOT_NAME: Kite Bot
|
||||
BOT_EMAIL: kite-bot@heliostech.fr
|
||||
REPO_NAME: ${DRONE_REPO}
|
||||
BRANCH_NAME: ${DRONE_BRANCH}
|
||||
SDK_BRANCH: ${DRONE_BRANCH}
|
||||
TELEGRAM_BOT_TOKEN:
|
||||
from_secret: telegram_bot_token
|
||||
TELEGRAM_CHAT_ID:
|
||||
from_secret: telegram_chat_id
|
||||
SLACK_TOKEN:
|
||||
from_secret: slack_token
|
||||
SLACK_CHANNEL:
|
||||
from_secret: slack_channel
|
||||
GITHUB_API_KEY:
|
||||
from_secret: kite_bot_key
|
||||
commands:
|
||||
- BUNDLE_GEMFILE=/sdk/Gemfile bundle exec rake --rakefile=/sdk/Rakefile ci:postbuild[/drone/src]
|
||||
|
||||
# Trigger custom Peatio image build
|
||||
- name: build-custom-image
|
||||
image: curlimages/curl
|
||||
environment:
|
||||
SERVER: https://ci.openware.work
|
||||
REPO: openware/assembly-line
|
||||
BRANCH: master
|
||||
COMPONENT: peatio
|
||||
DRONE_TOKEN:
|
||||
from_secret: drone_token
|
||||
commands:
|
||||
- COMPONENT_VERSION=$(cat .tags)
|
||||
- "curl -s -XPOST -H \"Authorization: Bearer $DRONE_TOKEN\" -i \"$SERVER/api/repos/$REPO/builds?branch=$BRANCH&ASSEMBLY_COMPONENT_VERSION=$COMPONENT_VERSION&ASSEMBLY_COMPONENT=$COMPONENT\""
|
||||
detach: true
|
||||
|
||||
- name: "Update global OpenDAX version"
|
||||
image: quay.io/openware/goci:0.0.6
|
||||
environment:
|
||||
GIT_TOKEN:
|
||||
from_secret: kite_bot_key
|
||||
commands:
|
||||
- /app/goci versions
|
||||
|
||||
trigger:
|
||||
event:
|
||||
- push
|
||||
branch:
|
||||
- "*-stable"
|
||||
|
||||
depends_on:
|
||||
- test-mysql-5.7
|
||||
- test-mariadb-10.3
|
||||
- test-mysql-8.0
|
||||
- test-postgresql-13.0
|
||||
|
||||
image_pull_secrets:
|
||||
- dockerconfigjson
|
||||
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.sh text eol=lf
|
||||
61
.gitignore
vendored
Normal file
61
.gitignore
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||
#
|
||||
# If you find yourself ignoring temporary files generated by your text editor
|
||||
# or operating system, you probably want to add a global ignore instead:
|
||||
# git config --global core.excludesfile ~/.gitignore_global
|
||||
|
||||
# Ignore bundler config
|
||||
/.bundle
|
||||
|
||||
# Ignore the default SQLite database.
|
||||
/db/*.sqlite3
|
||||
|
||||
# Ignore uploaded files
|
||||
/public/uploads
|
||||
|
||||
# Ignore all logfiles and tempfiles.
|
||||
log
|
||||
*.log
|
||||
tmp
|
||||
|
||||
# Ignore all config files
|
||||
/config/application.yml
|
||||
/config/markets.yml
|
||||
/config/markets.old.yml
|
||||
/config/deposit_channels.old.yml
|
||||
/config/withdraw_channels.old.yml
|
||||
/config/management_api_v1.yml
|
||||
/config/secrets
|
||||
/config/seed
|
||||
/config/bench/*.yml
|
||||
|
||||
# Ignore translations.js
|
||||
/public/javascripts/translations.js
|
||||
|
||||
# Ignore ctags
|
||||
.tags
|
||||
tags
|
||||
|
||||
# Ignore vim temporary files
|
||||
**/*.swp
|
||||
**/*.swo
|
||||
|
||||
# Ignore RubyMine files
|
||||
.idea
|
||||
|
||||
/vendor/bundle
|
||||
.env
|
||||
.DS_Store
|
||||
.agignore
|
||||
**/*.tgz
|
||||
|
||||
.cache
|
||||
.yarnrc
|
||||
.byebug_history
|
||||
.generators
|
||||
.rakeTasks
|
||||
|
||||
# Ignore bench reports & logs
|
||||
bench/reports/*
|
||||
bench/log/*
|
||||
/cofing/bench
|
||||
1
.ruby-version
Normal file
1
.ruby-version
Normal file
@@ -0,0 +1 @@
|
||||
ruby-2.6.6
|
||||
5511
CHANGELOG.md
Normal file
5511
CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load Diff
28
Dockerfile
Normal file
28
Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
||||
# syntax=docker/dockerfile:1.4
|
||||
# Fast app image — gems come from deps base (custom/dena-deps:1).
|
||||
# Rebuild deps when Gemfile.lock changes: docker build -f Dockerfile.deps -t custom/dena-deps:1 .
|
||||
ARG DEPS_IMAGE=custom/dena-deps:1
|
||||
FROM ${DEPS_IMAGE} AS base
|
||||
|
||||
ARG RAILS_ENV=production
|
||||
ENV RAILS_ENV=${RAILS_ENV} \
|
||||
APP_HOME=/home/app
|
||||
|
||||
USER app
|
||||
WORKDIR $APP_HOME
|
||||
|
||||
COPY --chown=app:app . .
|
||||
|
||||
RUN echo "# This file was overridden by default during docker image build." > Gemfile.plugin \
|
||||
&& ruby ./bin/init_config \
|
||||
&& chmod +x ./bin/logger \
|
||||
&& bundle exec rake tmp:create
|
||||
|
||||
EXPOSE 3000
|
||||
CMD ["bundle", "exec", "puma", "--config", "config/puma.rb"]
|
||||
|
||||
FROM base
|
||||
|
||||
COPY --chown=app:app Gemfile.plugin Gemfile.lock $APP_HOME/
|
||||
|
||||
RUN bundle install --path /opt/vendor/bundle --jobs $(nproc)
|
||||
37
Dockerfile.deps
Normal file
37
Dockerfile.deps
Normal file
@@ -0,0 +1,37 @@
|
||||
# syntax=docker/dockerfile:1.4
|
||||
# Heavy layer: Ruby gems + system packages. Rebuild only when Gemfile.lock changes.
|
||||
# docker build -f Dockerfile.deps -t custom/dena-deps:1 .
|
||||
FROM ruby:2.7.8
|
||||
|
||||
ARG UID=1000
|
||||
ARG GID=1000
|
||||
ENV KAIGARA_VERSION=0.1.9
|
||||
|
||||
ENV APP_HOME=/home/app \
|
||||
BUNDLE_PATH=/opt/vendor/bundle \
|
||||
TZ=UTC
|
||||
|
||||
RUN groupadd -r --gid ${GID} app \
|
||||
&& useradd --system --create-home --home ${APP_HOME} --shell /sbin/nologin --no-log-init \
|
||||
--gid ${GID} --uid ${UID} app
|
||||
|
||||
WORKDIR $APP_HOME
|
||||
|
||||
RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
|
||||
--mount=type=cache,target=/var/lib/apt,sharing=locked \
|
||||
apt-get update \
|
||||
&& apt-get install -y --no-install-recommends default-libmysqlclient-dev git curl vim \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& gem install bundler:2.2.16 \
|
||||
&& curl -fsSL -o /usr/bin/kaigara "https://github.com/openware/kaigara/releases/download/${KAIGARA_VERSION}/kaigara" \
|
||||
&& chmod +x /usr/bin/kaigara
|
||||
|
||||
COPY Gemfile Gemfile.lock ./
|
||||
|
||||
RUN mkdir -p ${BUNDLE_PATH} \
|
||||
&& chown -R app:app /opt/vendor ${APP_HOME} \
|
||||
&& su app -s /bin/bash -c "bundle config set --local path ${BUNDLE_PATH} \
|
||||
&& bundle install --jobs $(nproc)"
|
||||
|
||||
USER app
|
||||
ENV PATH="${BUNDLE_PATH}/bin:${PATH}"
|
||||
8
Dockerfile.plugin
Normal file
8
Dockerfile.plugin
Normal file
@@ -0,0 +1,8 @@
|
||||
# Extend base image with plugins.
|
||||
FROM rubykube/peatio:latest
|
||||
|
||||
# Copy Gemfile.plugin for installing plugins.
|
||||
COPY --chown=app:app Gemfile.plugin $APP_HOME
|
||||
|
||||
# Install plugins.
|
||||
RUN bundle install --path /opt/vendor/bundle
|
||||
103
Gemfile
Normal file
103
Gemfile
Normal file
@@ -0,0 +1,103 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
git_source(:github) { |repo_slug| "https://github.com/#{repo_slug}" }
|
||||
|
||||
ruby '~> 2.7'
|
||||
|
||||
gem 'ransack', '~> 2.3.2'
|
||||
gem 'rails', '~> 5.2.4'
|
||||
gem 'puma', '~> 3.12.2'
|
||||
gem 'mysql2', '~> 0.5.2'
|
||||
gem 'redis', '~> 4.1.2', require: ['redis', 'redis/connection/hiredis']
|
||||
gem 'hiredis', '~> 0.6.0'
|
||||
gem 'figaro', '~> 1.1.1'
|
||||
gem 'hashie', '~> 3.6.0'
|
||||
gem 'aasm', '~> 5.0.8'
|
||||
gem 'bunny', '~> 2.14.1'
|
||||
gem 'cancancan', '~> 3.1.0'
|
||||
gem 'enumerize', '~> 2.2.2'
|
||||
gem 'kaminari', '~> 1.2.1'
|
||||
gem 'rbtree', '~> 0.4.2'
|
||||
gem 'grape', '~> 1.3.1'
|
||||
gem 'grape-entity', '~> 0.7.1'
|
||||
gem 'grape-swagger', '~> 0.30.1'
|
||||
gem 'grape-swagger-ui', '~> 2.2.8'
|
||||
gem 'grape-swagger-entity', '~> 0.2.5'
|
||||
gem 'grape_logging', '~> 1.8.0'
|
||||
gem 'rack-attack', '~> 5.4.2'
|
||||
gem 'faraday', '~> 0.17'
|
||||
gem 'better-faraday', '~> 1.0.5'
|
||||
gem 'faraday_middleware', '~> 0.13.1'
|
||||
gem 'faye', '~> 1.4'
|
||||
gem 'eventmachine', '~> 1.2'
|
||||
gem 'em-synchrony', '~> 1.0'
|
||||
gem 'jwt', '~> 2.2.0'
|
||||
gem 'email_validator', '~> 1.6.0'
|
||||
gem 'validate_url', '~> 1.0.4'
|
||||
gem 'god', '~> 0.13.7', require: false
|
||||
gem 'arel-is-blank', '~> 1.0.0'
|
||||
gem 'sentry-raven', '~> 2.9.0', require: false
|
||||
gem 'memoist', '~> 0.16.0'
|
||||
gem 'method-not-implemented', '~> 1.0.1'
|
||||
gem 'validates_lengths_from_database', '~> 0.7.0'
|
||||
gem 'jwt-multisig', '~> 1.0.0'
|
||||
gem 'cash-addr', '~> 0.2.0', require: 'cash_addr'
|
||||
gem 'digest-sha3', '~> 1.1.0'
|
||||
gem 'scout_apm', '~> 2.4', require: false
|
||||
gem 'peatio', '~> 2.6.2'
|
||||
gem 'irix', '~> 2.6.0'
|
||||
gem 'rack-cors', '~> 1.0.6', require: false
|
||||
gem 'jwt-rack', '~> 0.1.0', require: false
|
||||
gem 'env-tweaks', '~> 1.0.0'
|
||||
gem 'vault', '~> 0.12', require: false
|
||||
gem 'vault-rails', git: 'https://github.com/rubykube/vault-rails'
|
||||
gem 'bootsnap', '>= 1.1.0', require: false
|
||||
gem 'net-http-persistent', '~> 3.0.1'
|
||||
gem 'influxdb', '~> 0.7.0'
|
||||
gem 'safe_yaml', '~> 1.0.5', require: 'safe_yaml/load'
|
||||
gem 'progress_bar'
|
||||
gem 'thread'
|
||||
gem 'dotenv-rails'
|
||||
gem 'rails_pdate'
|
||||
|
||||
group :development, :test do
|
||||
gem 'irb'
|
||||
gem 'bump', '~> 0.7'
|
||||
gem 'faker', '~> 1.8'
|
||||
gem 'pry-byebug', '~> 3.7'
|
||||
gem 'bullet', '~> 5.9'
|
||||
gem 'grape_on_rails_routes', '~> 0.3.2'
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem 'annotate', '~> 3.1.0'
|
||||
gem 'ruby-prof', '~> 0.17.0', require: false
|
||||
gem 'listen', '>= 3.0.5', '< 3.2'
|
||||
end
|
||||
|
||||
group :test do
|
||||
gem 'rspec-rails', '~> 3.8', '>= 3.8.2'
|
||||
gem 'rspec-retry', '~> 0.6'
|
||||
gem 'webmock', '~> 3.5'
|
||||
gem 'database_cleaner', '~> 1.7'
|
||||
gem 'mocha', '~> 1.8', require: false
|
||||
gem 'factory_bot_rails', '~> 5.0', '>= 5.0.2'
|
||||
gem 'timecop', '~> 0.9'
|
||||
gem 'rubocop-rspec', '~> 1.32', require: false
|
||||
end
|
||||
|
||||
# Load gems from Gemfile.plugin.
|
||||
Dir.glob File.expand_path('../Gemfile.plugin', __FILE__) do |file|
|
||||
eval_gemfile file
|
||||
end
|
||||
|
||||
gem "pg", "~> 1.2"
|
||||
|
||||
# Security patches — see Docs/10-cve-audit.md (phase 1)
|
||||
gem 'rack', '~> 2.2.13'
|
||||
gem 'nokogiri', '~> 1.15.5'
|
||||
gem 'rexml', '>= 3.3.9'
|
||||
gem 'tzinfo', '~> 1.2.10'
|
||||
gem 'rails-html-sanitizer', '>= 1.4.4'
|
||||
557
Gemfile.lock
Normal file
557
Gemfile.lock
Normal file
@@ -0,0 +1,557 @@
|
||||
GIT
|
||||
remote: https://github.com/rubykube/vault-rails
|
||||
revision: 167283b25c34ba8a61ce2ad505f68dced3189f43
|
||||
specs:
|
||||
vault-rails (0.6.0)
|
||||
rails (>= 4.1)
|
||||
vault (~> 0.14)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
aasm (5.0.8)
|
||||
concurrent-ruby (~> 1.0)
|
||||
actioncable (5.2.5)
|
||||
actionpack (= 5.2.5)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailer (5.2.5)
|
||||
actionpack (= 5.2.5)
|
||||
actionview (= 5.2.5)
|
||||
activejob (= 5.2.5)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.2.5)
|
||||
actionview (= 5.2.5)
|
||||
activesupport (= 5.2.5)
|
||||
rack (~> 2.0, >= 2.0.8)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.2.5)
|
||||
activesupport (= 5.2.5)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
activejob (5.2.5)
|
||||
activesupport (= 5.2.5)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.2.5)
|
||||
activesupport (= 5.2.5)
|
||||
activerecord (5.2.5)
|
||||
activemodel (= 5.2.5)
|
||||
activesupport (= 5.2.5)
|
||||
arel (>= 9.0)
|
||||
activestorage (5.2.5)
|
||||
actionpack (= 5.2.5)
|
||||
activerecord (= 5.2.5)
|
||||
marcel (~> 1.0.0)
|
||||
activesupport (5.2.5)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 0.7, < 2)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.7.0)
|
||||
public_suffix (>= 2.0.2, < 5.0)
|
||||
amq-protocol (2.3.2)
|
||||
amqp (1.8.0)
|
||||
amq-protocol (>= 2.2.0)
|
||||
eventmachine
|
||||
annotate (3.1.1)
|
||||
activerecord (>= 3.2, < 7.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
arel (9.0.0)
|
||||
arel-is-blank (1.0.0)
|
||||
arel (>= 6.0)
|
||||
arel-to-sql (~> 1.0)
|
||||
arel-to-sql (1.0.0)
|
||||
arel (>= 6.0)
|
||||
ast (2.4.2)
|
||||
aws-eventstream (1.1.1)
|
||||
aws-sigv4 (1.2.3)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
base58 (0.2.3)
|
||||
better-faraday (1.0.8)
|
||||
activesupport (>= 4.0, < 6.0)
|
||||
faraday (~> 0.12)
|
||||
bootsnap (1.7.4)
|
||||
msgpack (~> 1.0)
|
||||
builder (3.2.4)
|
||||
bullet (5.9.0)
|
||||
activesupport (>= 3.0.0)
|
||||
uniform_notifier (~> 1.11)
|
||||
bump (0.10.0)
|
||||
bunny (2.14.4)
|
||||
amq-protocol (~> 2.3, >= 2.3.0)
|
||||
byebug (11.1.3)
|
||||
cancancan (3.1.0)
|
||||
cash-addr (0.2.0)
|
||||
base58 (~> 0.2.2)
|
||||
clamp (1.3.2)
|
||||
coderay (1.1.3)
|
||||
concurrent-ruby (1.1.8)
|
||||
connection_pool (2.2.5)
|
||||
cookiejar (0.3.3)
|
||||
crack (0.4.5)
|
||||
rexml
|
||||
crass (1.0.7)
|
||||
daemons (1.4.0)
|
||||
database_cleaner (1.99.0)
|
||||
diff-lcs (1.4.4)
|
||||
digest-sha3 (1.1.0)
|
||||
dotenv (2.7.6)
|
||||
dotenv-rails (2.7.6)
|
||||
dotenv (= 2.7.6)
|
||||
railties (>= 3.2)
|
||||
dry-configurable (0.12.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
dry-core (~> 0.5, >= 0.5.0)
|
||||
dry-container (0.7.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
dry-configurable (~> 0.1, >= 0.1.3)
|
||||
dry-core (0.5.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
dry-inflector (0.2.0)
|
||||
dry-logic (1.2.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
dry-core (~> 0.5, >= 0.5)
|
||||
dry-types (1.5.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
dry-container (~> 0.3)
|
||||
dry-core (~> 0.5, >= 0.5)
|
||||
dry-inflector (~> 0.1, >= 0.1.2)
|
||||
dry-logic (~> 1.0, >= 1.0.2)
|
||||
em-http-request (1.1.7)
|
||||
addressable (>= 2.3.4)
|
||||
cookiejar (!= 0.3.1)
|
||||
em-socksify (>= 0.3)
|
||||
eventmachine (>= 1.0.3)
|
||||
http_parser.rb (>= 0.6.0)
|
||||
em-socksify (0.3.2)
|
||||
eventmachine (>= 1.0.0.beta.4)
|
||||
em-synchrony (1.0.6)
|
||||
eventmachine (>= 1.0.0.beta.1)
|
||||
em-websocket (0.5.2)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
email_validator (1.6.0)
|
||||
activemodel
|
||||
enumerize (2.2.2)
|
||||
activesupport (>= 3.2)
|
||||
env-tweaks (1.0.1)
|
||||
activesupport (>= 3.0, < 7.0)
|
||||
erubi (1.10.0)
|
||||
eventmachine (1.2.7)
|
||||
factory_bot (5.2.0)
|
||||
activesupport (>= 4.2.0)
|
||||
factory_bot_rails (5.2.0)
|
||||
factory_bot (~> 5.2.0)
|
||||
railties (>= 4.2.0)
|
||||
faker (1.9.6)
|
||||
i18n (>= 0.7)
|
||||
faraday (0.17.4)
|
||||
multipart-post (>= 1.2, < 3)
|
||||
faraday_middleware (0.13.1)
|
||||
faraday (>= 0.7.4, < 1.0)
|
||||
faye (1.4.0)
|
||||
cookiejar (>= 0.3.0)
|
||||
em-http-request (>= 1.1.6)
|
||||
eventmachine (>= 0.12.0)
|
||||
faye-websocket (>= 0.11.0)
|
||||
multi_json (>= 1.0.0)
|
||||
rack (>= 1.0.0)
|
||||
websocket-driver (>= 0.5.1)
|
||||
faye-websocket (0.11.0)
|
||||
eventmachine (>= 0.12.0)
|
||||
websocket-driver (>= 0.5.1)
|
||||
ffi (1.15.0)
|
||||
figaro (1.1.1)
|
||||
thor (~> 0.14)
|
||||
globalid (0.4.2)
|
||||
activesupport (>= 4.2.0)
|
||||
god (0.13.7)
|
||||
grape (1.3.3)
|
||||
activesupport
|
||||
builder
|
||||
dry-types (>= 1.1)
|
||||
mustermann-grape (~> 1.0.0)
|
||||
rack (>= 1.3.0)
|
||||
rack-accept
|
||||
grape-entity (0.7.1)
|
||||
activesupport (>= 4.0)
|
||||
multi_json (>= 1.3.2)
|
||||
grape-swagger (0.30.1)
|
||||
grape (>= 0.16.2)
|
||||
grape-swagger-entity (0.2.5)
|
||||
grape-entity (>= 0.5.0)
|
||||
grape-swagger (>= 0.20.4)
|
||||
grape-swagger-ui (2.2.8)
|
||||
railties (>= 3.1)
|
||||
grape_logging (1.8.4)
|
||||
grape
|
||||
rack
|
||||
grape_on_rails_routes (0.3.2)
|
||||
rails (>= 3.1.1)
|
||||
hashdiff (1.0.1)
|
||||
hashie (3.6.0)
|
||||
highline (2.0.3)
|
||||
hiredis (0.6.3)
|
||||
http_parser.rb (0.6.0)
|
||||
i18n (1.8.10)
|
||||
concurrent-ruby (~> 1.0)
|
||||
influxdb (0.7.0)
|
||||
io-console (0.5.9)
|
||||
irb (1.3.5)
|
||||
reline (>= 0.1.5)
|
||||
irix (2.6.0)
|
||||
em-synchrony (~> 1.0)
|
||||
em-websocket
|
||||
eventmachine
|
||||
faraday_middleware (~> 0.13.1)
|
||||
faye (~> 1.2)
|
||||
peatio (>= 2.4.2)
|
||||
jwt (2.2.3)
|
||||
jwt-multisig (1.0.5)
|
||||
activesupport (>= 4.0)
|
||||
jwt (~> 2.2)
|
||||
jwt-rack (0.1.1)
|
||||
jwt (~> 2.2)
|
||||
rack
|
||||
kaminari (1.2.1)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.2.1)
|
||||
kaminari-activerecord (= 1.2.1)
|
||||
kaminari-core (= 1.2.1)
|
||||
kaminari-actionview (1.2.1)
|
||||
actionview
|
||||
kaminari-core (= 1.2.1)
|
||||
kaminari-activerecord (1.2.1)
|
||||
activerecord
|
||||
kaminari-core (= 1.2.1)
|
||||
kaminari-core (1.2.1)
|
||||
listen (3.1.5)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
ruby_dep (~> 1.2)
|
||||
loofah (2.25.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
marcel (1.0.1)
|
||||
memoist (0.16.2)
|
||||
method-not-implemented (1.0.1)
|
||||
method_source (1.0.0)
|
||||
mini_mime (1.1.0)
|
||||
minitest (5.14.4)
|
||||
mocha (1.12.0)
|
||||
msgpack (1.4.2)
|
||||
multi_json (1.15.0)
|
||||
multipart-post (2.1.1)
|
||||
mustermann (1.1.1)
|
||||
ruby2_keywords (~> 0.0.1)
|
||||
mustermann-grape (1.0.1)
|
||||
mustermann (>= 1.0.0)
|
||||
mysql2 (0.5.3)
|
||||
net-http-persistent (3.0.1)
|
||||
connection_pool (~> 2.2)
|
||||
nio4r (2.5.7)
|
||||
nokogiri (1.15.7-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
options (2.3.2)
|
||||
parallel (1.20.1)
|
||||
parser (3.0.1.1)
|
||||
ast (~> 2.4.1)
|
||||
peatio (2.6.3)
|
||||
activemodel (> 5.2, <= 6.0.0)
|
||||
amqp
|
||||
bunny
|
||||
clamp
|
||||
em-synchrony (~> 1.0)
|
||||
em-websocket
|
||||
eventmachine
|
||||
faraday_middleware (~> 0.13.1)
|
||||
faye (~> 1.2)
|
||||
jwt
|
||||
mysql2
|
||||
prometheus-client
|
||||
thin
|
||||
peatio-bitcoincash (2.6.1)
|
||||
activesupport (~> 5.2.3)
|
||||
better-faraday (~> 1.0.5)
|
||||
cash-addr (~> 0.2.0)
|
||||
faraday (~> 0.17)
|
||||
memoist (~> 0.16.0)
|
||||
net-http-persistent (~> 3.0.1)
|
||||
peatio (>= 0.6.3)
|
||||
peatio-bitgo (2.6.6)
|
||||
activesupport (~> 5.2.3)
|
||||
better-faraday (~> 1.0.5)
|
||||
faraday (~> 0.17)
|
||||
memoist (~> 0.16.0)
|
||||
net-http-persistent (~> 3.0.1)
|
||||
peatio (>= 0.6.3)
|
||||
peatio-dash (2.6.1)
|
||||
activesupport (~> 5.2.3)
|
||||
better-faraday (~> 1.0.5)
|
||||
faraday (~> 0.17)
|
||||
memoist (~> 0.16.0)
|
||||
net-http-persistent (~> 3.0.1)
|
||||
peatio (>= 0.6.3)
|
||||
peatio-electrum (2.6.2)
|
||||
activesupport (~> 5.2.3)
|
||||
faraday (~> 0.17)
|
||||
net-http-persistent (~> 3.0.1)
|
||||
peatio (>= 0.6.3)
|
||||
peatio-litecoin (2.6.1)
|
||||
activesupport (~> 5.2.3)
|
||||
better-faraday (~> 1.0.5)
|
||||
faraday (~> 0.17)
|
||||
memoist (~> 0.16.0)
|
||||
net-http-persistent (~> 3.0.1)
|
||||
peatio (>= 0.6.3)
|
||||
peatio-ripple (2.6.1)
|
||||
activesupport (~> 5.2.3)
|
||||
better-faraday (~> 1.0.5)
|
||||
faraday (~> 0.17)
|
||||
memoist (~> 0.16.0)
|
||||
net-http-persistent (~> 3.0.1)
|
||||
peatio (>= 0.6.3)
|
||||
pg (1.2.3)
|
||||
polyamorous (2.3.2)
|
||||
activerecord (>= 5.2.1)
|
||||
progress_bar (1.3.3)
|
||||
highline (>= 1.6, < 3)
|
||||
options (~> 2.3.0)
|
||||
prometheus-client (2.1.0)
|
||||
pry (0.13.1)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
pry-byebug (3.9.0)
|
||||
byebug (~> 11.0)
|
||||
pry (~> 0.13.0)
|
||||
public_suffix (4.0.6)
|
||||
puma (3.12.6)
|
||||
racc (1.8.1)
|
||||
rack (2.2.23)
|
||||
rack-accept (0.4.5)
|
||||
rack (>= 0.4)
|
||||
rack-attack (5.4.2)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (1.0.6)
|
||||
rack (>= 1.6.0)
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (5.2.5)
|
||||
actioncable (= 5.2.5)
|
||||
actionmailer (= 5.2.5)
|
||||
actionpack (= 5.2.5)
|
||||
actionview (= 5.2.5)
|
||||
activejob (= 5.2.5)
|
||||
activemodel (= 5.2.5)
|
||||
activerecord (= 5.2.5)
|
||||
activestorage (= 5.2.5)
|
||||
activesupport (= 5.2.5)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 5.2.5)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.5.0)
|
||||
loofah (~> 2.19, >= 2.19.1)
|
||||
rails_pdate (0.0.4)
|
||||
rails (>= 4.0.0)
|
||||
railties (5.2.5)
|
||||
actionpack (= 5.2.5)
|
||||
activesupport (= 5.2.5)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.19.0, < 2.0)
|
||||
rainbow (3.0.0)
|
||||
rake (13.0.3)
|
||||
ransack (2.3.2)
|
||||
activerecord (>= 5.2.1)
|
||||
activesupport (>= 5.2.1)
|
||||
i18n
|
||||
polyamorous (= 2.3.2)
|
||||
rb-fsevent (0.10.4)
|
||||
rb-inotify (0.10.1)
|
||||
ffi (~> 1.0)
|
||||
rbtree (0.4.4)
|
||||
redis (4.1.4)
|
||||
regexp_parser (2.1.1)
|
||||
reline (0.2.5)
|
||||
io-console (~> 0.5)
|
||||
rexml (3.4.4)
|
||||
rspec-core (3.9.3)
|
||||
rspec-support (~> 3.9.3)
|
||||
rspec-expectations (3.9.4)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-mocks (3.9.1)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-rails (3.9.1)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 3.9.0)
|
||||
rspec-expectations (~> 3.9.0)
|
||||
rspec-mocks (~> 3.9.0)
|
||||
rspec-support (~> 3.9.0)
|
||||
rspec-retry (0.6.2)
|
||||
rspec-core (> 3.3)
|
||||
rspec-support (3.9.4)
|
||||
rubocop (0.93.1)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.7.1.5)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8)
|
||||
rexml
|
||||
rubocop-ast (>= 0.6.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 1.4.0, < 2.0)
|
||||
rubocop-ast (1.5.0)
|
||||
parser (>= 3.0.1.1)
|
||||
rubocop-rspec (1.44.1)
|
||||
rubocop (~> 0.87)
|
||||
rubocop-ast (>= 0.7.1)
|
||||
ruby-prof (0.17.0)
|
||||
ruby-progressbar (1.11.0)
|
||||
ruby2_keywords (0.0.4)
|
||||
ruby_dep (1.5.0)
|
||||
safe_yaml (1.0.5)
|
||||
scout_apm (2.6.10)
|
||||
parser
|
||||
sentry-raven (2.9.0)
|
||||
faraday (>= 0.7.6, < 1.0)
|
||||
sprockets (4.0.2)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.2.2)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
thin (1.8.0)
|
||||
daemons (~> 1.0, >= 1.0.9)
|
||||
eventmachine (~> 1.0, >= 1.0.4)
|
||||
rack (>= 1, < 3)
|
||||
thor (0.20.3)
|
||||
thread (0.2.2)
|
||||
thread_safe (0.3.6)
|
||||
timecop (0.9.4)
|
||||
tzinfo (1.2.11)
|
||||
thread_safe (~> 0.1)
|
||||
unicode-display_width (1.7.0)
|
||||
uniform_notifier (1.14.2)
|
||||
validate_url (1.0.13)
|
||||
activemodel (>= 3.0.0)
|
||||
public_suffix
|
||||
validates_lengths_from_database (0.7.0)
|
||||
activerecord (>= 3)
|
||||
vault (0.16.0)
|
||||
aws-sigv4
|
||||
webmock (3.12.2)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff (>= 0.4.0, < 2.0.0)
|
||||
websocket-driver (0.7.3)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
|
||||
PLATFORMS
|
||||
x86_64-linux
|
||||
|
||||
DEPENDENCIES
|
||||
aasm (~> 5.0.8)
|
||||
annotate (~> 3.1.0)
|
||||
arel-is-blank (~> 1.0.0)
|
||||
better-faraday (~> 1.0.5)
|
||||
bootsnap (>= 1.1.0)
|
||||
bullet (~> 5.9)
|
||||
bump (~> 0.7)
|
||||
bunny (~> 2.14.1)
|
||||
cancancan (~> 3.1.0)
|
||||
cash-addr (~> 0.2.0)
|
||||
database_cleaner (~> 1.7)
|
||||
digest-sha3 (~> 1.1.0)
|
||||
dotenv-rails
|
||||
em-synchrony (~> 1.0)
|
||||
email_validator (~> 1.6.0)
|
||||
enumerize (~> 2.2.2)
|
||||
env-tweaks (~> 1.0.0)
|
||||
eventmachine (~> 1.2)
|
||||
factory_bot_rails (~> 5.0, >= 5.0.2)
|
||||
faker (~> 1.8)
|
||||
faraday (~> 0.17)
|
||||
faraday_middleware (~> 0.13.1)
|
||||
faye (~> 1.4)
|
||||
figaro (~> 1.1.1)
|
||||
god (~> 0.13.7)
|
||||
grape (~> 1.3.1)
|
||||
grape-entity (~> 0.7.1)
|
||||
grape-swagger (~> 0.30.1)
|
||||
grape-swagger-entity (~> 0.2.5)
|
||||
grape-swagger-ui (~> 2.2.8)
|
||||
grape_logging (~> 1.8.0)
|
||||
grape_on_rails_routes (~> 0.3.2)
|
||||
hashie (~> 3.6.0)
|
||||
hiredis (~> 0.6.0)
|
||||
influxdb (~> 0.7.0)
|
||||
irb
|
||||
irix (~> 2.6.0)
|
||||
jwt (~> 2.2.0)
|
||||
jwt-multisig (~> 1.0.0)
|
||||
jwt-rack (~> 0.1.0)
|
||||
kaminari (~> 1.2.1)
|
||||
listen (>= 3.0.5, < 3.2)
|
||||
memoist (~> 0.16.0)
|
||||
method-not-implemented (~> 1.0.1)
|
||||
mocha (~> 1.8)
|
||||
mysql2 (~> 0.5.2)
|
||||
net-http-persistent (~> 3.0.1)
|
||||
nokogiri (~> 1.15.5)
|
||||
peatio (~> 2.6.2)
|
||||
peatio-bitcoincash (~> 2.6.1)
|
||||
peatio-bitgo (~> 2.6.3)
|
||||
peatio-dash (~> 2.6.1)
|
||||
peatio-electrum (~> 2.6.2)
|
||||
peatio-litecoin (~> 2.6.1)
|
||||
peatio-ripple (~> 2.6.1)
|
||||
pg (~> 1.2)
|
||||
progress_bar
|
||||
pry-byebug (~> 3.7)
|
||||
puma (~> 3.12.2)
|
||||
rack (~> 2.2.13)
|
||||
rack-attack (~> 5.4.2)
|
||||
rack-cors (~> 1.0.6)
|
||||
rails (~> 5.2.4)
|
||||
rails-html-sanitizer (>= 1.4.4)
|
||||
rails_pdate
|
||||
ransack (~> 2.3.2)
|
||||
rbtree (~> 0.4.2)
|
||||
redis (~> 4.1.2)
|
||||
rexml (>= 3.3.9)
|
||||
rspec-rails (~> 3.8, >= 3.8.2)
|
||||
rspec-retry (~> 0.6)
|
||||
rubocop-rspec (~> 1.32)
|
||||
ruby-prof (~> 0.17.0)
|
||||
safe_yaml (~> 1.0.5)
|
||||
scout_apm (~> 2.4)
|
||||
sentry-raven (~> 2.9.0)
|
||||
thread
|
||||
timecop (~> 0.9)
|
||||
tzinfo (~> 1.2.10)
|
||||
validate_url (~> 1.0.4)
|
||||
validates_lengths_from_database (~> 0.7.0)
|
||||
vault (~> 0.12)
|
||||
vault-rails!
|
||||
webmock (~> 3.5)
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.7.8p225
|
||||
|
||||
BUNDLED WITH
|
||||
2.2.16
|
||||
11
Gemfile.plugin
Normal file
11
Gemfile.plugin
Normal file
@@ -0,0 +1,11 @@
|
||||
source 'https://rubygems.org'
|
||||
git_source(:github) { |repo_slug| "https://github.com/#{repo_slug}" }
|
||||
|
||||
# List your plugins here.
|
||||
|
||||
gem 'peatio-dash', '~> 2.6.1'
|
||||
gem 'peatio-litecoin', '~> 2.6.1'
|
||||
gem 'peatio-bitcoincash', '~> 2.6.1'
|
||||
gem 'peatio-ripple', '~> 2.6.1'
|
||||
gem 'peatio-bitgo', '~> 2.6.3'
|
||||
gem 'peatio-electrum', '~> 2.6.2'
|
||||
25
LICENSE.md
Normal file
25
LICENSE.md
Normal file
@@ -0,0 +1,25 @@
|
||||
The MIT License (MIT)
|
||||
=====================
|
||||
|
||||
Copyright © `2018` `Helios Technologies`
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software and associated documentation
|
||||
files (the “Software”), to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
||||
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
261
README.md
Normal file
261
README.md
Normal file
@@ -0,0 +1,261 @@
|
||||
<div dir="rtl">
|
||||
|
||||
# Dena — موتور معاملات Fibitex (Peatio)
|
||||
|
||||
**Dena** فورک [Peatio](https://github.com/openware/peatio) `2-6-stable` برای صرافی Fibitex.
|
||||
|
||||
| OpenDAX | Fibitex |
|
||||
|---------|---------|
|
||||
| Peatio | **Dena** |
|
||||
| `/api/v2/peatio` | `/api/v2/dena` |
|
||||
|
||||
## پیشنیازها
|
||||
|
||||
Ruby 2.6.6، Rails 5.2، MySQL، Redis، RabbitMQ — معمولاً via Alvand-P Docker.
|
||||
|
||||
## راهاندازی سریع
|
||||
|
||||
<div dir="ltr">
|
||||
|
||||
```bash
|
||||
# توصیه: از Alvand-P
|
||||
cd ../Alvand-P && rake service:all
|
||||
# یا standalone: ./bin/setup && rails s
|
||||
```
|
||||
|
||||
</div>
|
||||
|
||||
لوکال: [`Docs/06-setup-local.md`](../Docs/06-setup-local.md) · Staging: [`Docs/Fibitex-staging-deploy.md`](../Docs/Fibitex-staging-deploy.md)
|
||||
|
||||
## مستندات
|
||||
|
||||
| سند | موضوع |
|
||||
|-----|--------|
|
||||
| [`Docs/02-dena-microservice.md`](../Docs/02-dena-microservice.md) | معماری Dena |
|
||||
| [`Docs/README.md`](../Docs/README.md) | فهرست مرکزی |
|
||||
| [`Alvand-P/README.md`](../Alvand-P/README.md) | Deploy compose |
|
||||
| [`carbon_platform/README.md`](../carbon_platform/README.md) | Market making |
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
### Peatio - Cryptocurrency Exchange Software
|
||||
|
||||
[](https://ci.openware.work/openware/peatio)
|
||||
[](https://t.me/peatio)
|
||||
|
||||
## What is Peatio
|
||||
|
||||
Peatio is a free and open-source crypto-currency exchange implementation with the Rails framework.
|
||||
This is a fork of Peatio designed for micro-services architecture. We have simplified the code
|
||||
in order to use only Peatio API with external frontend and server components.
|
||||
|
||||
Peatio is the core accounting component and configuration for markets; it is part of OpenDAX system.
|
||||
|
||||
## Getting Started
|
||||
|
||||
OpenDAX is a container distribution, the fastest way to install the full stack is using [OpenDAX](https://github.com/openware/opendax)
|
||||
OpenDAX can be installed under 15 minutes on any Linux / Mac OS X environment with Docker.
|
||||
|
||||
```
|
||||
# To install
|
||||
git clone https://github.com/openware/opendax.git
|
||||
# Follow the README instructions
|
||||
# Configure config/app.yml
|
||||
bundle install
|
||||
bundle exec rake service:all
|
||||
# Open your browser on www.app.local (please it in /etc/hosts)
|
||||
```
|
||||
|
||||
To build your own exchange you should now run Peatio as a backend instead of forking the repository,
|
||||
and extend it using other microservices such as [Barong](https://www.github.com/rubykube/barong).
|
||||
|
||||
## System Overview
|
||||
|
||||

|
||||
|
||||
This is a service oriented architecture; the system is designed to be customized by creating Applogic which is your api code.
|
||||
Barong will dispatch the traffic on your api to extend the current system.
|
||||
|
||||
## Mission
|
||||
|
||||
Our mission is to build an open-source [crypto exchange software](https://www.openware.com) with a high performance trading engine and incomparable security. We are moving toward dev/ops best practices of running an enterprise grade exchange.
|
||||
|
||||
We provide webinar or on site training for installing, configuring and administration best practices of Peatio.
|
||||
Feel free to contact us for joining the next training session: [Openware.com](https://www.openware.com)
|
||||
|
||||
Help is greatly appreciated, feel free to submit pull-requests or open issues.
|
||||
|
||||
## Things You Should Know
|
||||
|
||||
**RUNNING A CRYPTO CURRENCY EXCHANGE IS HARD.**
|
||||
|
||||
This repository is not a turn key solution and will require engineering and design of security process by your company, with or without our assistance. This repository is one component among many we recommend using for composing an enterprise grade exchange. It is highly recommended to deploy a UAT environment and build automated tests for your needs, including Functional tests, Smoke tests and Security vulnerability scans. You may not need to have an active developer on Peatio source code, however, we recommend the following team setup: 1 dev/ops, 3 frontend developers (react / angular), 2 QA engineers, 1 Security Officer.
|
||||
|
||||
**SECURITY KNOWLEDGE IS A REQUIREMENT.**
|
||||
|
||||
Peatio cannot protect your customers if you leave your admin password 1234567, or open sensitive ports to public internet. No one can. Running an exchange is a very risky task because you're dealing with money directly. If you don't know how to make your exchange secure, hire an expert.
|
||||
|
||||
You must know what you're doing, there's no shortcut. Please get prepared before you continue:
|
||||
|
||||
* Rails knowledge
|
||||
* Security knowledge
|
||||
* Cloud and Linux administration
|
||||
* Docker and Kubernetes administration
|
||||
* Micro-services and OAuth 2.0
|
||||
|
||||
## Features
|
||||
|
||||
* Designed as high performance crypto currency exchange
|
||||
* Built-in high performance matching-engine
|
||||
* Built-in multiple wallet support (e.g. deposit, hot, warm and cold)
|
||||
* Built-in [plugable coin API](docs/coins/development.md)
|
||||
* Build-in Management API - server-to-server API with high privileges
|
||||
* Build-in RabbitMQ Event API
|
||||
* Usability and scalability
|
||||
* Websocket API and high frequency trading support
|
||||
* Support multiple digital currencies (e.g. Bitcoin, Litecoin, Ethereum, Ripple etc.)
|
||||
* Support ERC20 Tokens
|
||||
* API endpoint for FIAT deposits or payment gateways.
|
||||
* Powerful admin dashboard and management tools
|
||||
* Highly configurable and extendable
|
||||
* Industry standard security out of box
|
||||
* Maintained by [Openware.com](https://www.openware.com)
|
||||
* [KYC Verification](http://en.wikipedia.org/wiki/Know_your_customer) provided by [Barong](https://www.github.com/openware/barong)
|
||||
|
||||
## Contribute
|
||||
|
||||
Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details on how to contribute
|
||||
issues, fixes, and patches to this project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
We advice to use [minimalistic environment](#minimalistic-local-development-environment-with-docker-compose) if you want to develop only Peatio and don't touch processes which interact with other components.
|
||||
|
||||
Otherwise we advice to use [microkube based environment](#local-development-environment-with-microkube)
|
||||
|
||||
### Minimalistic local development environment with docker-compose:
|
||||
|
||||
#### Prerequisites
|
||||
* [Docker](https://docs.docker.com/install/) installed
|
||||
* [Docker compose](https://docs.docker.com/compose/install/) installed
|
||||
* Ruby 2.6.5
|
||||
* Rails 5.2.3+
|
||||
|
||||
## Installation
|
||||
|
||||
### Local development install
|
||||
|
||||
1. Set up initial configuration `./bin/setup`
|
||||
2. Start peatio daemons `god -c lib/daemons/daemons.god`
|
||||
3. Add this to your `/etc/hosts`:
|
||||
```
|
||||
127.0.0.1 www.app.local
|
||||
127.0.0.1 peatio.app.local
|
||||
127.0.0.1 barong.app.local
|
||||
```
|
||||
4. Start rails server `JWT_PUBLIC_KEY=$(cat config/secrets/rsa-key.pub| base64 -w0) rails s -b 0.0.0.0`
|
||||
(`base64 -b0` for macOS)
|
||||
|
||||
|
||||
### Local development environment with docker compose:
|
||||
|
||||
We suggest you to start using Peatio by installing [OpenDAX](https://github.com/openware/opendax).
|
||||
[OpenDAX](https://github.com/openware/opendax) which is based on
|
||||
[Docker containers](https://www.docker.com/what-docker) is a convenient and straightforward way to start
|
||||
Peatio crypto exchange software development environment.
|
||||
|
||||
#### Prerequisites
|
||||
* [Docker](https://docs.docker.com/install/) installed
|
||||
* [Docker compose](https://docs.docker.com/compose/install/) installed
|
||||
|
||||
#### Start OpenDAX ready to use
|
||||
|
||||
Follow [OpenDAX](https://github.com/openware/opendax) documentation for the latest Peatio installation information.
|
||||
|
||||
#### [Barong](https://github.com/openware/barong)
|
||||
|
||||
Barong is an essential part of Openware [crypto exchange software](https://www.openware.com) stack. It's providing the authentication service, it provides KyC and 2FA features out of the box.
|
||||
|
||||
Barong manages roles and kyc level across all applications from the OpenDAX stack. It can be easily extended using Rest Management API and Event API.
|
||||
|
||||
##### Barong key features
|
||||
|
||||
* KYC Verification for individuals
|
||||
* SMS and Google two-factor authentication
|
||||
* Transaction Signature support
|
||||
* Implement JWT standard to authenticate users of every microservice of the OpenDAX stack
|
||||
|
||||
Start barong:
|
||||
|
||||
```sh
|
||||
$> docker-compose run --rm barong bash -c "./bin/link_config && ./bin/setup"
|
||||
$> docker-compose up -d barong
|
||||
```
|
||||
|
||||
This will output password for **admin@barong.io**. Default password is **`Qwerty123`**
|
||||
|
||||
#### Peatio
|
||||
|
||||
Start peatio server
|
||||
|
||||
```sh
|
||||
$> docker-compose run --rm peatio bash -c "bundle exec rake db:create db:migrate db:seed"
|
||||
$> docker-compose up -d peatio
|
||||
```
|
||||
|
||||
After all of that you can start using Peatio in your browser just by following one of the hosts which you added earlier.
|
||||
|
||||
## API
|
||||
|
||||
You can interact with Peatio through API:
|
||||
|
||||
* Account, Market & Public API v2
|
||||
* Management API v2
|
||||
* Websocket API
|
||||
* Event API (AMQP)
|
||||
|
||||
## Getting Involved
|
||||
We want to make it super-easy for Peatio users and contributors to talk to us and connect with each other, to share ideas, solve problems and help make Peatio awesome. Here are the main channels we're running currently, we'd love to hear from you on one of them:
|
||||
|
||||
### Discourse
|
||||
|
||||
[Rubykube Discourse Forum](https://discuss.rubykube.io)
|
||||
|
||||
This is for all Peatio users. You can find guides, recipes, questions, and answers from Snowplow users including the Peatio.tech team.
|
||||
We welcome questions and contributions!
|
||||
|
||||
### Telegram
|
||||
|
||||
[@peatio](https://t.me/peatio)
|
||||
|
||||
Chat with us and other community members on Telegram.
|
||||
|
||||
### GitHub
|
||||
Peatio issues
|
||||
|
||||
If you spot a bug, then please raise an issue in our main GitHub project (Openware Peatio)[https://github.com/openware/peatio/]; likewise, if you have developed a new feature or an improvement in your Rubykube Peatio fork, then send us a pull request!
|
||||
If you want to brainstorm a potential new feature, then the Telegram group is the best place to start (see above).
|
||||
|
||||
### Email
|
||||
hello@openware.com
|
||||
|
||||
If you want to talk directly to us (e.g. about a commercially sensitive issue), email is the easiest way.
|
||||
|
||||
## Getting Support and Customization
|
||||
|
||||
If you need help with running/deploying/customizing Peatio,
|
||||
you can contact us on [Openware.com](https://www.openware.com).
|
||||
|
||||
Contact us by email: [hello@openware.com](mailto:hello@openware.com)
|
||||
|
||||
## License
|
||||
|
||||
Peatio is released under the terms of the [MIT license](http://peatio.mit-license.org).
|
||||
|
||||
## What is Peatio?
|
||||
|
||||
[Peatio](http://en.wikipedia.org/wiki/Pixiu) (Chinese: 貔貅) is a Chinese mythical hybrid creature
|
||||
considered to be a very powerful protector to practitioners of Feng Shui.
|
||||
|
||||
13
Rakefile
Normal file
13
Rakefile
Normal file
@@ -0,0 +1,13 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
#!/usr/bin/env rake
|
||||
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
||||
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
||||
|
||||
require File.expand_path('../config/application', __FILE__)
|
||||
|
||||
Peatio::Application.load_tasks
|
||||
|
||||
# Load additional tasks from "support/tasks".
|
||||
Dir.glob('lib/peatio/tasks/**/*') { |f| load(f) }
|
||||
9
app/api/mount.rb
Normal file
9
app/api/mount.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
module API
|
||||
class Mount < Grape::API
|
||||
PREFIX = '/api'
|
||||
|
||||
cascade false
|
||||
|
||||
mount API::V2::Mount => API::V2::Mount::API_VERSION
|
||||
end
|
||||
end
|
||||
75
app/api/v2/account/balances.rb
Normal file
75
app/api/v2/account/balances.rb
Normal file
@@ -0,0 +1,75 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Balances < Grape::API
|
||||
|
||||
helpers ::API::V2::ParamHelpers
|
||||
|
||||
# TODO: Add failures.
|
||||
# TODO: Move desc hash options to block once issues are resolved.
|
||||
# https://github.com/ruby-grape/grape/issues/1789
|
||||
# https://github.com/ruby-grape/grape-swagger/issues/705
|
||||
desc 'Get list of user accounts',
|
||||
is_array: true,
|
||||
success: API::V2::Entities::Account
|
||||
params do
|
||||
use :pagination
|
||||
optional :nonzero,
|
||||
type: { value: Boolean, message: 'account.balances.invalid_nonzero' },
|
||||
default: false,
|
||||
desc: 'Filter non zero balances.'
|
||||
optional :search, type: JSON, default: {} do
|
||||
optional :currency_code,
|
||||
as: :code,
|
||||
type: String
|
||||
optional :currency_name,
|
||||
as: :name,
|
||||
type: String
|
||||
end
|
||||
end
|
||||
get '/balances' do
|
||||
user_authorize! :read, ::Operations::Account
|
||||
|
||||
search_params = params[:search]
|
||||
.slice(:code, :name)
|
||||
.transform_keys {|k| "#{k}_cont"}
|
||||
.merge(m: 'or')
|
||||
|
||||
accounts = ::Currency.visible.ransack(search_params).result.each_with_object([]) do |c, result|
|
||||
account = ::Account.find_by(currency: c, member: current_user)
|
||||
if account.present?
|
||||
next if params[:nonzero].present? && account.amount.zero? && account.locked.zero?
|
||||
|
||||
result << account
|
||||
elsif account.blank? && params[:nonzero].blank?
|
||||
result << ::Account.new(currency: c, member: current_user)
|
||||
end
|
||||
end
|
||||
|
||||
present paginate(accounts),
|
||||
with: Entities::Account, current_user: current_user
|
||||
end
|
||||
|
||||
desc 'Get user account by currency' do
|
||||
success API::V2::Entities::Account
|
||||
# TODO: Add failures.
|
||||
end
|
||||
params do
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.pluck(:id) }, message: 'account.currency.doesnt_exist' },
|
||||
desc: 'The currency code.'
|
||||
end
|
||||
get '/balances/:currency', requirements: { currency: /[\w\.\-]+/ } do
|
||||
user_authorize! :read, ::Operations::Account
|
||||
|
||||
present current_user.accounts.visible.find_by!(currency_id: params[:currency]),
|
||||
with: API::V2::Entities::Account
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
205
app/api/v2/account/beneficiaries.rb
Normal file
205
app/api/v2/account/beneficiaries.rb
Normal file
@@ -0,0 +1,205 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Beneficiaries < Grape::API
|
||||
|
||||
before { withdraws_must_be_permitted! }
|
||||
|
||||
namespace :beneficiaries do
|
||||
desc 'Get list of user beneficiaries',
|
||||
is_array: true,
|
||||
success: API::V2::Entities::Beneficiary
|
||||
|
||||
params do
|
||||
optional :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist' },
|
||||
as: :currency_id,
|
||||
desc: 'Beneficiary currency code.'
|
||||
optional :state,
|
||||
type: String,
|
||||
values: { value: -> { ::Beneficiary::STATES_AVAILABLE_FOR_MEMBER.map(&:to_s) }, message: 'account.beneficiary.invalid_state'},
|
||||
desc: 'Defines either beneficiary active - user can use it to withdraw money'\
|
||||
'or pending - requires beneficiary activation with pin.'
|
||||
|
||||
end
|
||||
get do
|
||||
user_authorize! :read, ::Beneficiary
|
||||
|
||||
current_user
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.tap do |q|
|
||||
q.where!(currency_id: params[:currency_id]) if params[:currency_id].present?
|
||||
end
|
||||
.tap do |q|
|
||||
q.where!(state: params[:state]) if params[:state].present?
|
||||
end
|
||||
.yield_self do |b|
|
||||
present paginate(b), with: API::V2::Entities::Beneficiary
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get beneficiary by ID',
|
||||
success: API::V2::Entities::Beneficiary
|
||||
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'account.beneficiary.non_integer_id' },
|
||||
desc: 'Beneficiary Identifier in Database'
|
||||
end
|
||||
get ':id' do
|
||||
user_authorize! :read, ::Beneficiary
|
||||
|
||||
current_user
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.find_by!(id: params[:id])
|
||||
.yield_self { |b| present b, with: API::V2::Entities::Beneficiary }
|
||||
end
|
||||
|
||||
desc 'Create new beneficiary',
|
||||
success: API::V2::Entities::Beneficiary
|
||||
|
||||
params do
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist' },
|
||||
as: :currency_id,
|
||||
desc: 'Beneficiary currency code.'
|
||||
requires :name,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
values: { value: ->(v) { v.present? && v.size <= 64 }, message: 'account.beneficiary.too_long_name' },
|
||||
desc: 'Human rememberable name which refer beneficiary.'
|
||||
optional :description,
|
||||
type: String,
|
||||
values: { value: ->(v) { v.size <= 255 }, message: 'account.beneficiary.too_long_description' },
|
||||
desc: 'Human rememberable name which refer beneficiary.'
|
||||
requires :data,
|
||||
type: { value: JSON, message: 'account.beneficiary.non_json_data' },
|
||||
allow_blank: false,
|
||||
desc: 'Beneficiary data in JSON format'
|
||||
end
|
||||
post do
|
||||
user_authorize! :create, ::Beneficiary
|
||||
|
||||
declared_params = declared(params)
|
||||
|
||||
currency = Currency.find_by!(id: params[:currency_id])
|
||||
|
||||
if !currency.withdrawal_enabled?
|
||||
error!({ errors: ['account.currency.withdrawal_disabled'] }, 422)
|
||||
elsif currency.coin? && declared_params.dig(:data, :address).blank?
|
||||
error!({ errors: ['account.beneficiary.missing_address_in_data'] }, 422)
|
||||
elsif currency.fiat? && declared_params.dig(:data, :full_name).blank?
|
||||
error!({ errors: ['account.beneficiary.missing_full_name_in_data'] }, 422)
|
||||
end
|
||||
|
||||
# Since data is stored in MySQL JSON format we iterate through all
|
||||
# beneficiaries one by one to detect duplicated address.
|
||||
if currency.coin? &&
|
||||
current_user
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.where(currency: currency)
|
||||
.any? { |b| b.data['address'] == declared_params.dig(:data, :address) }
|
||||
error!({ errors: ['account.beneficiary.duplicate_address'] }, 422)
|
||||
end
|
||||
|
||||
present current_user
|
||||
.beneficiaries
|
||||
.create!(declared_params),
|
||||
with: API::V2::Entities::Beneficiary
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
report_exception(e)
|
||||
error!({ errors: ['account.beneficiary.failed_to_create'] }, 422)
|
||||
end
|
||||
|
||||
desc 'Resend beneficiary pin'
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'account.beneficiary.non_integer_id' },
|
||||
desc: 'Beneficiary Identifier in Database'
|
||||
end
|
||||
patch ':id/resend_pin' do
|
||||
user_authorize! :update, ::Beneficiary
|
||||
|
||||
beneficiary = current_user
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.find_by!(id: params[:id])
|
||||
|
||||
unless beneficiary.pending?
|
||||
error!({ errors: ['account.beneficiary.cant_resend'] }, 422)
|
||||
end
|
||||
|
||||
if Time.now - beneficiary.sent_at < 60
|
||||
error!({ errors: ['account.beneficiary.cant_resend_within_1_minute'], sent_at: beneficiary.sent_at.iso8601 }, 422)
|
||||
end
|
||||
|
||||
beneficiary.regenerate_pin!
|
||||
status 204
|
||||
end
|
||||
|
||||
|
||||
desc 'Activates beneficiary with pin',
|
||||
success: API::V2::Entities::Beneficiary
|
||||
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'account.beneficiary.non_integer_id' },
|
||||
desc: 'Beneficiary Identifier in Database'
|
||||
|
||||
requires :pin,
|
||||
type: { value: Integer, message: 'account.beneficiary.non_integer_pin' },
|
||||
desc: 'Pin code for beneficiary activation'
|
||||
end
|
||||
patch ':id/activate' do
|
||||
user_authorize! :update, ::Beneficiary
|
||||
|
||||
beneficiary = current_user
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.find_by!(id: params[:id])
|
||||
|
||||
unless beneficiary.pending?
|
||||
error!({ errors: ['account.beneficiary.cant_activate'] }, 422)
|
||||
end
|
||||
|
||||
if beneficiary.activate!(params[:pin])
|
||||
present beneficiary, with: API::V2::Entities::Beneficiary
|
||||
else
|
||||
error!({ errors: ['account.beneficiary.invalid_pin'] }, 422)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Delete beneficiary'
|
||||
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'account.beneficiary.non_integer_id' },
|
||||
desc: 'Beneficiary Identifier in Database'
|
||||
end
|
||||
delete ':id' do
|
||||
user_authorize! :destroy, ::Beneficiary
|
||||
|
||||
beneficiary = current_user
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.find_by!(id: params[:id])
|
||||
|
||||
if beneficiary.archive!
|
||||
body false
|
||||
else
|
||||
error!({ errors: ['account.beneficiary.cant_delete'] }, 422)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
30
app/api/v2/account/bonuses.rb
Normal file
30
app/api/v2/account/bonuses.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Bonuses < Grape::API
|
||||
desc 'Get bonus simple data of a user',
|
||||
is_array: true
|
||||
get '/bonus' do
|
||||
user_bonuses = Bonus.where(bonus_member: current_user)
|
||||
all = user_bonuses.inject(0){ |sum, x| sum + x.amount }
|
||||
h24 = user_bonuses.h24.inject(0){ |sum, x| sum + x.amount }
|
||||
number = Member.where(referral_uid: current_user.uid).count
|
||||
|
||||
{ 'h24': h24, 'all': all, 'number': number }
|
||||
|
||||
end
|
||||
|
||||
desc 'Get bonus report',
|
||||
is_array: true
|
||||
get '/bonus/report' do
|
||||
bonus = Bonus.where(bonus_member: current_user)
|
||||
present paginate(bonus), with: Entities::Bonus
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
207
app/api/v2/account/deposits.rb
Normal file
207
app/api/v2/account/deposits.rb
Normal file
@@ -0,0 +1,207 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../validations'
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Deposits < Grape::API
|
||||
|
||||
before { deposits_must_be_permitted! }
|
||||
|
||||
desc 'Get your deposits history.',
|
||||
is_array: true,
|
||||
success: API::V2::Entities::Deposit
|
||||
|
||||
params do
|
||||
optional :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist' },
|
||||
desc: 'Currency code'
|
||||
optional :state,
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Deposit.aasm.states.map(&:name).map(&:to_s)).blank? }, message: 'account.deposit.invalid_state' },
|
||||
desc: 'Filter deposits by states.'
|
||||
optional :txid,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Deposit transaction id.'
|
||||
optional :time_from,
|
||||
allow_blank: { value: false, message: 'account.deposit.empty_time_from' },
|
||||
type: { value: Integer, message: 'account.deposit.non_integer_time_from' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'
|
||||
optional :time_to,
|
||||
type: { value: Integer, message: 'account.deposit.non_integer_time_to' },
|
||||
allow_blank: { value: false, message: 'account.deposit.empty_time_to' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'
|
||||
optional :limit,
|
||||
type: { value: Integer, message: 'account.deposit.non_integer_limit' },
|
||||
values: { value: 1..100, message: 'account.deposit.invalid_limit' },
|
||||
default: 100,
|
||||
desc: "Number of deposits per page (defaults to 100, maximum is 100)."
|
||||
optional :page,
|
||||
type: { value: Integer, message: 'account.deposit.non_integer_page' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'account.deposit.non_positive_page'},
|
||||
default: 1,
|
||||
desc: 'Page number (defaults to 1).'
|
||||
end
|
||||
get "/deposits" do
|
||||
user_authorize! :read, ::Deposit
|
||||
|
||||
currency = Currency.find(params[:currency]) if params[:currency].present?
|
||||
|
||||
current_user.deposits.order(id: :desc)
|
||||
.tap { |q| q.where!(currency: currency) if currency }
|
||||
.tap { |q| q.where!(txid: params[:txid]) if params[:txid] }
|
||||
.tap { |q| q.where!(aasm_state: params[:state]) if params[:state] }
|
||||
.tap { |q| q.where!('updated_at >= ?', Time.at(params[:time_from])) if params[:time_from].present? }
|
||||
.tap { |q| q.where!('updated_at <= ?', Time.at(params[:time_to])) if params[:time_to].present? }
|
||||
.tap { |q| present paginate(q), with: API::V2::Entities::Deposit }
|
||||
end
|
||||
|
||||
desc 'Get details of specific deposit.' do
|
||||
success API::V2::Entities::Deposit
|
||||
end
|
||||
params do
|
||||
requires :txid,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: "Deposit transaction id"
|
||||
end
|
||||
get "/deposits/:txid" do
|
||||
user_authorize! :read, ::Deposit
|
||||
|
||||
deposit = current_user.deposits.find_by!(txid: params[:txid])
|
||||
present deposit, with: API::V2::Entities::Deposit
|
||||
end
|
||||
|
||||
desc 'Returns deposit address for account you want to deposit to by currency. ' \
|
||||
'The address may be blank because address generation process is still in progress. ' \
|
||||
'If this case you should try again later.',
|
||||
success: API::V2::Entities::Deposit
|
||||
params do
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.coins.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist'},
|
||||
desc: 'The account you want to deposit to.'
|
||||
given :currency do
|
||||
optional :address_format,
|
||||
type: String,
|
||||
values: { value: -> { %w[legacy cash] }, message: 'account.deposit_address.invalid_address_format' },
|
||||
validate_currency_address_format: { value: true, prefix: 'account.deposit_address' },
|
||||
desc: 'Address format legacy/cash'
|
||||
end
|
||||
end
|
||||
get '/deposit_address/:currency', requirements: { currency: /[\w\.\-]+/ } do
|
||||
user_authorize! :read, ::PaymentAddress
|
||||
|
||||
currency = Currency.find(params[:currency])
|
||||
|
||||
unless currency.deposit_enabled?
|
||||
error!({ errors: ['account.currency.deposit_disabled'] }, 422)
|
||||
end
|
||||
|
||||
wallet = Wallet.deposit_wallet(currency.id)
|
||||
|
||||
unless wallet.present?
|
||||
error!({ errors: ['account.wallet.not_found'] }, 422)
|
||||
end
|
||||
|
||||
payment_address = current_user.payment_address(wallet.id)
|
||||
present payment_address, with: API::V2::Entities::PaymentAddress, address_format: params[:address_format]
|
||||
end
|
||||
############
|
||||
|
||||
desc 'Create fiat deposits'
|
||||
params do
|
||||
requires :amount,
|
||||
type: { value: BigDecimal, message: 'deposit.fiat.non_decimal_amount' },
|
||||
values: { value: ->(v) { v.try(:positive?) }, message: 'deposit.fiat.non_positive_amount' },
|
||||
desc: 'The amount to deposit fiat.'
|
||||
requires :callback_url,
|
||||
regexp: { value: URI::regexp, message: 'callback_url.invalid' },
|
||||
desc: -> { 'callback_url payment url' }
|
||||
requires :currency,
|
||||
type: String,
|
||||
default: 'irt',
|
||||
desc: -> { 'currency' }
|
||||
requires :card,
|
||||
type: String,
|
||||
desc: 'card bank number'
|
||||
optional :factorNumber,
|
||||
type: String,
|
||||
allow_blank: true,
|
||||
desc: 'factor number'
|
||||
optional :description,
|
||||
type: String,
|
||||
allow_blank: true,
|
||||
desc: 'description'
|
||||
|
||||
end
|
||||
post '/deposits/fiat' do
|
||||
user_authorize! :create, ::Deposit
|
||||
error!({ errors: ['account.deposit.use_valid_card'] }, 403) if current_user.cards.exclude?(params[:card].to_s)
|
||||
|
||||
deposit_data = { member: current_user, currency: Currency.find(params['currency']), aasm_state: 'canceled',
|
||||
address: params['card'], amount: params['amount'], transfer_type: :fiat }
|
||||
deposit = ::Deposits::Fiat.new(deposit_data)
|
||||
|
||||
error!({ errors: deposit.errors.full_messages }, 422) if deposit.errors.any?
|
||||
|
||||
data = { amount: (params[:amount] * 10), callback_url: params[:callback_url],
|
||||
description: params[:description], valid_card_number: params[:card] }
|
||||
response = VandarService::generate_token(data)
|
||||
if response.dig('status').to_i.positive?
|
||||
deposit.tap { |d| d.tid = response.dig('token') }.save!
|
||||
present(response: response.dig('token'))
|
||||
else
|
||||
present(response: response)
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Confirm fiat deposits'
|
||||
params do
|
||||
requires :token,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'payment token'
|
||||
end
|
||||
post '/deposits/confirm' do
|
||||
deposit = ::Deposits::Fiat.find_by(tid: params[:token])
|
||||
error!({ errors: ['corresponding.deposit.record.not.found'] }, 403) unless deposit.present?
|
||||
|
||||
# because vandar just response one
|
||||
if deposit.txid.present?
|
||||
db_response = {
|
||||
status: 1,
|
||||
amount: deposit.amount,
|
||||
wage: deposit.fee,
|
||||
cardNumber: deposit.address,
|
||||
transId: deposit.txid
|
||||
}
|
||||
return present(response: db_response)
|
||||
end
|
||||
|
||||
# first step: check everything is ok
|
||||
response = ::VandarService.transaction(params[:token])
|
||||
if response.dig('status').to_i.positive? && response.dig('trackingCode').present?
|
||||
if response['cardNumber'].last(4).to_i == deposit.address.last(4).to_i
|
||||
response = VandarService.verify(params[:token])
|
||||
if response.dig('status').to_i.positive?
|
||||
deposit.update(txid: response['transId'], fee: response['wage'].to_f / 10, aasm_state: 'submitted')
|
||||
deposit.charge!
|
||||
end
|
||||
end
|
||||
# TODO we should see the response and check it for rial and toman confirmation
|
||||
present(response: response)
|
||||
else
|
||||
present(response: response)
|
||||
status 422
|
||||
end
|
||||
# second step: verify payment
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
90
app/api/v2/account/internal_transfers.rb
Normal file
90
app/api/v2/account/internal_transfers.rb
Normal file
@@ -0,0 +1,90 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class InternalTransfers < Grape::API
|
||||
namespace :internal_transfers do
|
||||
desc 'List your internal transfers as paginated collection.',
|
||||
is_array: true,
|
||||
success: API::V2::Entities::InternalTransfer
|
||||
params do
|
||||
optional :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist' },
|
||||
desc: 'Currency code.'
|
||||
optional :state, type: String, desc: 'The state to filter by.'
|
||||
optional :sender
|
||||
end
|
||||
|
||||
get do
|
||||
user_authorize! :read, ::InternalTransfer
|
||||
|
||||
ransack_params = ::API::V2::Admin::Helpers::RansackBuilder.new(params)
|
||||
.eq(:state)
|
||||
.translate(currency: :currency_id)
|
||||
.merge(g: [
|
||||
{ sender_id_eq: current_user.id, receiver_id_eq: current_user.id, m: 'or' }
|
||||
]).build
|
||||
search = InternalTransfer.ransack(ransack_params)
|
||||
.result
|
||||
.order('id desc')
|
||||
|
||||
present paginate(search), with: API::V2::Entities::InternalTransfer, current_user: current_user
|
||||
end
|
||||
desc 'Creates internal transfer.'
|
||||
params do
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist' },
|
||||
desc: 'The currency code.'
|
||||
requires :amount,
|
||||
type: { value: BigDecimal, message: 'account.internal_transfer.non_decimal_amount' },
|
||||
values: { value: ->(v) { v.try(:positive?) }, message: 'account.internal_transfer.non_positive_amount' },
|
||||
desc: 'The amount to transfer.'
|
||||
requires :otp,
|
||||
type: { value: Integer, message: 'account.internal_transfer.non_integer_otp' },
|
||||
allow_blank: false,
|
||||
desc: 'OTP to perform action'
|
||||
requires :username_or_uid,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Receiver uid or username.'
|
||||
end
|
||||
post do
|
||||
receiver = Member.find_by_username_or_uid(params[:username_or_uid])
|
||||
|
||||
error!({ errors: ['account.internal_transfer.receiver_not_found'] }, 422) if receiver.nil?
|
||||
currency = Currency.find(params[:currency])
|
||||
|
||||
unless Vault::TOTP.validate?(current_user.uid, params[:otp])
|
||||
error!({ errors: ['account.internal_transfer.invalid_otp'] }, 422)
|
||||
end
|
||||
|
||||
if current_user.get_account(currency).balance < params[:amount]
|
||||
error!({ errors: ['account.internal_transfer.insufficient_balance'] }, 422)
|
||||
end
|
||||
|
||||
if current_user == receiver
|
||||
error!({ errors: ['account.internal_transfer.can_not_tranfer_to_yourself'] }, 422)
|
||||
end
|
||||
|
||||
internal_transfer = ::InternalTransfer.new(
|
||||
currency: currency,
|
||||
sender: current_user,
|
||||
receiver: receiver,
|
||||
amount: params[:amount]
|
||||
)
|
||||
if internal_transfer.save
|
||||
present internal_transfer, with: API::V2::Entities::InternalTransfer
|
||||
status 201
|
||||
else
|
||||
body errors: internal_transfer.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
28
app/api/v2/account/levels.rb
Normal file
28
app/api/v2/account/levels.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Levels < Grape::API
|
||||
|
||||
desc 'Get current level and next level informations',
|
||||
is_array: true
|
||||
get '/levels' do
|
||||
current_level = current_user.group
|
||||
next_level = current_user.next_group
|
||||
current_level_min_max = Member.min_max_group(current_level)
|
||||
next_level_min_max = Member.min_max_group(next_level)
|
||||
|
||||
current_level_fee = TradingFee.for(group: current_level, market_id: 'ANY')
|
||||
next_level_fee = TradingFee.for(group: next_level, market_id: 'ANY')
|
||||
|
||||
{ 'current': { 'name': current_level, 'max': current_level_min_max[1], 'min': current_level_min_max[0],
|
||||
'maker_fee': current_level_fee.maker, 'taker_fee': current_level_fee.taker },
|
||||
'next': { 'name': next_level, 'max': next_level_min_max[1], 'min': next_level_min_max[0],
|
||||
'maker_fee': next_level_fee.maker, 'taker_fee': next_level_fee.taker }}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
21
app/api/v2/account/mount.rb
Normal file
21
app/api/v2/account/mount.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
module API::V2
|
||||
module Account
|
||||
class Mount < Grape::API
|
||||
|
||||
before { authenticate! }
|
||||
before { set_ets_context! }
|
||||
|
||||
mount Account::Balances
|
||||
mount Account::Deposits
|
||||
mount Account::Beneficiaries
|
||||
mount Account::Withdraws
|
||||
mount Account::Transactions
|
||||
mount Account::Stats
|
||||
mount Account::InternalTransfers
|
||||
mount Account::Otp
|
||||
mount Account::Treasury
|
||||
mount Account::Levels
|
||||
mount Account::Bonuses
|
||||
end
|
||||
end
|
||||
end
|
||||
78
app/api/v2/account/otp.rb
Normal file
78
app/api/v2/account/otp.rb
Normal file
@@ -0,0 +1,78 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Otp < Grape::API
|
||||
helpers API::V2::Account::Utils
|
||||
resource :otp do
|
||||
# for example
|
||||
# for withdraw with id 3
|
||||
# we create a cache with name -> withdraw_3_2fa
|
||||
#
|
||||
# # for example
|
||||
# # for deposit with id 6
|
||||
# # we create a cache with name -> deposit_6_2fa
|
||||
desc 'validate user otp'
|
||||
params do
|
||||
requires :otp,
|
||||
type: { value: Integer, message: 'integer_otp' },
|
||||
allow_blank: false,
|
||||
desc: 'OTP to perform action'
|
||||
optional :action,
|
||||
type: String,
|
||||
desc: 'action for top'
|
||||
# optional :id,
|
||||
# type: String,
|
||||
# desc: 'if for top action'
|
||||
end
|
||||
post do
|
||||
error!({ errors: ['account.not_active_otp'] }, 422) unless current_user.otp
|
||||
|
||||
error!({ errors: ['account.invalid_otp'] }, 422) unless Vault::TOTP.validate?(current_user.uid, params[:otp])
|
||||
|
||||
action = 'validate'
|
||||
# id = '0'
|
||||
action = params[:action] if params[:action].present?
|
||||
# id = params[:id] if params[:id].present?
|
||||
current_user.write_cache("#{action}_2fa", 1, 60)
|
||||
status 200
|
||||
end
|
||||
|
||||
desc 'Send confirmations code',
|
||||
success: { code: 201, message: 'Generated verification code' },
|
||||
failure: [
|
||||
{ code: 400, message: 'Required params are missing' },
|
||||
{ code: 422, message: 'Validation errors' }
|
||||
]
|
||||
params do
|
||||
requires :data,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Account email or Telephone number'
|
||||
requires :action,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'for what need auth code'
|
||||
optional :channel,
|
||||
type: String,
|
||||
allow_blank: true,
|
||||
default: 'email',
|
||||
desc: 'channel that send in'
|
||||
optional :captcha_response,
|
||||
types: [String, Hash],
|
||||
desc: 'Response from captcha widget'
|
||||
end
|
||||
post '/resend' do
|
||||
current_user = Member.find_by(email: params[:data])
|
||||
return status 201 if current_user.nil?
|
||||
|
||||
publish_confirmation_code(current_user, params[:action])
|
||||
status 201
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
36
app/api/v2/account/stats.rb
Normal file
36
app/api/v2/account/stats.rb
Normal file
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Stats < Grape::API
|
||||
desc 'Get assets pnl calculated into one currency'
|
||||
params do
|
||||
optional :pnl_currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'pnl.currency.doesnt_exist' },
|
||||
desc: 'Currency code in which the PnL is calculated'
|
||||
end
|
||||
get '/stats/pnl' do
|
||||
user_authorize! :read, ::StatsMemberPnl
|
||||
|
||||
query = 'SELECT pnl_currency_id, currency_id, total_credit, total_debit, total_credit_value, total_debit_value, ' \
|
||||
'total_credit_value / NULLIF(total_credit, 0) "average_buy_price", ' \
|
||||
'total_debit_value / NULLIF(total_debit, 0) "average_sell_price", ' \
|
||||
'average_balance_price, total_balance_value ' \
|
||||
'FROM stats_member_pnl WHERE member_id = ?'
|
||||
conditions = [current_user.id]
|
||||
|
||||
if params[:pnl_currency].present?
|
||||
query += ' AND pnl_currency_id = ?'
|
||||
conditions << params[:pnl_currency]
|
||||
end
|
||||
|
||||
squery = ActiveRecord::Base.sanitize_sql_for_conditions([query] + conditions)
|
||||
result = ActiveRecord::Base.connection.exec_query(squery).to_hash
|
||||
present result.each(&:symbolize_keys!), with: API::V2::Entities::Pnl
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
109
app/api/v2/account/transactions.rb
Normal file
109
app/api/v2/account/transactions.rb
Normal file
@@ -0,0 +1,109 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../validations'
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Transactions < Grape::API
|
||||
|
||||
before { deposits_must_be_permitted! }
|
||||
before { withdraws_must_be_permitted! }
|
||||
|
||||
desc 'Get your transactions history.',
|
||||
is_array: true
|
||||
|
||||
params do
|
||||
optional :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.transactions.currency_doesnt_exist' },
|
||||
desc: 'Currency code'
|
||||
|
||||
optional :order_by,
|
||||
type: String,
|
||||
values: { value: %w(asc desc), message: 'account.transactions.order_by_invalid' },
|
||||
default: 'desc',
|
||||
desc: 'Sorting order'
|
||||
|
||||
optional :time_from,
|
||||
allow_blank: { value: false, message: 'account.transactions.empty_time_from' },
|
||||
type: { value: Integer, message: 'account.transactions.non_integer_time_from' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'
|
||||
|
||||
optional :time_to,
|
||||
type: { value: Integer, message: 'account.transactions.non_integer_time_to' },
|
||||
allow_blank: { value: false, message: 'account.transactions.empty_time_to' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'
|
||||
|
||||
optional :deposit_state,
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Deposit.aasm.states.map(&:name).map(&:to_s)).blank? }, message: 'account.transactions.invalid_deposit_state' },
|
||||
desc: 'Filter deposits by states.',
|
||||
default: []
|
||||
|
||||
optional :withdraw_state,
|
||||
values: { value: ->(v) { (Array.wrap(v) - Withdraw::STATES.map(&:to_s)).blank? }, message: 'account.transactions.invalid_withdraw_state' },
|
||||
desc: 'Filter withdraws by states.',
|
||||
default: []
|
||||
|
||||
optional :txid,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Transaction id.'
|
||||
|
||||
optional :limit,
|
||||
type: { value: Integer, message: 'account.transactions.non_integer_limit' },
|
||||
values: { value: 1..1000, message: 'account.transactions.invalid_limit' },
|
||||
default: 100,
|
||||
desc: 'Limit the number of returned transactions. Default to 100.'
|
||||
|
||||
optional :page,
|
||||
type: { value: Integer, message: 'account.transactions.non_integer_page' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'account.transactions.non_positive_page'},
|
||||
allow_blank: false,
|
||||
default: 1,
|
||||
desc: 'Specify the page of paginated results.'
|
||||
|
||||
end
|
||||
get "/transactions" do
|
||||
user_authorize! :read, ::Withdraw
|
||||
user_authorize! :read, ::Deposit
|
||||
|
||||
deposit_state = params[:deposit_state]&.split(/\W+/)&.join(',')
|
||||
withdraw_state = params[:withdraw_state]&.split(/\W+/)&.join(',')
|
||||
|
||||
deposit_sql = "(SELECT d.id, currency_id, amount, fee, address, aasm_state, NULL AS note, txid, d.created_at, d.updated_at, d.type, b.height - block_number AS confirmations FROM deposits d " \
|
||||
"INNER JOIN currencies c ON c.id=d.currency_id LEFT JOIN blockchains b ON b.key=c.blockchain_key WHERE member_id=#{current_user.id} "
|
||||
if params[:deposit_state].present?
|
||||
deposit_sql += if Rails.configuration.database_adapter.downcase == 'PostgreSQL'.downcase
|
||||
"and aasm_state = any(string_to_array('#{deposit_state}',','))"
|
||||
else
|
||||
"and FIND_IN_SET(aasm_state, '#{deposit_state}')"
|
||||
end
|
||||
end
|
||||
|
||||
withdraw_sql = "SELECT w.id, currency_id, amount, fee, rid, aasm_state, note, txid, w.created_at, w.updated_at, w.type, b.height - block_number AS confirmations FROM withdraws w " \
|
||||
"INNER JOIN currencies c ON c.id=w.currency_id LEFT JOIN blockchains b ON b.key=c.blockchain_key WHERE member_id=#{current_user.id} "
|
||||
if params[:withdraw_state].present?
|
||||
withdraw_sql += if Rails.configuration.database_adapter.downcase == 'PostgreSQL'.downcase
|
||||
"and aasm_state = any(string_to_array('#{withdraw_state}',','))"
|
||||
else
|
||||
"and FIND_IN_SET(aasm_state, '#{withdraw_state}')"
|
||||
end
|
||||
end
|
||||
|
||||
sql = "SELECT * FROM " + deposit_sql + "UNION " + withdraw_sql + ") AS transactions ORDER BY updated_at #{params[:order_by].upcase}"
|
||||
|
||||
result = ActiveRecord::Base.connection.exec_query(sql).to_hash
|
||||
|
||||
result.select! { |t| t['currency_id'] == params[:currency].downcase } if params[:currency].present?
|
||||
result.select! { |t| t['txid'] == params[:txid] } if params[:txid].present?
|
||||
result.select! { |t| t['updated_at'] >= Time.at(params[:time_from]) } if params[:time_from].present?
|
||||
result.select! { |t| t['updated_at'] <= Time.at(params[:time_to]) } if params[:time_to].present?
|
||||
|
||||
present paginate(result.each(&:symbolize_keys!)), with: API::V2::Entities::Transactions
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
43
app/api/v2/account/treasury.rb
Normal file
43
app/api/v2/account/treasury.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Treasury < Grape::API
|
||||
|
||||
helpers ::API::V2::ParamHelpers
|
||||
|
||||
desc 'Get list of user cards',
|
||||
is_array: true
|
||||
params do
|
||||
# use :pagination
|
||||
optional :uid,
|
||||
type: String,
|
||||
default: false,
|
||||
desc: 'UID of user'
|
||||
end
|
||||
get '/treasury/cards' do
|
||||
user_authorize! :read, ::Operations::Account
|
||||
|
||||
present current_user.cards
|
||||
end
|
||||
|
||||
desc 'Get list of user ibans',
|
||||
is_array: true
|
||||
params do
|
||||
# use :pagination
|
||||
optional :uid,
|
||||
type: String,
|
||||
default: false,
|
||||
desc: 'UID of user'
|
||||
end
|
||||
get '/treasury/ibans' do
|
||||
user_authorize! :read, ::Operations::Account
|
||||
|
||||
present current_user.ibans
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
19
app/api/v2/account/utils.rb
Normal file
19
app/api/v2/account/utils.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
module Utils
|
||||
def session
|
||||
request.session
|
||||
end
|
||||
|
||||
def publish_confirmation_code(user, action)
|
||||
totp = ::Vault::TOTPAction.new(action)
|
||||
totp.create(user.uid, user.email)
|
||||
::EventAPI.notify(action, record: { user: user.as_json_for_event_api,
|
||||
domain: Peatio::App.config.domain,
|
||||
code: totp.read_code(user.uid) })
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
245
app/api/v2/account/withdraws.rb
Normal file
245
app/api/v2/account/withdraws.rb
Normal file
@@ -0,0 +1,245 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Account
|
||||
class Withdraws < Grape::API
|
||||
|
||||
before { withdraws_must_be_permitted! }
|
||||
helpers API::V2::Account::Utils
|
||||
|
||||
desc 'List your withdraws as paginated collection.',
|
||||
is_array: true,
|
||||
success: API::V2::Entities::Withdraw
|
||||
params do
|
||||
optional :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist'},
|
||||
desc: 'Currency code.'
|
||||
optional :limit,
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_limit' },
|
||||
values: { value: 1..100, message: 'account.withdraw.invalid_limit' },
|
||||
default: 100,
|
||||
desc: "Number of withdraws per page (defaults to 100, maximum is 100)."
|
||||
optional :state,
|
||||
values: { value: ->(v) { (Array.wrap(v) - Withdraw::STATES.map(&:to_s)).blank? }, message: 'account.withdraw.invalid_state' },
|
||||
desc: 'Filter withdrawals by states.'
|
||||
optional :rid,
|
||||
type: String,
|
||||
allow_blank: false,
|
||||
desc: 'Wallet address on the Blockchain.'
|
||||
optional :time_from,
|
||||
allow_blank: { value: false, message: 'account.withdraw.empty_time_from' },
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_time_from' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'
|
||||
optional :time_to,
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_time_to' },
|
||||
allow_blank: { value: false, message: 'account.withdraw.empty_time_to' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'
|
||||
optional :page,
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_page' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'account.withdraw.non_positive_page'},
|
||||
default: 1,
|
||||
desc: 'Page number (defaults to 1).'
|
||||
end
|
||||
get '/withdraws' do
|
||||
user_authorize! :read, ::Withdraw
|
||||
|
||||
currency = Currency.find(params[:currency]) if params[:currency].present?
|
||||
|
||||
current_user.withdraws.order(id: :desc)
|
||||
.tap { |q| q.where!(currency: currency) if currency }
|
||||
.tap { |q| q.where!(aasm_state: params[:state]) if params[:state] }
|
||||
.tap { |q| q.where!(rid: params[:rid]) if params[:rid] }
|
||||
.tap { |q| q.where!('updated_at >= ?', Time.at(params[:time_from])) if params[:time_from].present? }
|
||||
.tap { |q| q.where!('updated_at <= ?', Time.at(params[:time_to])) if params[:time_to].present? }
|
||||
.tap { |q| present paginate(q), with: API::V2::Entities::Withdraw }
|
||||
end
|
||||
|
||||
desc 'Returns withdrawal sums for last 4 hours and 1 month'
|
||||
get '/withdraws/sums' do
|
||||
user_authorize! :read, ::Withdraw
|
||||
|
||||
sum_24_hours, sum_1_month = Withdraw.sanitize_execute_sum_queries(current_user.id)
|
||||
|
||||
present({ last_24_hours: sum_24_hours, last_1_month: sum_1_month })
|
||||
end
|
||||
|
||||
desc 'Creates new withdrawal to active beneficiary.'
|
||||
params do
|
||||
requires :otp,
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_otp' },
|
||||
allow_blank: false,
|
||||
desc: 'OTP to perform action'
|
||||
requires :beneficiary_id,
|
||||
type: { value: Integer, message: 'account.withdraw.non_integer_beneficiary_id' },
|
||||
allow_blank: false,
|
||||
desc: 'ID of Active Beneficiary belonging to user.'
|
||||
requires :currency,
|
||||
type: String,
|
||||
values: { value: -> { Currency.visible.codes(bothcase: true) }, message: 'account.currency.doesnt_exist'},
|
||||
desc: 'The currency code.'
|
||||
requires :amount,
|
||||
type: { value: BigDecimal, message: 'account.withdraw.non_decimal_amount' },
|
||||
values: { value: ->(v) { v.try(:positive?) }, message: 'account.withdraw.non_positive_amount' },
|
||||
desc: 'The amount to withdraw.'
|
||||
optional :note,
|
||||
type: String,
|
||||
values: { value: ->(v) { v.size <= 256 }, message: 'account.withdraw.too_long_note' },
|
||||
desc: 'Optional user metadata to be applied to the transaction. Used to tag transactions with memorable comments.'
|
||||
end
|
||||
post '/withdraws' do
|
||||
user_authorize! :create, ::Withdraw
|
||||
|
||||
withdraw_api_must_be_enabled!
|
||||
|
||||
if current_user.otp.present? && current_user.read_cache('withdraw_2fa').blank?
|
||||
error!({ errors: ['account.withdraw.need.2fa'] }, 403)
|
||||
end
|
||||
|
||||
totp = Vault::TOTPAction.new('withdraw-coin')
|
||||
error!({ errors: ['withdraw.totp.code'] }, 422) unless totp.validate?(current_user.uid, params[:otp])
|
||||
|
||||
beneficiary = current_user
|
||||
.beneficiaries
|
||||
.available_to_member
|
||||
.find_by(id: params[:beneficiary_id])
|
||||
|
||||
if beneficiary.blank?
|
||||
error!({ errors: ['account.beneficiary.doesnt_exist'] }, 422)
|
||||
elsif !beneficiary.active?
|
||||
error!({ errors: ['account.beneficiary.invalid_state_for_withdrawal'] }, 422)
|
||||
end
|
||||
|
||||
currency = Currency.find(params[:currency])
|
||||
error!({ errors: ['account.currency.withdrawal_disabled'] }, 422) unless currency.withdrawal_enabled?
|
||||
|
||||
# TODO: Delete subclasses from Deposit and Withdraw
|
||||
withdraw = "withdraws/#{currency.type}".camelize.constantize.new \
|
||||
beneficiary: beneficiary,
|
||||
sum: params[:amount],
|
||||
member: current_user,
|
||||
currency: currency,
|
||||
note: params[:note]
|
||||
withdraw.save!
|
||||
withdraw.with_lock { withdraw.accept! }
|
||||
current_user.delete_cache('withdraw_2fa')
|
||||
present withdraw, with: API::V2::Entities::Withdraw
|
||||
|
||||
rescue ::Account::AccountError => e
|
||||
report_api_error(e, request)
|
||||
error!({ errors: ['account.withdraw.insufficient_balance'] }, 422)
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
report_api_error(e, request)
|
||||
# TODO: Check if there are other errors possible here.
|
||||
# For now single error which is not handled by params validations is
|
||||
# sum precision validation error (PrecisionValidator).
|
||||
error!({ errors: ['account.withdraw.invalid_amount'] }, 422)
|
||||
rescue => e
|
||||
report_exception(e)
|
||||
error!({ errors: ['account.withdraw.create_error'] }, 422)
|
||||
end
|
||||
|
||||
desc 'Creates new fiat withdrawal'
|
||||
params do
|
||||
requires :amount,
|
||||
type: { value: BigDecimal, message: 'account.withdraw.non_decimal_amount' },
|
||||
values: { value: ->(v) { v.try(:positive?) }, message: 'account.withdraw.non_positive_amount' },
|
||||
desc: 'The amount to withdraw.'
|
||||
requires :iban,
|
||||
type: String,
|
||||
desc: 'iban bank number'
|
||||
requires :currency,
|
||||
type: String,
|
||||
default: 'irt',
|
||||
desc: -> { 'currency' }
|
||||
optional :note,
|
||||
type: String,
|
||||
values: { value: ->(v) { v.size <= 256 }, message: 'account.withdraw.too_long_note' },
|
||||
desc: 'Optional user metadata to be applied to the transaction. Used to tag transactions with memorable comments.'
|
||||
end
|
||||
post '/withdraws/fiat' do
|
||||
user_authorize! :create, ::Withdraw
|
||||
withdraw_api_must_be_enabled!
|
||||
|
||||
if current_user.otp.present? && current_user.read_cache('withdraw_2fa').blank?
|
||||
error!({ errors: ['account.withdraw.need.2fa'] }, 403)
|
||||
end
|
||||
|
||||
error!({ errors: ['account.withdraw.use_valid_iban'] }, 403) if current_user.ibans.exclude?(params[:iban])
|
||||
|
||||
currency = Currency.find(params[:currency])
|
||||
|
||||
withdraw = "withdraws/#{currency.type}".camelize.constantize.new \
|
||||
rid: params[:iban],
|
||||
sum: params[:amount],
|
||||
member: current_user,
|
||||
currency: currency,
|
||||
transfer_type: :fiat,
|
||||
note: params[:note]
|
||||
|
||||
withdraw.save
|
||||
error!({ errors: withdraw.errors.full_messages }, 422) unless withdraw.valid?
|
||||
# delete 2fa cache
|
||||
current_user.delete_cache('withdraw_2fa')
|
||||
publish_confirmation_code(current_user, 'withdraw-fiat')
|
||||
present withdraw, with: API::V2::Entities::Withdraw
|
||||
end
|
||||
#
|
||||
#
|
||||
desc 'confirm fiat withdrawal'
|
||||
params do
|
||||
requires :id,
|
||||
type: Integer,
|
||||
desc: 'withdraw id'
|
||||
requires :otp,
|
||||
type: Integer,
|
||||
desc: 'auth otp in sms or email'
|
||||
end
|
||||
post '/withdraws/confirm' do
|
||||
user_authorize! :create, ::Withdraw
|
||||
withdraw = current_user.withdraws.find_by(id: params[:id])
|
||||
error!({ errors: ['account.withdraw.not.found'] }, 403) unless withdraw.present?
|
||||
|
||||
error!({ errors: ['account.withdraw.process_before'] }, 403) unless withdraw.aasm_state == 'prepared'
|
||||
|
||||
totp = Vault::TOTPAction.new('withdraw-fiat')
|
||||
error!({ errors: ['withdraw.totp.code'] }, 422) unless totp.validate?(current_user.uid, params[:otp])
|
||||
|
||||
# here must lock found
|
||||
withdraw.with_lock { withdraw.accept! }
|
||||
response = ::VandarService.new.create_withdraw(amount: withdraw.sum, iban: withdraw.rid)
|
||||
if response.dig('status').to_i.positive?
|
||||
withdraw.update(txid: response['data'].dig('settlement', 0, 'transaction_id'), aasm_state: 'confirming')
|
||||
else
|
||||
withdraw.with_lock { withdraw.reject! }
|
||||
end
|
||||
|
||||
present(response: response)
|
||||
end
|
||||
|
||||
#
|
||||
#
|
||||
desc 'Get fiat withdrawal'
|
||||
params do
|
||||
requires :id,
|
||||
type: Integer,
|
||||
desc: 'withdraw id'
|
||||
optional :with_otp,
|
||||
type: String,
|
||||
desc: -> { 'send otp or not' }
|
||||
end
|
||||
get '/withdraw' do
|
||||
user_authorize! :read, ::Withdraw
|
||||
|
||||
withdraw = current_user.withdraws.find_by(id: params[:id])
|
||||
if withdraw.present? && withdraw.accepted? && params['with_otp'].present?
|
||||
publish_confirmation_code(current_user, 'withdraw')
|
||||
end
|
||||
present withdraw, with: API::V2::Entities::Withdraw
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
17
app/api/v2/admin/abilities.rb
Normal file
17
app/api/v2/admin/abilities.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Abilities < Grape::API
|
||||
namespace :abilities do
|
||||
desc 'Get all roles and permissions.'
|
||||
get do
|
||||
Ability.admin_permissions[current_user.role] || {}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
155
app/api/v2/admin/adjustments.rb
Normal file
155
app/api/v2/admin/adjustments.rb
Normal file
@@ -0,0 +1,155 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Adjustments < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
namespace :adjustments do
|
||||
desc 'Get all adjustments, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Adjustment
|
||||
params do
|
||||
use :currency
|
||||
use :date_picker
|
||||
use :pagination
|
||||
use :ordering
|
||||
optional :state,
|
||||
type: String,
|
||||
values: { value: -> { Adjustment.aasm.states.map(&:name).map(&:to_s) }, message: 'admin.adjustment.invalid_action' },
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:state][:desc] }
|
||||
optional :category,
|
||||
type: String,
|
||||
values: { value: -> { ::Adjustment::CATEGORIES }, message: 'admin.adjustment.invalid_category' },
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:category][:desc] }
|
||||
end
|
||||
get do
|
||||
admin_authorize! :read, Adjustment
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:state, :category)
|
||||
.translate(currency: :currency_id)
|
||||
.with_daterange
|
||||
.build
|
||||
|
||||
search = Adjustment.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::Adjustment
|
||||
end
|
||||
|
||||
desc 'Get adjustment by ID',
|
||||
success: API::V2::Admin::Entities::Adjustment
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'account.adjustment.non_integer_id' },
|
||||
desc: 'Adjsustment Identifier in Database'
|
||||
end
|
||||
get ':id' do
|
||||
admin_authorize! :read, Adjustment
|
||||
|
||||
present ::Adjustment.find(params[:id]), with: API::V2::Admin::Entities::Adjustment
|
||||
end
|
||||
|
||||
desc 'Create new adjustment.',
|
||||
success: API::V2::Admin::Entities::Adjustment
|
||||
params do
|
||||
requires :reason,
|
||||
type: String,
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:reason][:desc] }
|
||||
requires :description,
|
||||
type: String,
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:description][:desc] }
|
||||
requires :category,
|
||||
type: String,
|
||||
values: { value: -> { ::Adjustment::CATEGORIES }, message: 'admin.adjustment.invalid_category' },
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:category][:desc] }
|
||||
requires :amount,
|
||||
type: { value: BigDecimal, message: 'admin.adjustment.non_decimal_amount' },
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:amount][:desc] }
|
||||
requires :currency_id,
|
||||
type: String,
|
||||
values: { value: -> { ::Currency.codes }, message: 'admin.adjustment.currency_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:currency][:desc] }
|
||||
requires :asset_account_code,
|
||||
type: { value: Integer, message: 'admin.adjustment.non_integer_asset_account_code' },
|
||||
values: { value: -> { ::Operations::Account.where(type: :asset).pluck(:code) }, message: 'admin.adjustment.invalid_asset_account_code' },
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:asset_account_code][:desc] }
|
||||
requires :receiving_account_code,
|
||||
type: { value: Integer, message: 'admin.adjustment.non_integer_receiving_account_code' },
|
||||
values: { value: -> { ::Operations::Account.where.not(type: :asset).pluck(:code) }, message: 'admin.adjustment.invalid_receiving_account_code' },
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:receiving_account_code][:desc] }
|
||||
optional :receiving_member_uid,
|
||||
type: String,
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:receiving_account_code][:desc] }
|
||||
end
|
||||
post '/new' do
|
||||
admin_authorize! :create, Adjustment
|
||||
|
||||
# Do not accept member_uid if account code is not Liability or Revenue
|
||||
# Raise error if there is no :receiving_member_uid for Liability
|
||||
operation_klass = ::Operations.klass_for(code: params[:receiving_account_code])
|
||||
if operation_klass == ::Operations::Liability && params[:receiving_member_uid].blank?
|
||||
error!({ errors: ['admin.adjustment.missing_receiving_member_uid'] }, 422)
|
||||
elsif operation_klass == ::Operations::Expense && params[:receiving_member_uid].present?
|
||||
error!({ errors: ['admin.adjustment.redundant_receiving_member_uid'] }, 422)
|
||||
end
|
||||
|
||||
receiving = ::Operations.build_account_number(currency_id: params[:currency_id],
|
||||
account_code: params[:receiving_account_code],
|
||||
member_uid: params[:receiving_member_uid])
|
||||
|
||||
adjustment = Adjustment.new(declared(params)
|
||||
.except(:receiving_account_code, :receiving_member_uid)
|
||||
.merge(receiving_account_number: receiving,
|
||||
creator: current_user))
|
||||
if adjustment.save
|
||||
present adjustment, with: API::V2::Admin::Entities::Adjustment
|
||||
status 201
|
||||
else
|
||||
body errors: adjustment.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Accepts adjustment and creates operations or reject adjustment.',
|
||||
success: API::V2::Admin::Entities::Adjustment
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.adjustment.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Adjustment.documentation[:id][:desc] }
|
||||
requires :action,
|
||||
type: String,
|
||||
values: { value: -> { Adjustment.aasm.events.map(&:name).map(&:to_s) }, message: 'admin.adjustment.invalid_action' },
|
||||
desc: "Adjustment action all available actions: #{Adjustment.aasm.events.map(&:name)}"
|
||||
end
|
||||
post '/action' do
|
||||
admin_authorize! :update, Adjustment
|
||||
adjustment = Adjustment.find(params[:id])
|
||||
|
||||
if adjustment.amount < 0
|
||||
account_number_hash = ::Operations.split_account_number(account_number: adjustment.receiving_account_number)
|
||||
member = Member.find_by(uid: account_number_hash[:member_uid])
|
||||
balance = member.get_account(account_number_hash[:currency_id]).balance
|
||||
|
||||
if adjustment.amount.abs() > balance
|
||||
error!({ errors: ['admin.adjustment.user_insufficient_balance'] }, 422)
|
||||
end
|
||||
end
|
||||
|
||||
if adjustment.public_send("may_#{params[:action]}?")
|
||||
# TODO: Add behaviour in case of errors on action.
|
||||
adjustment.public_send("#{params[:action]}!", validator: current_user)
|
||||
present adjustment, with: API::V2::Admin::Entities::Adjustment
|
||||
else
|
||||
body errors: ["admin.adjustment.cannot_perform_#{params[:action]}_action"]
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
23
app/api/v2/admin/airdrops.rb
Normal file
23
app/api/v2/admin/airdrops.rb
Normal file
@@ -0,0 +1,23 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'peatio/airdrop'
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Airdrops < Grape::API
|
||||
|
||||
desc 'Process user airdrop'
|
||||
params do
|
||||
requires :file,
|
||||
type: File
|
||||
end
|
||||
post '/airdrops' do
|
||||
Peatio::Airdrop.new.process(current_user, params)
|
||||
present(result: 'Airdrop processing started')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
77
app/api/v2/admin/beneficiaries.rb
Normal file
77
app/api/v2/admin/beneficiaries.rb
Normal file
@@ -0,0 +1,77 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Beneficiaries < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
namespace :beneficiaries do
|
||||
desc 'Get list of beneficiaries',
|
||||
success: API::V2::Admin::Entities::Beneficiary
|
||||
params do
|
||||
use :uid
|
||||
use :ordering
|
||||
use :pagination
|
||||
optional :id,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Entities::Beneficiary.documentation[:id][:desc] }
|
||||
optional :currency,
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Currency.codes).blank? }, message: 'account.currency.doesnt_exist' },
|
||||
desc: 'Beneficiary currency code'
|
||||
optional :state,
|
||||
type: Array[Integer],
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Beneficiary::STATES_MAPPING.values).blank? }, message: 'account.beneficiary.invalid_state' },
|
||||
desc: 'Beneficiary state',
|
||||
coerce_with: lambda { |val|
|
||||
val.map { |s| Beneficiary::STATES_MAPPING[s.to_sym] }
|
||||
}
|
||||
end
|
||||
|
||||
get do
|
||||
admin_authorize! :read, Beneficiary
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:id)
|
||||
.in(:state)
|
||||
.translate_in(currency: :currency_id)
|
||||
.translate(uid: :member_uid)
|
||||
.build
|
||||
|
||||
search = Beneficiary.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::Beneficiary
|
||||
end
|
||||
|
||||
desc 'Take an action on the beneficiary',
|
||||
success: API::V2::Admin::Entities::Beneficiary
|
||||
|
||||
params do
|
||||
requires :id,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Admin::Entities::Beneficiary.documentation[:id][:desc] }
|
||||
requires :action,
|
||||
type: String,
|
||||
values: { value: -> { ::Beneficiary.aasm.events.map(&:name).map(&:to_s) }, message: 'admin.beneficiary.invalid_action' },
|
||||
desc: "Valid actions are #{::Beneficiary.aasm.events.map(&:name)}."
|
||||
end
|
||||
|
||||
post '/actions' do
|
||||
admin_authorize! :update, Beneficiary
|
||||
|
||||
beneficiary = Beneficiary.find(params[:id])
|
||||
|
||||
if beneficiary.public_send("may_#{params[:action]}?")
|
||||
beneficiary.public_send("#{params[:action]}!")
|
||||
present beneficiary, with: API::V2::Admin::Entities::Beneficiary
|
||||
else
|
||||
body errors: ["admin.beneficiary.cannot_#{params[:action]}"]
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
201
app/api/v2/admin/blockchains.rb
Normal file
201
app/api/v2/admin/blockchains.rb
Normal file
@@ -0,0 +1,201 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Blockchains < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
namespace :blockchains do
|
||||
desc 'Get all blockchains, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Blockchain
|
||||
params do
|
||||
optional :key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.blockchain.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:key][:desc] }
|
||||
optional :client,
|
||||
values: { value: -> { ::Blockchain.clients.map(&:to_s) }, message: 'admin.blockchain.blockchain_client_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:client][:desc] }
|
||||
optional :status,
|
||||
values: { value: -> { %w[active disabled] }, message: 'admin.blockchain.blockchain_status_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:status][:desc] }
|
||||
optional :name,
|
||||
values: { value: -> { ::Blockchain.pluck(:name) }, message: 'admin.blockchain.blockchain_name_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:name][:desc] }
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get do
|
||||
admin_authorize! :read, Blockchain
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:key, :client, :status, :name)
|
||||
.build
|
||||
|
||||
search = ::Blockchain.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::Blockchain
|
||||
end
|
||||
|
||||
desc 'Get available blockchain clients.',
|
||||
is_array: true
|
||||
get '/clients' do
|
||||
Blockchain.clients
|
||||
end
|
||||
|
||||
desc 'Get a blockchain.' do
|
||||
success API::V2::Admin::Entities::Blockchain
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:id][:desc] }
|
||||
end
|
||||
get '/:id' do
|
||||
admin_authorize! :read, Blockchain
|
||||
|
||||
present Blockchain.find(params[:id]), with: API::V2::Admin::Entities::Blockchain
|
||||
end
|
||||
|
||||
desc 'Get a latest blockchain block.'
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:id][:desc] }
|
||||
end
|
||||
get '/:id/latest_block' do
|
||||
admin_authorize! :read, Blockchain
|
||||
|
||||
Blockchain.find(params[:id])&.blockchain_api.latest_block_number
|
||||
rescue
|
||||
error!({ errors: ['admin.blockchain.latest_block'] }, 422)
|
||||
end
|
||||
|
||||
desc 'Create new blockchain.' do
|
||||
success API::V2::Admin::Entities::Blockchain
|
||||
end
|
||||
params do
|
||||
requires :key,
|
||||
values: { value: -> (v){ v && v.length < 255 }, message: 'admin.blockchain.key_too_long' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:key][:desc] }
|
||||
requires :name,
|
||||
values: { value: -> (v){ v && v.length < 255 }, message: 'admin.blockchain.name_too_long' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:name][:desc] }
|
||||
requires :client,
|
||||
values: { value: -> { ::Blockchain.clients.map(&:to_s) }, message: 'admin.blockchain.invalid_client' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:client][:desc] }
|
||||
requires :height,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_height' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'admin.blockchain.non_positive_height' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:height][:desc] }
|
||||
optional :explorer_transaction,
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:explorer_transaction][:desc] }
|
||||
optional :explorer_address,
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:explorer_address][:desc] }
|
||||
optional :server,
|
||||
regexp: { value: URI::regexp, message: 'admin.blockchain.invalid_server' },
|
||||
desc: -> { 'Blockchain server url' }
|
||||
optional :status,
|
||||
values: { value: %w(active disabled), message: 'admin.blockchain.invalid_status' },
|
||||
default: 'active',
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:status][:desc] }
|
||||
optional :min_confirmations,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_min_confirmations' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'admin.blockchain.non_positive_min_confirmations' },
|
||||
default: 6,
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:min_confirmations][:desc] }
|
||||
end
|
||||
post '/new' do
|
||||
admin_authorize! :create, Blockchain
|
||||
|
||||
blockchain = Blockchain.new(declared(params))
|
||||
if blockchain.save
|
||||
present blockchain, with: API::V2::Admin::Entities::Blockchain
|
||||
status 201
|
||||
else
|
||||
body errors: blockchain.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update blockchain.' do
|
||||
success API::V2::Admin::Entities::Blockchain
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:id][:desc] }
|
||||
optional :key,
|
||||
type: String,
|
||||
values: { value: -> (v){ v.length < 255 }, message: 'admin.blockchain.key_too_long' },
|
||||
coerce_with: ->(v) { v.strip.downcase },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:key][:desc] }
|
||||
optional :name,
|
||||
values: { value: -> (v){ v.length < 255 }, message: 'admin.blockchain.name_too_long' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:name][:desc] }
|
||||
optional :client,
|
||||
values: { value: -> { ::Blockchain.clients.map(&:to_s) }, message: 'admin.blockchain.invalid_client' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:client][:desc] }
|
||||
optional :server,
|
||||
regexp: { value: URI::regexp, message: 'admin.blockchain.invalid_server' },
|
||||
desc: -> { 'Blockchain server url' }
|
||||
optional :height,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_height' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'admin.blockchain.non_positive_height' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:height][:desc] }
|
||||
optional :explorer_transaction,
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:explorer_transaction][:desc] }
|
||||
optional :explorer_address,
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:explorer_address][:desc] }
|
||||
optional :status,
|
||||
values: { value: %w(active disabled), message: 'admin.blockchain.invalid_status' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:status][:desc] }
|
||||
optional :min_confirmations,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_min_confirmations' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'admin.blockchain.non_positive_min_confirmations' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:min_confirmations][:desc] }
|
||||
end
|
||||
post '/update' do
|
||||
admin_authorize! :update, Blockchain, params.except(:id)
|
||||
|
||||
blockchain = Blockchain.find(params[:id])
|
||||
if blockchain.update(declared(params, include_missing: false))
|
||||
present blockchain, with: API::V2::Admin::Entities::Blockchain
|
||||
else
|
||||
body errors: blockchain.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Process blockchain\'s block.' do
|
||||
success API::V2::Admin::Entities::Blockchain
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Blockchain.documentation[:id][:desc] }
|
||||
requires :block_number,
|
||||
type: { value: Integer, message: 'admin.blockchain.non_integer_block_number' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'admin.blockchain.non_positive_block_number' },
|
||||
desc: -> { 'The id of a particular block on blockchain' }
|
||||
end
|
||||
post '/process_block' do
|
||||
admin_authorize! :update, Blockchain
|
||||
|
||||
blockchain = Blockchain.find(params[:id])
|
||||
begin
|
||||
blockchain.blockchain_api.process_block(params[:block_number])
|
||||
present blockchain, with: API::V2::Admin::Entities::Blockchain
|
||||
status 201
|
||||
rescue StandardError => e
|
||||
Rails.logger.error { "Error: #{e} while processing block #{params[:block_number]} of blockchain id: #{params[:id]}" }
|
||||
error!({ errors: ['admin.blockchain.process_block'] }, 422)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
234
app/api/v2/admin/currencies.rb
Normal file
234
app/api/v2/admin/currencies.rb
Normal file
@@ -0,0 +1,234 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Currencies < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
helpers do
|
||||
# Collection of shared params, used to
|
||||
# generate required/optional Grape params.
|
||||
OPTIONAL_CURRENCY_PARAMS ||= {
|
||||
name: { desc: -> { API::V2::Admin::Entities::Currency.documentation[:name][:desc] } },
|
||||
deposit_fee: {
|
||||
type: { value: BigDecimal, message: 'admin.currency.non_decimal_deposit_fee' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.currency.invalid_deposit_fee' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:deposit_fee][:desc] }
|
||||
},
|
||||
min_deposit_amount: {
|
||||
type: { value: BigDecimal, message: 'admin.currency.min_deposit_amount' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.currency.min_deposit_amount' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:min_deposit_amount][:desc] }
|
||||
},
|
||||
min_collection_amount: {
|
||||
type: { value: BigDecimal, message: 'admin.currency.non_decimal_min_collection_amount' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.currency.invalid_min_collection_amount' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:min_collection_amount][:desc] }
|
||||
},
|
||||
withdraw_fee: {
|
||||
type: { value: BigDecimal, message: 'admin.currency.non_decimal_withdraw_fee' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.currency.ivalid_withdraw_fee' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:withdraw_fee][:desc] }
|
||||
},
|
||||
min_withdraw_amount: {
|
||||
type: { value: BigDecimal, message: 'admin.currency.non_decimal_min_withdraw_amount' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.currency.invalid_min_withdraw_amount' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:min_withdraw_amount][:desc] }
|
||||
},
|
||||
withdraw_limit_24h: {
|
||||
type: { value: BigDecimal, message: 'admin.currency.non_decimal_withdraw_limit_24h' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.currency.invalid_withdraw_limit_24h' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:withdraw_limit_24h][:desc] }
|
||||
},
|
||||
withdraw_limit_72h: {
|
||||
type: { value: BigDecimal, message: 'admin.currency.non_decimal_withdraw_limit_72h' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.currency.invalid_withdraw_limit_72h' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:withdraw_limit_72h][:desc] }
|
||||
},
|
||||
options: {
|
||||
type: { value: JSON, message: 'admin.currency.non_json_options' },
|
||||
default: {},
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:options][:desc] }
|
||||
},
|
||||
visible: {
|
||||
type: { value: Boolean, message: 'admin.currency.non_boolean_visible' },
|
||||
default: true,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:visible][:desc] }
|
||||
},
|
||||
deposit_enabled: {
|
||||
type: { value: Boolean, message: 'admin.currency.non_boolean_deposit_enabled' },
|
||||
default: true,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:deposit_enabled][:desc] }
|
||||
},
|
||||
withdrawal_enabled: {
|
||||
type: { value: Boolean, message: 'admin.currency.non_boolean_withdrawal_enabled' },
|
||||
default: true,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:withdrawal_enabled][:desc] }
|
||||
},
|
||||
precision: {
|
||||
type: { value: Integer, message: 'admin.currency.non_integer_base_precision' },
|
||||
default: 8,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:precision][:desc] }
|
||||
},
|
||||
price: {
|
||||
type: { value: BigDecimal, message: 'admin.currency.non_decimal_price' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:price][:desc] }
|
||||
},
|
||||
icon_url: { desc: -> { API::V2::Admin::Entities::Currency.documentation[:icon_url][:desc] } },
|
||||
description: { desc: -> { API::V2::Admin::Entities::Currency.documentation[:description][:desc] } },
|
||||
homepage: { desc: -> { API::V2::Admin::Entities::Currency.documentation[:homepage][:desc] } },
|
||||
}
|
||||
|
||||
params :create_currency_params do
|
||||
OPTIONAL_CURRENCY_PARAMS.each do |key, params|
|
||||
optional key, params
|
||||
end
|
||||
end
|
||||
|
||||
params :update_currency_params do
|
||||
OPTIONAL_CURRENCY_PARAMS.each do |key, params|
|
||||
optional key, params.except(:default)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get list of currencies',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Currency
|
||||
params do
|
||||
use :currency_type
|
||||
use :pagination
|
||||
optional :ordering,
|
||||
values: { value: %w(asc desc), message: 'admin.pagination.invalid_ordering' },
|
||||
default: 'asc',
|
||||
desc: 'If set, returned values will be sorted in specific order, defaults to \'asc\'.'
|
||||
optional :order_by,
|
||||
default: 'position',
|
||||
desc: 'Name of the field, which result will be ordered by.'
|
||||
optional :deposit_enabled,
|
||||
type: { value: Boolean, message: 'admin.currency.non_boolean_deposit_enabled' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:deposit_enabled][:desc] }
|
||||
optional :withdrawal_enabled,
|
||||
type: { value: Boolean, message: 'admin.currency.non_boolean_withdrawal_enabled' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:withdrawal_enabled][:desc] }
|
||||
optional :visible,
|
||||
type: { value: Boolean, message: 'admin.currency.non_boolean_visible' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:visible][:desc] }
|
||||
end
|
||||
get '/currencies' do
|
||||
admin_authorize! :read, Currency
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:type, :deposit_enabled, :withdrawal_enabled, :visible)
|
||||
.with_daterange
|
||||
.build
|
||||
|
||||
search = Currency.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::Currency
|
||||
end
|
||||
|
||||
desc 'Get a currency.' do
|
||||
success API::V2::Admin::Entities::Currency
|
||||
end
|
||||
params do
|
||||
requires :code,
|
||||
type: String,
|
||||
values: { value: -> { Currency.codes(bothcase: true) }, message: 'admin.currency.doesnt_exist'},
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:code][:desc] }
|
||||
end
|
||||
get '/currencies/:code', requirements: { code: /[\w\.\-]+/ } do
|
||||
admin_authorize! :read, Currency
|
||||
|
||||
present Currency.find(params[:code]), with: API::V2::Admin::Entities::Currency
|
||||
end
|
||||
|
||||
desc 'Create new currency.' do
|
||||
success API::V2::Admin::Entities::Currency
|
||||
end
|
||||
params do
|
||||
use :create_currency_params
|
||||
requires :code,
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:code][:desc] }
|
||||
optional :type,
|
||||
values: { value: ::Currency.types.map(&:to_s), message: 'admin.currency.invalid_type' },
|
||||
default: 'coin',
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:type][:desc] }
|
||||
optional :base_factor,
|
||||
type: { value: Integer, message: 'admin.currency.non_integer_base_factor' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:base_factor][:desc] }
|
||||
optional :position,
|
||||
type: { value: Integer, message: 'admin.currency.non_integer_position' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:position][:desc] }
|
||||
optional :subunits,
|
||||
type: { value: Integer, message: 'admin.currency.non_integer_subunits' },
|
||||
values: { value: (0..18), message: 'admin.currency.invalid_subunits' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:subunits][:desc] }
|
||||
given type: ->(val) { val == 'coin' } do
|
||||
optional :blockchain_key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.currency.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:blockchain_key][:desc] }
|
||||
optional :parent_id,
|
||||
values: { value: -> { Currency.coins_without_tokens.pluck(:id).map(&:to_s) }, message: 'admin.currency.parent_id_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:parent_id][:desc] }
|
||||
end
|
||||
mutually_exclusive :base_factor, :subunits, message: 'admin.currency.one_of_base_factor_subunits_fields'
|
||||
end
|
||||
post '/currencies/new' do
|
||||
admin_authorize! :create, Currency
|
||||
|
||||
currency = Currency.new(declared(params, include_missing: false))
|
||||
if currency.save
|
||||
present currency, with: API::V2::Admin::Entities::Currency
|
||||
status 201
|
||||
else
|
||||
body errors: currency.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update currency.' do
|
||||
success API::V2::Admin::Entities::Currency
|
||||
end
|
||||
params do
|
||||
use :update_currency_params
|
||||
requires :code,
|
||||
values: { value: -> { ::Currency.codes }, message: 'admin.currency.doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:code][:desc] }
|
||||
optional :position,
|
||||
type: { value: Integer, message: 'admin.currency.non_integer_position' },
|
||||
values: { value: -> (p){ p >= ::Currency::TOP_POSITION }, message: 'admin.currency.invalid_position' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:position][:desc] }
|
||||
optional :blockchain_key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.currency.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:blockchain_key][:desc] }
|
||||
given code: -> (val) { val.in?(Currency.coins.pluck(:code).map(&:to_s)) } do
|
||||
optional :parent_id,
|
||||
values: { value: -> { Currency.coins_without_tokens.pluck(:id).map(&:to_s) }, message: 'admin.currency.parent_id_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:parent_id][:desc] }
|
||||
end
|
||||
end
|
||||
post '/currencies/update' do
|
||||
admin_authorize! :update, Currency, params.except(:code)
|
||||
|
||||
currency = Currency.find(params[:code])
|
||||
if currency.update(declared(params, include_missing: false))
|
||||
present currency, with: API::V2::Admin::Entities::Currency
|
||||
else
|
||||
body errors: currency.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
133
app/api/v2/admin/deposit_limits.rb
Normal file
133
app/api/v2/admin/deposit_limits.rb
Normal file
@@ -0,0 +1,133 @@
|
||||
# frozen_string_literal: true
|
||||
#
|
||||
# this file follow withdraw_limits.rb
|
||||
#
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class DepositLimits < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
# list of deposit limits
|
||||
desc 'Returns deposit limits table as paginated collection',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::DepositLimit
|
||||
params do
|
||||
optional :group,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:group][:desc] },
|
||||
coerce_with: ->(c) { c.strip.downcase }
|
||||
optional :kyc_level,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:kyc_level][:desc] }
|
||||
optional :kind,
|
||||
values: { value: ->(v) { (Array.wrap(v) - DepositLimit.kind.values).blank? }, message: 'limit.kind.invalid' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:kind][:desc] }
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/deposit_limits' do
|
||||
admin_authorize! :read, DepositLimit
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:group, :kyc_level, :kind)
|
||||
.build
|
||||
|
||||
search = DepositLimit.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result), with: API::V2::Entities::DepositLimit
|
||||
end
|
||||
|
||||
# create deposit limit
|
||||
desc 'It creates deposit limits record',
|
||||
success: API::V2::Entities::DepositLimit
|
||||
params do
|
||||
requires :limit_24_hour,
|
||||
type: { value: BigDecimal, message: 'admin.deposit_limit.non_decimal_limit_24_hour' },
|
||||
values: { value: ->(p) { p && p >= 0 }, message: 'admin.deposit_limit.invalid_limit_24_hour' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:limit_24_hour][:desc] }
|
||||
requires :limit_1_month,
|
||||
type: { value: BigDecimal, message: 'admin.deposit_limit.non_decimal_limit_1_month' },
|
||||
values: { value: ->(p) { p && p >= 0 }, message: 'admin.deposit_limit.invalid_limit_1_month' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:limit_1_month][:desc] }
|
||||
requires :kind,
|
||||
values: { value: ->(v) { (Array.wrap(v) - DepositLimit.kind.values).blank? }, message: 'limit.kind.invalid' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:kind][:desc] }
|
||||
optional :group,
|
||||
type: String,
|
||||
default: ::DepositLimit::ANY,
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:group][:desc] }
|
||||
optional :kyc_level,
|
||||
type: String,
|
||||
default: ::DepositLimit::ANY,
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:kyc_level][:desc] }
|
||||
end
|
||||
post '/deposit_limits' do
|
||||
admin_authorize! :create, DepositLimit
|
||||
|
||||
deposit_limit = ::DepositLimit.new(declared(params))
|
||||
if deposit_limit.save
|
||||
present deposit_limit, with: API::V2::Entities::DepositLimit
|
||||
status 201
|
||||
else
|
||||
body errors: deposit_limit.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
# update deposit limit
|
||||
desc 'It updates deposit limits record',
|
||||
success: API::V2::Entities::DepositLimit
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.deposit_limit.non_integer_id' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:id][:desc] }
|
||||
optional :limit_24_hour,
|
||||
type: { value: BigDecimal, message: 'admin.deposit_limit.non_decimal_limit_24_hour' },
|
||||
values: { value: ->(p) { p && p >= 0 }, message: 'admin.deposit_limit.invalid_limit_24_hour' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:limit_24_hour][:desc] }
|
||||
optional :limit_1_month,
|
||||
type: { value: BigDecimal, message: 'admin.deposit_limit.non_decimal_limit_1_month' },
|
||||
values: { value: ->(p) { p && p >= 0 }, message: 'admin.deposit_limit.invalid_limit_1_month' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:limit_1_month][:desc] }
|
||||
optional :kind,
|
||||
values: { value: ->(v) { (Array.wrap(v) - DepositLimit.kind.values).blank? }, message: 'limit.kind.invalid' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:kind][:desc] }
|
||||
optional :kyc_level,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:kyc_level][:desc] }
|
||||
optional :group,
|
||||
type: String,
|
||||
coerce_with: ->(c) { c.strip.downcase },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:group][:desc] }
|
||||
end
|
||||
put '/deposit_limits' do
|
||||
admin_authorize! :update, DepositLimit
|
||||
|
||||
deposit_limit = ::DepositLimit.find(params[:id])
|
||||
if deposit_limit.update(declared(params, include_missing: false))
|
||||
present deposit_limit, with: API::V2::Entities::DepositLimit
|
||||
else
|
||||
body errors: deposit_limit.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
# delete deposit limit
|
||||
desc 'It deletes deposit limits record',
|
||||
success: API::V2::Entities::DepositLimit
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.deposit_limit.non_integer_id' },
|
||||
desc: -> { API::V2::Entities::DepositLimit.documentation[:id][:desc] }
|
||||
end
|
||||
delete '/deposit_limits/:id' do
|
||||
admin_authorize! :delete, DepositLimit
|
||||
|
||||
present DepositLimit.destroy(params[:id]), with: API::V2::Entities::DepositLimit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
180
app/api/v2/admin/deposits.rb
Normal file
180
app/api/v2/admin/deposits.rb
Normal file
@@ -0,0 +1,180 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Deposits < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
desc 'Get all deposits, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Deposit
|
||||
params do
|
||||
optional :state,
|
||||
values: { value: -> { ::Deposit.aasm.states.map(&:name).map(&:to_s) }, message: 'admin.deposit.invalid_state' },
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:state][:desc] }
|
||||
optional :id,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:id][:desc] }
|
||||
optional :txid,
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:txid][:desc] }
|
||||
optional :address,
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:address][:desc] }
|
||||
optional :tid,
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:tid][:desc] }
|
||||
optional :email,
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:email][:desc] }
|
||||
use :uid
|
||||
use :currency
|
||||
use :currency_type
|
||||
use :date_picker
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/deposits' do
|
||||
admin_authorize! :read, Deposit
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:id, :txid, :tid, :address)
|
||||
.translate(state: :aasm_state, uid: :member_uid, currency: :currency_id, email: :member_email)
|
||||
.with_daterange
|
||||
.merge(type_eq: params[:type].present? ? "Deposits::#{params[:type].capitalize}" : nil)
|
||||
.build
|
||||
|
||||
search = Deposit.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::Deposit
|
||||
end
|
||||
|
||||
desc 'Take an action on the deposit.',
|
||||
success: API::V2::Admin::Entities::Deposit
|
||||
params do
|
||||
requires :id,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:id][:desc] }
|
||||
requires :action,
|
||||
type: String,
|
||||
values: { value: -> { ::Deposit.aasm.events.map(&:name).map(&:to_s) }, message: 'admin.deposit.invalid_action' },
|
||||
desc: "Valid actions are #{::Deposit.aasm.events.map(&:name)}."
|
||||
given action: ->(val) { val == 'process' } do
|
||||
optional :fees,
|
||||
type: Boolean,
|
||||
default: false,
|
||||
desc: 'Process deposit collection with collecting fees or not'
|
||||
end
|
||||
end
|
||||
post '/deposits/actions' do
|
||||
admin_authorize! :update, Deposit
|
||||
|
||||
deposit = Deposit.find(params[:id])
|
||||
|
||||
if deposit.public_send("may_#{params[:action]}?")
|
||||
deposit.public_send("#{params[:action]}!")
|
||||
present deposit, with: API::V2::Admin::Entities::Deposit
|
||||
else
|
||||
body errors: ["admin.depodit.cannot_#{params[:action]}"]
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Creates new fiat deposit .',
|
||||
success: API::V2::Admin::Entities::Deposit
|
||||
params do
|
||||
requires :uid,
|
||||
values: { value: -> (v) { Member.exists?(uid: v) }, message: 'admin.deposit.user_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:uid][:desc] }
|
||||
requires :currency,
|
||||
values: { value: -> { Currency.fiats.codes(bothcase: true) }, message: 'admin.deposit.currency_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:currency][:desc] }
|
||||
requires :amount,
|
||||
type: { value: BigDecimal, message: 'admin.deposit.non_decimal_amount' },
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:amount][:desc] }
|
||||
optional :tid,
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:tid][:desc] }
|
||||
end
|
||||
post '/deposits/new' do
|
||||
admin_authorize! :create, ::Deposits::Fiat
|
||||
|
||||
declared_params = declared(params, include_missing: false)
|
||||
member = Member.find_by(uid: declared_params[:uid])
|
||||
currency = Currency.find(declared_params[:currency])
|
||||
data = { member: member, currency: currency }.merge!(declared_params.slice(:amount, :tid))
|
||||
deposit = ::Deposits::Fiat.new(data)
|
||||
|
||||
if deposit.save
|
||||
present deposit, with: API::V2::Admin::Entities::Deposit
|
||||
status 201
|
||||
else
|
||||
body errors: deposit.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Creates new crypto refund',
|
||||
success: API::V2::Admin::Entities::Refund
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.deposit.non_integer_type' },
|
||||
desc: -> { 'Deposit id' }
|
||||
requires :address,
|
||||
desc: -> { API::V2::Admin::Entities::Refund.documentation[:address][:desc] }
|
||||
end
|
||||
post '/deposits/:id/refund' do
|
||||
admin_authorize! :wrrie, Deposit
|
||||
|
||||
deposit = Deposit.find(params[:id])
|
||||
|
||||
refund = ::Refund.new(deposit: deposit, address: params[:address])
|
||||
|
||||
if refund.save
|
||||
present refund, with: API::V2::Admin::Entities::Refund
|
||||
else
|
||||
body errors: refund.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Returns deposit address for account you want to deposit to by currency and uid.',
|
||||
success: API::V2::Admin::Entities::Deposit
|
||||
params do
|
||||
requires :uid,
|
||||
values: { value: -> (v) { Member.exists?(uid: v) }, message: 'admin.deposit.user_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:uid][:desc] }
|
||||
requires :currency,
|
||||
values: { value: -> { Currency.codes }, message: 'admin.deposit.currency_doesnt_exist' },
|
||||
as: :currency_id,
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:currency][:desc] }
|
||||
given :currency_id do
|
||||
optional :address_format,
|
||||
type: String,
|
||||
values: { value: -> { %w[legacy cash] }, message: 'admin.deposit.invalid_address_format' },
|
||||
validate_currency_address_format: { value: true, prefix: 'admin.deposit' },
|
||||
desc: 'Address format legacy/cash'
|
||||
end
|
||||
end
|
||||
post '/deposit_address' do
|
||||
admin_authorize! :create, PaymentAddress
|
||||
|
||||
member = Member.find_by!(uid: params[:uid])
|
||||
currency = Currency.find_by!(id: params[:currency_id])
|
||||
wallet = Wallet.deposit_wallet(currency.id)
|
||||
|
||||
unless wallet.present?
|
||||
error!({ errors: ['admin.deposit.wallet_not_found'] }, 422)
|
||||
end
|
||||
|
||||
if currency.deposit_enabled
|
||||
payment_address = member.payment_address(wallet.id)
|
||||
present payment_address, with: API::V2::Entities::PaymentAddress, address_format: params[:address_format]
|
||||
status 201
|
||||
else
|
||||
body errors: ["admin.deposit.deposit_disabled"]
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
101
app/api/v2/admin/engines.rb
Normal file
101
app/api/v2/admin/engines.rb
Normal file
@@ -0,0 +1,101 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Engines < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
desc 'Get all engine, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Engine
|
||||
params do
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/engines' do
|
||||
admin_authorize! :read, ::Engine
|
||||
|
||||
result = ::Engine.order(params[:order_by] => params[:ordering])
|
||||
present paginate(result), with: API::V2::Admin::Entities::Engine
|
||||
end
|
||||
|
||||
desc 'Get engine.' do
|
||||
success API::V2::Admin::Entities::Engine
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: String,
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:id][:desc] }
|
||||
end
|
||||
get '/engines/:id' do
|
||||
admin_authorize! :read, ::Engine
|
||||
|
||||
present ::Engine.find(params[:id]), with: API::V2::Admin::Entities::Engine
|
||||
end
|
||||
|
||||
desc 'Create new engine.' do
|
||||
success API::V2::Admin::Entities::Engine
|
||||
end
|
||||
params do
|
||||
requires :name,
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:name][:desc] },
|
||||
values: { value: ->(v) { !v.in?(::Engine.pluck(:name)) }, message: 'admin.engine.duplicate_name' }
|
||||
requires :driver,
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:driver][:desc] }
|
||||
optional :uid,
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:uid][:desc] }
|
||||
optional :key,
|
||||
desc: -> { 'Credentials for remote engine' }
|
||||
optional :secret,
|
||||
desc: -> { 'Credentials for remote engine' }
|
||||
optional :data,
|
||||
desc: -> { 'Metadata for engine' }
|
||||
end
|
||||
post '/engines/new' do
|
||||
admin_authorize! :create, ::Engine
|
||||
|
||||
engine = ::Engine.new(declared(params))
|
||||
if engine.save
|
||||
present engine, with: API::V2::Admin::Entities::Engine
|
||||
status 201
|
||||
else
|
||||
body errors: engine.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update engine' do
|
||||
success API::V2::Admin::Entities::Engine
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:id][:desc] }
|
||||
optional :name,
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:name][:desc] }
|
||||
optional :driver,
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:driver][:desc] }
|
||||
optional :key,
|
||||
desc: -> { 'Credentials for remote engine' }
|
||||
optional :secret,
|
||||
desc: -> { 'Credentials for remote engine' }
|
||||
optional :state,
|
||||
values: { value: ::Engine::STATES.values, message: 'admin.engine.invalid_state' },
|
||||
default: 1,
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:state][:desc] }
|
||||
end
|
||||
post '/engines/update' do
|
||||
admin_authorize! :update, ::Engine
|
||||
|
||||
engine = ::Engine.find(params[:id])
|
||||
if engine.update(declared(params, include_missing: false))
|
||||
present engine, with: API::V2::Admin::Entities::Engine
|
||||
else
|
||||
body errors: engine.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
169
app/api/v2/admin/entities/adjustment.rb
Normal file
169
app/api/v2/admin/entities/adjustment.rb
Normal file
@@ -0,0 +1,169 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Adjustment < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Unique adjustment identifier in database.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:reason,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Adjustment reason.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:description,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Adjustment description.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:category,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Adjustment category'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:amount,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Adjustment amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:validator_uid,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Unique adjustment validator identifier in database.'
|
||||
},
|
||||
if: ->(adjustment, _options) { adjustment.validator }
|
||||
) do |adjustment, _options|
|
||||
adjustment.validator.uid
|
||||
end
|
||||
|
||||
expose(
|
||||
:creator_uid,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Unique adjustment creator identifier in database.'
|
||||
},
|
||||
) do |adjustment, _options|
|
||||
adjustment.creator.uid
|
||||
end
|
||||
|
||||
expose(
|
||||
:currency_id,
|
||||
as: :currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Adjustment currency ID.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:asset,
|
||||
using: API::V2::Admin::Entities::Operation,
|
||||
if: ->(adjustment, _options) { adjustment.fetch_asset }
|
||||
) do |adjustment, _options|
|
||||
adjustment.fetch_asset
|
||||
end
|
||||
|
||||
expose(
|
||||
:liability,
|
||||
using: API::V2::Admin::Entities::Operation,
|
||||
if: ->(adjustment, _options) { adjustment.fetch_liability }
|
||||
) do |adjustment, _options|
|
||||
adjustment.fetch_liability
|
||||
end
|
||||
|
||||
expose(
|
||||
:revenue,
|
||||
using: API::V2::Admin::Entities::Operation,
|
||||
if: ->(adjustment, _options) { adjustment.fetch_revenue }
|
||||
) do |adjustment, _options|
|
||||
adjustment.fetch_revenue
|
||||
end
|
||||
|
||||
expose(
|
||||
:expense,
|
||||
using: API::V2::Admin::Entities::Operation,
|
||||
if: ->(adjustment, _options) { adjustment.fetch_expense }
|
||||
) do |adjustment, _options|
|
||||
adjustment.fetch_expense
|
||||
end
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Adjustment\'s state.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:asset_account_code,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Adjustment asset account code.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:receiving_account_code,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Adjustment receiving account code.'
|
||||
}
|
||||
) do |adjustment, _options|
|
||||
::Operations.split_account_number(account_number: adjustment.receiving_account_number)[:code]
|
||||
end
|
||||
|
||||
expose(
|
||||
:receiving_member_uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Adjustment receiving member uid.'
|
||||
},
|
||||
if: ->(adjustment, _options) { adjustment.fetch_liability.present? }
|
||||
) do |adjustment, _options|
|
||||
::Operations.split_account_number(account_number: adjustment.receiving_account_number)[:member_uid]
|
||||
end
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetime when operation was created.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetime when operation was updated.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
20
app/api/v2/admin/entities/beneficiary.rb
Normal file
20
app/api/v2/admin/entities/beneficiary.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Beneficiary < API::V2::Entities::Beneficiary
|
||||
expose(
|
||||
:data,
|
||||
documentation: {
|
||||
desc: 'Bank Account details for fiat Beneficiary in JSON format.'\
|
||||
'For crypto it\'s blockchain address.',
|
||||
type: JSON
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
102
app/api/v2/admin/entities/blockchain.rb
Normal file
102
app/api/v2/admin/entities/blockchain.rb
Normal file
@@ -0,0 +1,102 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Blockchain < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation:{
|
||||
type: Integer,
|
||||
desc: 'Unique blockchain identifier in database.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:key,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'Unique key to identify blockchain.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:name,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'A name to identify blockchain.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:client,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'Integrated blockchain client.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:height,
|
||||
documentation:{
|
||||
type: Integer,
|
||||
desc: 'The number of blocks preceding a particular block on blockchain.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:explorer_address,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'Blockchain explorer address template.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:explorer_transaction,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'Blockchain explorer transaction template.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:min_confirmations,
|
||||
documentation:{
|
||||
type: Integer,
|
||||
desc: 'Minimum number of confirmations.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:status,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'Blockchain status (active/disabled).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Blockchain created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Blockchain updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
123
app/api/v2/admin/entities/currency.rb
Normal file
123
app/api/v2/admin/entities/currency.rb
Normal file
@@ -0,0 +1,123 @@
|
||||
# enco ding: UTF-8
|
||||
# froz en_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Currency < API::V2::Entities::Currency
|
||||
unexpose(:id)
|
||||
|
||||
expose(
|
||||
:code,
|
||||
documentation: {
|
||||
desc: 'Unique currency code.',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:blockchain_key,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Associated blockchain key which will perform transactions synchronization for currency.'
|
||||
},
|
||||
if: -> (currency){ currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:parent_id,
|
||||
documentation: {
|
||||
desc: 'Parent currency id.',
|
||||
type: String
|
||||
},
|
||||
if: -> (currency){ currency.token? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:min_collection_amount,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Minimal collection amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:position,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Currency position.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:visible,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency display status (true/false).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:base_factor,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Currency base factor.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:subunits,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Fraction of the basic monetary unit.'
|
||||
}
|
||||
) { |currency| currency.subunits }
|
||||
|
||||
expose(
|
||||
:options,
|
||||
documentation: {
|
||||
type: JSON,
|
||||
desc: 'Currency options.'
|
||||
},
|
||||
if: -> (currency){ currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:precision,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Currency precision.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Currency price.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
108
app/api/v2/admin/entities/deposit.rb
Normal file
108
app/api/v2/admin/entities/deposit.rb
Normal file
@@ -0,0 +1,108 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Deposit < API::V2::Entities::Deposit
|
||||
expose(
|
||||
:member_id,
|
||||
as: :member,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The member id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit member uid.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:email,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The deposit member email.'
|
||||
}
|
||||
) { |d| d.member.email }
|
||||
|
||||
expose(
|
||||
:address,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit blockchain address.'
|
||||
},
|
||||
if: ->(deposit) { deposit.currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:txout,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Deposit blockchain transaction output.'
|
||||
},
|
||||
if: ->(deposit) { deposit.currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:block_number,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Deposit blockchain block number.'
|
||||
},
|
||||
if: ->(deposit) { deposit.currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:type,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit type (fiat or coin).'
|
||||
}
|
||||
) { |d| d.currency.fiat? ? :fiat : :coin }
|
||||
|
||||
expose(
|
||||
:tid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit tid.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:spread,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit collection spread.'
|
||||
},
|
||||
if: -> (deposit) { !deposit.spread.empty? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetime when deposit was updated.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:completed_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetime when deposit was completed.'
|
||||
},
|
||||
if: ->(deposit) { deposit.completed? }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
51
app/api/v2/admin/entities/engine.rb
Normal file
51
app/api/v2/admin/entities/engine.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Engine < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Engine uniq id'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:name,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Engine name'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:driver,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Engine driver'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Owner of a engine'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Engine state'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
20
app/api/v2/admin/entities/internal_transfer.rb
Normal file
20
app/api/v2/admin/entities/internal_transfer.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class InternalTransfer < API::V2::Entities::InternalTransfer
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Internal transfer uniq id'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
54
app/api/v2/admin/entities/market.rb
Normal file
54
app/api/v2/admin/entities/market.rb
Normal file
@@ -0,0 +1,54 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Market < API::V2::Entities::Market
|
||||
expose(
|
||||
:engine_id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Engine id for this market.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:position,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Market position.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:data,
|
||||
documentation: {
|
||||
type: JSON,
|
||||
desc: 'Market additional data.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Market created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Market updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
104
app/api/v2/admin/entities/member.rb
Normal file
104
app/api/v2/admin/entities/member.rb
Normal file
@@ -0,0 +1,104 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Member < API::V2::Entities::Member
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Unique member identifier in database.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:level,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Member\'s level.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:role,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Member\'s role.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:group,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Member\'s group.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Member\'s state.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Member created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Member updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:beneficiaries,
|
||||
using: API::V2::Admin::Entities::Beneficiary,
|
||||
documentation: {
|
||||
type: 'API::V2::Admin::Entities::Beneficiary',
|
||||
is_array: true,
|
||||
desc: 'Member Beneficiary.'
|
||||
}
|
||||
) do |m|
|
||||
m.beneficiaries
|
||||
end
|
||||
|
||||
expose(
|
||||
:accounts,
|
||||
using: API::V2::Entities::Account,
|
||||
documentation: {
|
||||
type: 'API::V2::Entities::Account',
|
||||
is_array: true,
|
||||
desc: 'Member accounts.'
|
||||
}
|
||||
) do |m|
|
||||
m.accounts.includes(:currency)
|
||||
end
|
||||
|
||||
expose(
|
||||
:payment_addresses,
|
||||
as: :deposit_addresses,
|
||||
using: API::V2::Entities::PaymentAddress,
|
||||
documentation: {
|
||||
type: 'API::V2::Entities::PaymentAddress',
|
||||
is_array: true,
|
||||
desc: 'Member deposits addresses'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
94
app/api/v2/admin/entities/operation.rb
Normal file
94
app/api/v2/admin/entities/operation.rb
Normal file
@@ -0,0 +1,94 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Operation < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Unique operation identifier in database.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:code,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The Account code which this operation related to.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:currency_id,
|
||||
as: :currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Operation currency ID.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:credit,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Operation credit amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:debit,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Operation debit amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:uid,
|
||||
if: ->(operation) { operation.try(:member).try(:uid) },
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
}) { |operation| operation.try(:member).try(:uid) }
|
||||
|
||||
expose(
|
||||
:account_kind,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Operation\'s account kind (locked or main).'
|
||||
}
|
||||
) { |operation| operation.account.kind }
|
||||
|
||||
expose(
|
||||
:reference_id,
|
||||
as: :rid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The id of operation reference.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:reference_type,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The type of operations.'
|
||||
}) { |operation| operation.reference_type.downcase if operation.reference_type.present? }
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetime when operation was created.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
30
app/api/v2/admin/entities/order.rb
Normal file
30
app/api/v2/admin/entities/order.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Order < API::V2::Entities::Order
|
||||
unexpose(:trades)
|
||||
|
||||
expose(
|
||||
:email,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The shared user email.'
|
||||
}
|
||||
) { |w| w.member.email }
|
||||
|
||||
expose(
|
||||
:uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
}
|
||||
) { |w| w.member.uid }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
33
app/api/v2/admin/entities/refund.rb
Normal file
33
app/api/v2/admin/entities/refund.rb
Normal file
@@ -0,0 +1,33 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Refund < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'The refund id'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:address,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Refund address'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:deposit,
|
||||
using: API::V2::Admin::Entities::Deposit
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
112
app/api/v2/admin/entities/trade.rb
Normal file
112
app/api/v2/admin/entities/trade.rb
Normal file
@@ -0,0 +1,112 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Trade < API::V2::Entities::Trade
|
||||
unexpose(:side)
|
||||
unexpose(:order_id)
|
||||
unexpose(:fee_currency)
|
||||
unexpose(:fee)
|
||||
unexpose(:fee_amount)
|
||||
|
||||
expose(
|
||||
:maker_order_email,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade maker member email.'
|
||||
}
|
||||
) { |trade| trade.maker.email }
|
||||
|
||||
expose(
|
||||
:maker_uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade maker member uid.'
|
||||
}
|
||||
) { |trade| trade.maker.uid }
|
||||
|
||||
expose(
|
||||
:maker_fee,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade maker fee percentage.',
|
||||
},
|
||||
if: ->(object, options) { options[:extended] }
|
||||
) { |trade| trade.maker_order.maker_fee }
|
||||
|
||||
expose(
|
||||
:maker_fee_amount,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade maker fee amount.',
|
||||
}
|
||||
) { |trade| fee_amount(trade, trade.maker_order) }
|
||||
|
||||
expose(
|
||||
:maker_fee_currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade maker fee currency code.'
|
||||
}
|
||||
) { |trade| fee_currency(trade.maker_order) }
|
||||
|
||||
expose(
|
||||
:maker_order,
|
||||
using: API::V2::Admin::Entities::Order,
|
||||
if: ->(object, options) { options[:extended] }
|
||||
)
|
||||
|
||||
expose(
|
||||
:taker_order_email,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade taker member email.'
|
||||
}
|
||||
) { |trade| trade.taker.email }
|
||||
|
||||
expose(
|
||||
:taker_uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade taker member uid.'
|
||||
}
|
||||
) { |trade| trade.taker.uid }
|
||||
|
||||
expose(
|
||||
:taker_fee_currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Trade taker fee currency code.'
|
||||
}
|
||||
) { |trade| fee_currency(trade.taker_order) }
|
||||
|
||||
expose(
|
||||
:taker_fee,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade taker fee percentage.',
|
||||
},
|
||||
if: ->(object, options) { options[:extended] }
|
||||
) { |trade| trade.taker_order.taker_fee }
|
||||
|
||||
expose(
|
||||
:taker_fee_amount,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Trade taker fee amount.',
|
||||
}
|
||||
) { |trade| fee_amount(trade, trade.taker_order) }
|
||||
|
||||
expose(
|
||||
:taker_order,
|
||||
using: API::V2::Admin::Entities::Order,
|
||||
if: ->(object, options) { options[:extended] }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
112
app/api/v2/admin/entities/wallet.rb
Normal file
112
app/api/v2/admin/entities/wallet.rb
Normal file
@@ -0,0 +1,112 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Wallet < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation:{
|
||||
type: Integer,
|
||||
desc: 'Unique wallet identifier in database.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:name,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Wallet name.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:kind,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Kind of wallet 'deposit','fee','hot','warm' or 'cold'."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:currency_ids,
|
||||
as: :currencies,
|
||||
documentation: {
|
||||
is_array: true,
|
||||
desc: 'Wallet currency code.',
|
||||
example: -> { ::Currency.visible.codes }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:address,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Wallet address.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:gateway,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Wallet gateway.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:max_balance,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Wallet max balance.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:balance,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Wallet balance'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:blockchain_key,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Wallet blockchain key.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:status,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Wallet status (active/disabled).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Wallet created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Wallet updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
69
app/api/v2/admin/entities/whitelisted_smart_contract.rb
Normal file
69
app/api/v2/admin/entities/whitelisted_smart_contract.rb
Normal file
@@ -0,0 +1,69 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class WhitelistedSmartContract < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Unique whitelisted smart contract identifier in database.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:address,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Whitelisted smart contract address.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:description,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Whitelisted smart contract description.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:blockchain_key,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Whitelisted smart contract blockchain key.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Whitelisted smart contract status (active/disabled).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Whitelisted smart contract created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Whitelisted smart contract updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
87
app/api/v2/admin/entities/withdraw.rb
Normal file
87
app/api/v2/admin/entities/withdraw.rb
Normal file
@@ -0,0 +1,87 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Entities
|
||||
class Withdraw < API::V2::Entities::Withdraw
|
||||
expose(
|
||||
:member_id,
|
||||
as: :member,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The member id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:beneficiary,
|
||||
using: API::V2::Entities::Beneficiary,
|
||||
if: ->(withdraw, options) do
|
||||
options[:with_beneficiary] && withdraw.beneficiary.present?
|
||||
end
|
||||
)
|
||||
|
||||
expose(
|
||||
:uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The withdrawal member uid.'
|
||||
}
|
||||
) { |w| w.member.uid }
|
||||
|
||||
expose(
|
||||
:email,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The withdrawal member email.'
|
||||
}
|
||||
) { |w| w.member.email }
|
||||
|
||||
expose(
|
||||
:account,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The account code.'
|
||||
}
|
||||
) { |w| w.account.id }
|
||||
|
||||
expose(
|
||||
:block_number,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'The withdrawal block_number.'
|
||||
},
|
||||
if: ->(w) { w.currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:tid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Withdraw tid.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:error,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Withdraw error.'
|
||||
},
|
||||
unless: ->(w) { w.succeed? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:metadata,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Optional metadata to be applied to the transaction.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
115
app/api/v2/admin/helpers.rb
Normal file
115
app/api/v2/admin/helpers.rb
Normal file
@@ -0,0 +1,115 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
module Helpers
|
||||
extend ::Grape::API::Helpers
|
||||
|
||||
class RansackBuilder
|
||||
# RansackBuilder creates a hash in a format ransack accepts
|
||||
# eq(:column) generetes a pair column_eq: params[:column]
|
||||
# translate(:column1 => :column2) generates a pair column2_eq: params[:column1]
|
||||
# merge allows to append additional selectors in
|
||||
# build returns prepared hash
|
||||
|
||||
attr_reader :build
|
||||
|
||||
def initialize(params)
|
||||
@params = params
|
||||
@build = {}
|
||||
end
|
||||
|
||||
def merge(opt)
|
||||
@build.merge!(opt)
|
||||
self
|
||||
end
|
||||
|
||||
def with_daterange
|
||||
@build.merge!("#{@params[:range]}_at_gteq" => @params[:from])
|
||||
@build.merge!("#{@params[:range]}_at_lteq" => @params[:to])
|
||||
self
|
||||
end
|
||||
|
||||
def translate(opt)
|
||||
opt.each { |k, v| @build.merge!("#{v}_eq" => @params[k]) }
|
||||
self
|
||||
end
|
||||
|
||||
def translate_in(opt)
|
||||
opt.each { |k, v| @build.merge!("#{v}_in" => @params[k]) }
|
||||
self
|
||||
end
|
||||
|
||||
def in(*keys)
|
||||
keys.each { |k| @build.merge!("#{k}_in" => @params[k]) }
|
||||
self
|
||||
end
|
||||
|
||||
def eq(*keys)
|
||||
keys.each { |k| @build.merge!("#{k}_eq" => @params[k]) }
|
||||
self
|
||||
end
|
||||
end
|
||||
|
||||
params :currency_type do
|
||||
optional :type,
|
||||
type: String,
|
||||
values: { value: ::Currency.types.map(&:to_s), message: 'admin.currency.invalid_type' },
|
||||
desc: -> { API::V2::Admin::Entities::Currency.documentation[:type][:desc] }
|
||||
end
|
||||
|
||||
params :currency do
|
||||
optional :currency,
|
||||
values: { value: -> { Currency.codes(bothcase: true) }, message: 'admin.currency.doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Deposit.documentation[:currency][:desc] }
|
||||
end
|
||||
|
||||
params :uid do
|
||||
optional :uid,
|
||||
values: { value: -> (v) { Member.exists?(uid: v) }, message: 'admin.user.doesnt_exist' },
|
||||
desc: -> { API::V2::Entities::Member.documentation[:uid][:desc] }
|
||||
end
|
||||
|
||||
params :pagination do
|
||||
optional :limit,
|
||||
type: { value: Integer, message: 'admin.pagination.non_integer_limit' },
|
||||
values: { value: 1..1000, message: 'admin.pagination.invalid_limit' },
|
||||
default: 100,
|
||||
desc: 'Limit the number of returned paginations. Defaults to 100.'
|
||||
optional :page,
|
||||
type: { value: Integer, message: 'admin.pagination.non_integer_page' },
|
||||
allow_blank: false,
|
||||
default: 1,
|
||||
desc: 'Specify the page of paginated results.'
|
||||
end
|
||||
|
||||
params :ordering do
|
||||
optional :ordering,
|
||||
values: { value: %w(asc desc), message: 'admin.pagination.invalid_ordering' },
|
||||
default: 'asc',
|
||||
desc: 'If set, returned values will be sorted in specific order, defaults to \'asc\'.'
|
||||
optional :order_by,
|
||||
default: 'id',
|
||||
desc: 'Name of the field, which result will be ordered by.'
|
||||
end
|
||||
|
||||
params :date_picker do
|
||||
optional :range,
|
||||
default: 'created',
|
||||
values: { value: -> { %w[created updated completed] } },
|
||||
desc: 'Date range picker, defaults to \'created\'.'
|
||||
optional :from,
|
||||
type: { value: Time, message: 'admin.filter.range_from_invalid' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'\
|
||||
'If set, only entities FROM the time will be retrieved.'
|
||||
optional :to,
|
||||
type: { value: Time, message: 'admin.filter.range_to_invalid' },
|
||||
desc: 'An integer represents the seconds elapsed since Unix epoch.'\
|
||||
'If set, only entities BEFORE the time will be retrieved.'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
51
app/api/v2/admin/internal_transfers.rb
Normal file
51
app/api/v2/admin/internal_transfers.rb
Normal file
@@ -0,0 +1,51 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class InternalTransfers < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
desc 'Get all internal transfers.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::InternalTransfer
|
||||
params do
|
||||
optional :sender,
|
||||
values: { value: -> (v) { Member.where('uid = ? OR username = ?', v, v).present? }, message: 'admin.receiver.doesnt_exist' },
|
||||
desc: 'Sender uid or username.'
|
||||
optional :receiver,
|
||||
values: { value: -> (v) { Member.where('uid = ? OR username = ?', v, v).present? }, message: 'admin.receiver.doesnt_exist' },
|
||||
desc: 'Receiver uid or username.'
|
||||
use :currency
|
||||
use :pagination
|
||||
use :date_picker
|
||||
use :ordering
|
||||
end
|
||||
get '/internal_transfers' do
|
||||
admin_authorize! :read, InternalTransfer
|
||||
|
||||
if params[:sender].present?
|
||||
sender = Member.find_by('uid = ? OR username = ?', params[:sender], params[:sender])
|
||||
params.except!(:sender).merge!(sender_id: sender.id) if sender.present?
|
||||
end
|
||||
|
||||
if params[:receiver].present?
|
||||
receiver = Member.find_by('uid = ? OR username = ?', params[:receiver], params[:receiver])
|
||||
params.except!(:receiver).merge!(receiver_id: receiver.id) if receiver.present?
|
||||
end
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:sender_id, :receiver_id)
|
||||
.translate_in(currency: :currency_id)
|
||||
.with_daterange
|
||||
.build
|
||||
|
||||
search = ::InternalTransfer.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::InternalTransfer
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
171
app/api/v2/admin/markets.rb
Normal file
171
app/api/v2/admin/markets.rb
Normal file
@@ -0,0 +1,171 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Markets < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
helpers do
|
||||
# Collection of shared params, used to
|
||||
# generate required/optional Grape params.
|
||||
OPTIONAL_MARKET_PARAMS ||= {
|
||||
amount_precision: {
|
||||
type: { value: Integer, message: 'admin.market.non_integer_amount_precision' },
|
||||
values: { value: -> (p){ p && p >= 0 }, message: 'admin.market.invalid_amount_precision' },
|
||||
default: 4,
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:amount_precision][:desc] }
|
||||
},
|
||||
price_precision: {
|
||||
type: { value: Integer, message: 'admin.market.non_integer_price_precision' },
|
||||
values: { value: -> (p){ p && p >= 0 }, message: 'admin.market.invalid_price_precision' },
|
||||
default: 4,
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:price_precision][:desc] }
|
||||
},
|
||||
max_price: {
|
||||
type: { value: BigDecimal, message: 'admin.market.non_decimal_max_price' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.market.invalid_max_price' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:max_price][:desc] }
|
||||
},
|
||||
data: {
|
||||
type: { value: JSON, message: 'admin.market.invalid_data' },
|
||||
default: {},
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:data][:desc] }
|
||||
},
|
||||
state: {
|
||||
values: { value: ::Market::STATES, message: 'admin.market.invalid_state' },
|
||||
default: 'enabled',
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:state][:desc] }
|
||||
},
|
||||
}
|
||||
|
||||
params :create_market_params do
|
||||
OPTIONAL_MARKET_PARAMS.each do |key, params|
|
||||
optional key, params
|
||||
end
|
||||
end
|
||||
|
||||
params :update_market_params do
|
||||
OPTIONAL_MARKET_PARAMS.each do |key, params|
|
||||
optional key, params.except(:default)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get all markets, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Market
|
||||
params do
|
||||
use :pagination
|
||||
optional :ordering,
|
||||
values: { value: %w(asc desc), message: 'admin.pagination.invalid_ordering' },
|
||||
default: 'asc',
|
||||
desc: 'If set, returned values will be sorted in specific order, defaults to \'asc\'.'
|
||||
optional :order_by,
|
||||
default: 'position',
|
||||
desc: 'Name of the field, which result will be ordered by.'
|
||||
end
|
||||
get '/markets' do
|
||||
admin_authorize! :read, ::Market
|
||||
|
||||
result = ::Market.order(params[:order_by] => params[:ordering])
|
||||
present paginate(result), with: API::V2::Admin::Entities::Market
|
||||
end
|
||||
|
||||
desc 'Get market.' do
|
||||
success API::V2::Admin::Entities::Market
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: String,
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:id][:desc] }
|
||||
end
|
||||
get '/markets/:id', requirements: { id: /[\w\.\-]+/ } do
|
||||
admin_authorize! :read, ::Market
|
||||
|
||||
present ::Market.find(params[:id]), with: API::V2::Admin::Entities::Market
|
||||
end
|
||||
|
||||
desc 'Create new market.' do
|
||||
success API::V2::Admin::Entities::Market
|
||||
end
|
||||
params do
|
||||
use :create_market_params
|
||||
requires :base_currency,
|
||||
values: { value: -> { ::Currency.ids }, message: 'admin.market.currency_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:base_unit][:desc] }
|
||||
requires :quote_currency,
|
||||
values: { value: -> { ::Currency.ids }, message: 'admin.market.currency_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:quote_unit][:desc] }
|
||||
requires :min_price,
|
||||
type: { value: BigDecimal, message: 'admin.market.non_decimal_min_price' },
|
||||
values: { value: -> (p){ p && p >= 0 }, message: 'admin.market.invalid_min_price' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:min_price][:desc] }
|
||||
requires :min_amount,
|
||||
type: { value: BigDecimal, message: 'admin.market.non_decimal_min_amount' },
|
||||
values: { value: -> (p){ p && p >= 0 }, message: 'admin.market.invalid_min_amount' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:min_amount][:desc] }
|
||||
optional :engine_id,
|
||||
type: { value: Integer, message: 'admin.market.non_integer_engine_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:engine_id][:desc] }
|
||||
optional :position,
|
||||
type: { value: Integer, message: 'admin.market.non_integer_position' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:position][:desc] }
|
||||
optional :engine_name,
|
||||
values: { value: -> { ::Engine.pluck(:name) }, message: 'admin.market.engine_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Engine.documentation[:name][:desc] }
|
||||
exactly_one_of :engine_id, :engine_name, message: 'admin.market.one_of_engine_id_engine_name_fields'
|
||||
end
|
||||
post '/markets/new' do
|
||||
admin_authorize! :create, ::Market
|
||||
|
||||
market = ::Market.new(declared(params, include_missing: false))
|
||||
if market.save
|
||||
present market, with: API::V2::Admin::Entities::Market
|
||||
status 201
|
||||
else
|
||||
body errors: market.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update market.' do
|
||||
success API::V2::Admin::Entities::Market
|
||||
end
|
||||
params do
|
||||
use :update_market_params
|
||||
requires :id,
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:id][:desc] }
|
||||
optional :engine_id,
|
||||
type: { value: Integer, message: 'admin.market.non_integer_engine_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:engine_id][:desc] }
|
||||
optional :position,
|
||||
type: { value: Integer, message: 'admin.market.non_integer_position' },
|
||||
values: { value: -> (p){ p >= ::Market::TOP_POSITION }, message: 'admin.market.invalid_position' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:position][:desc] }
|
||||
optional :min_price,
|
||||
type: { value: BigDecimal, message: 'admin.market.non_decimal_min_price' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.market.invalid_min_price' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:min_price][:desc] }
|
||||
optional :min_amount,
|
||||
type: { value: BigDecimal, message: 'admin.market.non_decimal_min_amount' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.market.invalid_min_amount' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:min_amount][:desc] }
|
||||
|
||||
end
|
||||
post '/markets/update' do
|
||||
admin_authorize! :update, ::Market
|
||||
|
||||
market = ::Market.find(params[:id])
|
||||
if market.update(declared(params, include_missing: false))
|
||||
present market, with: API::V2::Admin::Entities::Market
|
||||
else
|
||||
body errors: market.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
90
app/api/v2/admin/members.rb
Normal file
90
app/api/v2/admin/members.rb
Normal file
@@ -0,0 +1,90 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Members < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
desc 'Get all members, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Member
|
||||
params do
|
||||
optional :state,
|
||||
desc: 'Filter order by state.'
|
||||
optional :role,
|
||||
values: { value: -> { ::Ability.roles }, message: 'admin.member.invalid_role' }
|
||||
optional :group,
|
||||
values: { value: -> { ::Member.groups }, message: 'admin.member.invalid_group' }
|
||||
optional :email,
|
||||
desc: -> { API::V2::Entities::Member.documentation[:email][:desc] }
|
||||
use :uid
|
||||
use :date_picker
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/members' do
|
||||
admin_authorize! :read, Member
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:uid, :email, :state, :role, :group)
|
||||
.with_daterange
|
||||
.build
|
||||
|
||||
search = Member.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::Member
|
||||
end
|
||||
|
||||
desc 'Get available members groups.',
|
||||
is_array: true
|
||||
get '/members/groups' do
|
||||
admin_authorize! :read, Member
|
||||
|
||||
Member.groups
|
||||
end
|
||||
|
||||
desc 'Get a member.' do
|
||||
success API::V2::Admin::Entities::Member
|
||||
end
|
||||
params do
|
||||
requires :uid,
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
end
|
||||
get '/members/:uid' do
|
||||
admin_authorize! :read, Member
|
||||
|
||||
present Member.find_by!(uid: params[:uid]), with: API::V2::Admin::Entities::Member
|
||||
end
|
||||
|
||||
desc 'Set user group.' do
|
||||
success API::V2::Admin::Entities::Member
|
||||
end
|
||||
params do
|
||||
requires :uid,
|
||||
type: String,
|
||||
desc: 'The shared user ID.'
|
||||
requires :group,
|
||||
type: String,
|
||||
coerce_with: ->(v) { v.strip.downcase },
|
||||
desc: 'User gruop'
|
||||
end
|
||||
put '/members/:uid' do
|
||||
admin_authorize! :update, Member
|
||||
declared_params = declared(params)
|
||||
|
||||
member = Member.find_by!(uid: declared_params[:uid])
|
||||
if member.update(group: declared_params[:group])
|
||||
present member, with: API::V2::Admin::Entities::Member
|
||||
status 201
|
||||
else
|
||||
body errors: member.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
67
app/api/v2/admin/mount.rb
Normal file
67
app/api/v2/admin/mount.rb
Normal file
@@ -0,0 +1,67 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Mount < Grape::API
|
||||
PREFIX = '/admin'
|
||||
|
||||
before { authenticate! unless request.path == '/api/v2/admin/swagger' }
|
||||
|
||||
formatter :csv, CSVFormatter
|
||||
|
||||
mount Admin::Orders
|
||||
mount Admin::Blockchains
|
||||
mount Admin::Currencies
|
||||
mount Admin::Markets
|
||||
mount Admin::Wallets
|
||||
mount Admin::Deposits
|
||||
mount Admin::Withdraws
|
||||
mount Admin::Trades
|
||||
mount Admin::Operations
|
||||
mount Admin::Members
|
||||
mount Admin::TradingFees
|
||||
mount Admin::Adjustments
|
||||
mount Admin::Engines
|
||||
mount Admin::Beneficiaries
|
||||
mount Admin::Abilities
|
||||
mount Admin::WithdrawLimits
|
||||
mount Admin::DepositLimits
|
||||
mount Admin::Airdrops
|
||||
mount Admin::InternalTransfers
|
||||
mount Admin::WhitelistedSmartContracts
|
||||
|
||||
# The documentation is accessible at http://localhost:3000/swagger?url=/api/v2/admin/swagger
|
||||
# Add swagger documentation for Peatio Admin API
|
||||
add_swagger_documentation base_path: File.join(API::Mount::PREFIX, API::V2::Mount::API_VERSION, PREFIX, 'peatio'),
|
||||
add_base_path: true,
|
||||
mount_path: '/swagger',
|
||||
api_version: API::V2::Mount::API_VERSION,
|
||||
doc_version: Peatio::Application::VERSION,
|
||||
info: {
|
||||
title: "Peatio Admin API #{API::V2::Mount::API_VERSION}",
|
||||
description: 'Admin API high privileged API with RBAC.',
|
||||
contact_name: 'openware.com',
|
||||
contact_email: 'hello@openware.com',
|
||||
contact_url: 'https://www.openware.com',
|
||||
licence: 'MIT',
|
||||
license_url: 'https://github.com/openware/peatio/blob/master/LICENSE.md'
|
||||
},
|
||||
models: [
|
||||
API::V2::Admin::Entities::Blockchain,
|
||||
API::V2::Admin::Entities::Currency,
|
||||
API::V2::Admin::Entities::Deposit,
|
||||
API::V2::Admin::Entities::Market,
|
||||
API::V2::Admin::Entities::Member,
|
||||
API::V2::Admin::Entities::Operation,
|
||||
API::V2::Admin::Entities::Order,
|
||||
API::V2::Admin::Entities::Trade,
|
||||
API::V2::Admin::Entities::Wallet,
|
||||
API::V2::Admin::Entities::Withdraw,
|
||||
API::V2::Admin::Entities::InternalTransfer,
|
||||
API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
84
app/api/v2/admin/operations.rb
Normal file
84
app/api/v2/admin/operations.rb
Normal file
@@ -0,0 +1,84 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Operations < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
helpers do
|
||||
params :get_operations_params do
|
||||
optional :reference_type,
|
||||
desc: 'The reference type for which operation was created.'
|
||||
optional :rid,
|
||||
type: Integer,
|
||||
desc: 'The unique id of operation\'s reference, for which operation was created.'
|
||||
optional :code,
|
||||
type: Integer,
|
||||
desc: 'Opeartion\'s code.'
|
||||
use :currency
|
||||
use :date_picker
|
||||
end
|
||||
|
||||
def ransack_params
|
||||
Helpers::RansackBuilder.new(params)
|
||||
.eq(:code, :reference_type)
|
||||
.translate(currency: :currency_id, rid: :reference_id)
|
||||
.with_daterange
|
||||
.build
|
||||
end
|
||||
end
|
||||
|
||||
# GET: api/v2/admin/assets
|
||||
# GET: api/v2/admin/expenses
|
||||
# GET: api/v2/admin/revenues
|
||||
::Operations::Account::PLATFORM_TYPES.each do |op_type|
|
||||
op_type_plural = op_type.to_s.pluralize
|
||||
|
||||
desc "Returns #{op_type_plural} as a paginated collection." do
|
||||
success API::V2::Admin::Entities::Operation
|
||||
end
|
||||
params do
|
||||
use :get_operations_params
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get op_type_plural do
|
||||
klass = ::Operations.const_get(op_type.capitalize)
|
||||
admin_authorize! :read, klass
|
||||
|
||||
search = klass.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result, false), with: API::V2::Admin::Entities::Operation
|
||||
end
|
||||
end
|
||||
|
||||
# Get: api/v2/admin/liabilities
|
||||
::Operations::Account::MEMBER_TYPES.each do |op_type|
|
||||
op_type_plural = op_type.to_s.pluralize
|
||||
|
||||
desc "Returns #{op_type_plural} as a paginated collection." do
|
||||
success API::V2::Admin::Entities::Operation
|
||||
end
|
||||
params do
|
||||
use :uid
|
||||
use :get_operations_params
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get op_type_plural do
|
||||
klass = ::Operations.const_get(op_type.capitalize)
|
||||
admin_authorize! :read, klass
|
||||
|
||||
member = Member.find_by(uid: params[:uid]) if params[:uid].present?
|
||||
search = klass.ransack(ransack_params.merge(member_id_eq: member&.id))
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result, false), with: API::V2::Admin::Entities::Operation
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
122
app/api/v2/admin/orders.rb
Normal file
122
app/api/v2/admin/orders.rb
Normal file
@@ -0,0 +1,122 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Orders < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
helpers ::API::V2::OrderHelpers
|
||||
|
||||
content_type :csv, 'text/csv'
|
||||
|
||||
desc 'Get all orders, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Order
|
||||
params do
|
||||
optional :market,
|
||||
values: { value: -> { ::Market.ids }, message: 'admin.market.doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:id][:desc] }
|
||||
optional :state,
|
||||
values: { value: -> { ::Order.state.values }, message: 'admin.order.invalid_state' },
|
||||
desc: 'Filter order by state.'
|
||||
optional :ord_type,
|
||||
values: { value: ::Order::TYPES, message: 'admin.order.invalid_ord_type' },
|
||||
desc: 'Filter order by ord_type.'
|
||||
optional :price,
|
||||
type: { value: BigDecimal, message: 'admin.order.non_decimal_price' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'admin.order.non_positive_price' },
|
||||
desc: -> { API::V2::Admin::Entities::Order.documentation[:price][:desc] }
|
||||
optional :origin_volume,
|
||||
type: { value: BigDecimal, message: 'admin.order.non_decimal_price' },
|
||||
values: { value: -> (p){ p.try(:positive?) }, message: 'admin.order.non_positive_origin_volume' },
|
||||
desc: -> { API::V2::Admin::Entities::Order.documentation[:origin_volume][:desc] }
|
||||
optional :type,
|
||||
values: { value: %w(sell buy), message: 'admin.order.invalid_type' },
|
||||
desc: 'Filter order by type.'
|
||||
optional :email,
|
||||
desc: -> { API::V2::Entities::Member.documentation[:email][:desc] }
|
||||
use :uid
|
||||
use :date_picker
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/orders' do
|
||||
admin_authorize! :read, Order
|
||||
|
||||
if params[:uid].present? || params[:email].present?
|
||||
member = Member.find_by('uid = ? OR email = ?', params[:uid], params[:email])
|
||||
params.except!(:uid, :email).merge!(member_id: member.id) if member.present?
|
||||
end
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:price, :origin_volume, :ord_type, :state, :member_id)
|
||||
.translate(market: :market_id)
|
||||
.with_daterange
|
||||
.merge({
|
||||
type_eq: params[:type].present? ? params[:type] == 'buy' ? 'OrderBid' : 'OrderAsk' : nil
|
||||
}).build
|
||||
|
||||
search = Order.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
if params[:format] == 'csv'
|
||||
search.result
|
||||
else
|
||||
present paginate(search.result, false), with: API::V2::Admin::Entities::Order
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Cancel an order.'
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.order.non_integer_id' },
|
||||
allow_blank: false,
|
||||
desc: -> { API::V2::Admin::Entities::Order.documentation[:id][:desc] }
|
||||
end
|
||||
post '/orders/:id/cancel' do
|
||||
admin_authorize! :update, ::Order
|
||||
|
||||
begin
|
||||
order = Order.find(params[:id])
|
||||
order.trigger_cancellation
|
||||
present order, with: API::V2::Admin::Entities::Order
|
||||
rescue ActiveRecord::RecordNotFound => e
|
||||
# RecordNotFound in rescued by ExceptionsHandler.
|
||||
raise(e)
|
||||
rescue
|
||||
error!({ errors: ['admin.order.cancel_error'] }, 422)
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Cancel all orders.'
|
||||
params do
|
||||
requires :market,
|
||||
values: { value: -> { ::Market.active.ids }, message: 'admin.order.market_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Order.documentation[:id][:desc] }
|
||||
optional :side,
|
||||
values: { value: %w(sell buy), message: 'admin.order.invalid_side' },
|
||||
desc: 'If present, only sell orders (asks) or buy orders (bids) will be cancelled.'
|
||||
end
|
||||
post '/orders/cancel' do
|
||||
admin_authorize! :update, ::Order
|
||||
|
||||
begin
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(state: 'wait')
|
||||
.translate(market: :market_id)
|
||||
.merge({
|
||||
type_eq: params[:side].present? ? params[:side] == 'buy' ? 'OrderBid' : 'OrderAsk' : nil,
|
||||
}).build
|
||||
|
||||
orders = Order.ransack(ransack_params)
|
||||
orders.result.map(&:trigger_cancellation)
|
||||
present orders.result, with: API::V2::Entities::Order
|
||||
rescue
|
||||
error!({ errors: ['admin.order.cancel_error'] }, 422)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
68
app/api/v2/admin/trades.rb
Normal file
68
app/api/v2/admin/trades.rb
Normal file
@@ -0,0 +1,68 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'csv'
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Trades < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
content_type :csv, 'text/csv'
|
||||
|
||||
desc 'Get all trades, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Trade
|
||||
params do
|
||||
optional :market,
|
||||
values: { value: -> { ::Market.ids }, message: 'admin.market.doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Market.documentation[:id][:desc] }
|
||||
optional :order_id,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Entities::Order.documentation[:id][:desc] }
|
||||
use :uid
|
||||
use :date_picker
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/trades' do
|
||||
admin_authorize! :read, Trade
|
||||
|
||||
member = Member.find_by(uid: params[:uid]) if params[:uid].present?
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params.except!(:uid))
|
||||
.translate(market: :market_id)
|
||||
.with_daterange
|
||||
.merge(g: [
|
||||
{ maker_id_eq: member&.id, taker_id_eq: member&.id, m: 'or' },
|
||||
{ maker_order_id_eq: params[:order_id], taker_order_id_eq: params[:order_id], m: 'or' },
|
||||
]).build
|
||||
|
||||
search = Trade.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
if params[:format] == 'csv'
|
||||
search.result
|
||||
else
|
||||
present paginate(search.result, false), with: API::V2::Admin::Entities::Trade
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get a trade with detailed information.' do
|
||||
success API::V2::Admin::Entities::Blockchain
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.trade.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Trade.documentation[:id][:desc] }
|
||||
end
|
||||
get '/trades/:id' do
|
||||
admin_authorize! :read, Trade
|
||||
|
||||
present Trade.find(params[:id]), with: API::V2::Admin::Entities::Trade, extended: true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
125
app/api/v2/admin/trading_fees.rb
Normal file
125
app/api/v2/admin/trading_fees.rb
Normal file
@@ -0,0 +1,125 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class TradingFees < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
desc 'Returns trading_fees table as paginated collection',
|
||||
is_array: true,
|
||||
success: API::V2::Entities::TradingFee
|
||||
params do
|
||||
optional :group,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:group][:desc] },
|
||||
coerce_with: ->(c) { c.strip.downcase }
|
||||
optional :market_id,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:market_id][:desc] },
|
||||
values: { value: -> { ::Market.ids.append(::TradingFee::ANY) },
|
||||
message: 'admin.trading_fee.market_doesnt_exist' }
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/trading_fees' do
|
||||
admin_authorize! :read, TradingFee
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:group, :market_id)
|
||||
.build
|
||||
|
||||
search = TradingFee.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result), with: API::V2::Entities::TradingFee
|
||||
end
|
||||
|
||||
desc 'It creates trading fees record',
|
||||
success: API::V2::Entities::TradingFee
|
||||
params do
|
||||
requires :maker,
|
||||
type: { value: BigDecimal, message: 'admin.trading_fee.non_decimal_maker' },
|
||||
values: { value: -> (p){ p && p >= 0 }, message: 'admin.trading_fee.invalid_maker' },
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:maker][:desc] }
|
||||
requires :taker,
|
||||
type: { value: BigDecimal, message: 'admin.trading_fee.non_decimal_taker' },
|
||||
values: { value: -> (p){ p && p >= 0 }, message: 'admin.trading_fee.invalid_taker' },
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:taker][:desc] }
|
||||
optional :group,
|
||||
type: String,
|
||||
default: ::TradingFee::ANY,
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:group][:desc] }
|
||||
optional :market_id,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:market_id][:desc] },
|
||||
default: ::TradingFee::ANY,
|
||||
values: { value: -> { ::Market.ids.append(::TradingFee::ANY) },
|
||||
message: 'admin.trading_fee.market_doesnt_exist' }
|
||||
end
|
||||
post '/trading_fees/new' do
|
||||
admin_authorize! :create, TradingFee
|
||||
|
||||
trading_fee = ::TradingFee.new(declared(params))
|
||||
if trading_fee.save
|
||||
present trading_fee, with: API::V2::Entities::TradingFee
|
||||
status 201
|
||||
else
|
||||
body errors: trading_fee.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'It updates trading fees record',
|
||||
success: API::V2::Entities::TradingFee
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.trading_fee.non_integer_id' },
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:id][:desc] }
|
||||
optional :maker,
|
||||
type: { value: BigDecimal, message: 'admin.trading_fee.non_decimal_maker' },
|
||||
values: { value: -> (p){ p && p >= 0 }, message: 'admin.trading_fee.invalid_maker' },
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:maker][:desc] }
|
||||
optional :taker,
|
||||
type: { value: BigDecimal, message: 'admin.trading_fee.non_decimal_taker' },
|
||||
values: { value: -> (p){ p && p >= 0 }, message: 'admin.trading_fee.invalid_taker' },
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:taker][:desc] }
|
||||
optional :group,
|
||||
type: String,
|
||||
coerce_with: ->(c) { c.strip.downcase },
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:group][:desc] }
|
||||
optional :market_id,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:market_id][:desc] },
|
||||
values: { value: -> { ::Market.ids.append(::TradingFee::ANY) },
|
||||
message: 'admin.trading_fee.market_doesnt_exist' }
|
||||
end
|
||||
post '/trading_fees/update' do
|
||||
admin_authorize! :update, TradingFee
|
||||
|
||||
trading_fee = ::TradingFee.find(params[:id])
|
||||
if trading_fee.update(declared(params, include_missing: false))
|
||||
present trading_fee, with: API::V2::Entities::TradingFee
|
||||
else
|
||||
body errors: trading_fee.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'It deletes trading fees record',
|
||||
success: API::V2::Entities::TradingFee
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.trading_fee.non_integer_id' },
|
||||
desc: -> { API::V2::Entities::TradingFee.documentation[:id][:desc] }
|
||||
end
|
||||
post '/trading_fees/delete' do
|
||||
admin_authorize! :delete, TradingFee
|
||||
|
||||
present TradingFee.destroy(params[:id]), with: API::V2::Entities::TradingFee
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
249
app/api/v2/admin/wallets.rb
Normal file
249
app/api/v2/admin/wallets.rb
Normal file
@@ -0,0 +1,249 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Wallets < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
helpers do
|
||||
# Collection of shared params, used to
|
||||
# generate required/optional Grape params.
|
||||
OPTIONAL_WALLET_PARAMS ||= {
|
||||
max_balance: {
|
||||
type: { value: BigDecimal, message: 'admin.blockchain.non_decimal_max_balance' },
|
||||
values: { value: -> (p){ p >= 0 }, message: 'admin.wallet.invalid_max_balance' },
|
||||
default: 0.0,
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:max_balance][:desc] }
|
||||
},
|
||||
status: {
|
||||
values: { value: %w(active disabled), message: 'admin.wallet.invalid_status' },
|
||||
default: 'active',
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:status][:desc] }
|
||||
},
|
||||
}
|
||||
|
||||
params :create_wallet_params do
|
||||
OPTIONAL_WALLET_PARAMS.each do |key, params|
|
||||
optional key, params
|
||||
end
|
||||
end
|
||||
|
||||
params :update_wallet_params do
|
||||
OPTIONAL_WALLET_PARAMS.each do |key, params|
|
||||
optional key, params.except(:default)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get all wallets, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Wallet
|
||||
params do
|
||||
optional :blockchain_key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.currency.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:blockchain_key][:desc] }
|
||||
optional :kind,
|
||||
values: { value: -> { Wallet.kind.values }, message: 'admin.wallet.invalid_kind' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:kind][:desc] }
|
||||
optional :currencies,
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Currency.codes).blank? }, message: 'admin.wallet.currency_doesnt_exist' },
|
||||
types: [String, Array], coerce_with: ->(c) { Array.wrap(c) },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:currencies][:desc] }
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/wallets' do
|
||||
admin_authorize! :read, Wallet
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:blockchain_key)
|
||||
.translate_in(currencies: :currencies_id)
|
||||
.merge(kind_eq: params[:kind].present? ? Wallet.kinds[params[:kind].to_sym] : nil)
|
||||
.build
|
||||
|
||||
search = ::Wallet.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
present paginate(search.result.includes(:currencies).distinct), with: API::V2::Admin::Entities::Wallet
|
||||
end
|
||||
|
||||
desc 'List wallet kinds.'
|
||||
get '/wallets/kinds' do
|
||||
::Wallet.kind.values
|
||||
end
|
||||
|
||||
desc 'List wallet gateways.'
|
||||
get '/wallets/gateways' do
|
||||
::Wallet.gateways.map(&:to_s)
|
||||
end
|
||||
|
||||
desc 'Get a wallet.' do
|
||||
success API::V2::Admin::Entities::Wallet
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.wallet.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:id][:desc] }
|
||||
end
|
||||
get '/wallets/:id' do
|
||||
admin_authorize! :read, Wallet
|
||||
|
||||
present ::Wallet.find(params[:id]), with: API::V2::Admin::Entities::Wallet
|
||||
end
|
||||
|
||||
desc 'Creates new wallet.' do
|
||||
success API::V2::Admin::Entities::Wallet
|
||||
end
|
||||
params do
|
||||
use :create_wallet_params
|
||||
requires :blockchain_key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.wallet.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:blockchain_key][:desc] }
|
||||
requires :name,
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:name][:desc] }
|
||||
optional :address,
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:address][:desc] }
|
||||
optional :currencies,
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Currency.codes).blank? }, message: 'admin.wallet.currency_doesnt_exist' },
|
||||
types: [String, Array], coerce_with: ->(c) { Array.wrap(c) },
|
||||
as: :currency_ids,
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:currencies][:desc] }
|
||||
# @deprecated Please use `currencies` field
|
||||
optional :currency,
|
||||
values: { value: -> { ::Currency.codes }, message: 'admin.wallet.currency_doesnt_exist' },
|
||||
as: :currency_ids,
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:currencies][:desc] }
|
||||
requires :kind,
|
||||
values: { value: ::Wallet.kind.values, message: 'admin.wallet.invalid_kind' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:kind][:desc] }
|
||||
requires :gateway,
|
||||
values: { value: -> { ::Wallet.gateways.map(&:to_s) }, message: 'admin.wallet.gateway_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:gateway][:desc] }
|
||||
optional :settings, type: JSON,
|
||||
default: {},
|
||||
desc: -> { 'Wallet settings (uri, secret)' } do
|
||||
optional :uri,
|
||||
values: { value: ->(v) { URI.parse(v).is_a?(URI::HTTP) || URI.parse(v).is_a?(URI::HTTPS) }, message: 'admin.wallet.invalid_uri_setting' },
|
||||
desc: -> { 'Wallet uri setting' }
|
||||
optional :secret,
|
||||
desc: -> { 'Wallet secret setting' }
|
||||
end
|
||||
exactly_one_of :currencies, :currency, message: 'admin.wallet.currencies_field_is_missing'
|
||||
end
|
||||
post '/wallets/new' do
|
||||
admin_authorize! :create, Wallet
|
||||
|
||||
wallet = ::Wallet.new(declared(params))
|
||||
if wallet.save
|
||||
present wallet, with: API::V2::Admin::Entities::Wallet
|
||||
status 201
|
||||
else
|
||||
body errors: wallet.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update wallet.' do
|
||||
success API::V2::Admin::Entities::Wallet
|
||||
end
|
||||
params do
|
||||
use :update_wallet_params
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.wallet.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:id][:desc] }
|
||||
optional :blockchain_key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.wallet.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:blockchain_key][:desc] }
|
||||
optional :name,
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:name][:desc] }
|
||||
optional :address,
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:address][:desc] }
|
||||
optional :kind,
|
||||
values: { value: ::Wallet.kind.values, message: 'admin.wallet.invalid_kind' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:kind][:desc] }
|
||||
optional :gateway,
|
||||
values: { value: -> { ::Wallet.gateways.map(&:to_s) }, message: 'admin.wallet.gateway_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:gateway][:desc] }
|
||||
optional :currencies,
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Currency.codes).blank? }, message: 'admin.wallet.currency_doesnt_exist' },
|
||||
types: [String, Array], coerce_with: ->(c) { Array.wrap(c) },
|
||||
as: :currency_ids,
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:currencies][:desc] }
|
||||
optional :settings, type: JSON,
|
||||
desc: -> { 'Wallet settings' } do
|
||||
optional :uri,
|
||||
values: { value: ->(v) { URI.parse(v).is_a?(URI::HTTP) || URI.parse(v).is_a?(URI::HTTPS) }, message: 'admin.wallet.invalid_uri_setting' },
|
||||
desc: -> { 'Wallet uri setting' }
|
||||
optional :secret,
|
||||
desc: -> { 'Wallet secret setting' }
|
||||
end
|
||||
end
|
||||
post '/wallets/update' do
|
||||
admin_authorize! :update, Wallet
|
||||
wallet = ::Wallet.find(params[:id])
|
||||
|
||||
declared_params = declared(params, include_missing: false)
|
||||
declared_params.merge!(settings: params[:settings]) if params[:settings].present?
|
||||
if wallet.update(declared_params)
|
||||
present wallet, with: API::V2::Admin::Entities::Wallet
|
||||
else
|
||||
body errors: wallet.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Add currency to the wallet' do
|
||||
success API::V2::Admin::Entities::Wallet
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.wallet.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:id][:desc] }
|
||||
requires :currencies,
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Currency.codes).blank? }, message: 'admin.wallet.currency_doesnt_exist' },
|
||||
types: [String, Array], coerce_with: ->(c) { Array.wrap(c) },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:currencies][:desc] }
|
||||
end
|
||||
post '/wallets/currencies' do
|
||||
wallet = Wallet.find(params[:id])
|
||||
|
||||
wallet.transaction do
|
||||
params[:currencies].each do |c_id|
|
||||
c_w = CurrencyWallet.new(currency_id: c_id, wallet_id: params[:id])
|
||||
error!({ errors: c_w.errors.full_messages }, 422) unless c_w.save
|
||||
end
|
||||
end
|
||||
|
||||
present wallet, with: API::V2::Admin::Entities::Wallet
|
||||
status 201
|
||||
end
|
||||
|
||||
desc 'Delete currency from the wallet' do
|
||||
success API::V2::Admin::Entities::Wallet
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.wallet.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:id][:desc] }
|
||||
requires :currencies,
|
||||
values: { value: ->(v) { (Array.wrap(v) - ::Currency.codes).blank? }, message: 'admin.wallet.currency_doesnt_exist' },
|
||||
types: [String, Array], coerce_with: ->(c) { Array.wrap(c) },
|
||||
desc: -> { API::V2::Admin::Entities::Wallet.documentation[:currencies][:desc] }
|
||||
end
|
||||
delete '/wallets/currencies' do
|
||||
wallet = Wallet.find(params[:id])
|
||||
wallet.transaction do
|
||||
params[:currencies].each do |c_id|
|
||||
# Check if exist (will return error)
|
||||
CurrencyWallet.find_by!(currency_id: c_id, wallet_id: params[:id])
|
||||
# Delete relation
|
||||
CurrencyWallet.where(currency_id: c_id, wallet_id: params[:id]).delete_all
|
||||
end
|
||||
end
|
||||
|
||||
present wallet, with: API::V2::Admin::Entities::Wallet
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
137
app/api/v2/admin/whitelisted_smart_contracts.rb
Normal file
137
app/api/v2/admin/whitelisted_smart_contracts.rb
Normal file
@@ -0,0 +1,137 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class WhitelistedSmartContracts < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
content_type :csv, 'text/csv'
|
||||
|
||||
desc 'Get all whitelisted addresses, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
params do
|
||||
optional :blockchain_key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.whitelistedsmartcontract.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:blockchain_key][:desc] }
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/whitelisted_smart_contracts' do
|
||||
admin_authorize! :read, WhitelistedSmartContract
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params).eq(:blockchain_key).build
|
||||
|
||||
search = ::WhitelistedSmartContract.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
if params[:format] == 'csv'
|
||||
search.result
|
||||
else
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get a whitelisted address.' do
|
||||
success API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.whitelistedsmartcontract.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:id][:desc] }
|
||||
end
|
||||
get '/whitelisted_smart_contract/:id' do
|
||||
admin_authorize! :read, WhitelistedSmartContract
|
||||
|
||||
present ::WhitelistedSmartContract.find(params[:id]), with: API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
end
|
||||
|
||||
desc 'Creates new whitelisted address.' do
|
||||
success API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
end
|
||||
params do
|
||||
requires :blockchain_key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.whitelistedsmartcontract.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:blockchain_key][:desc] }
|
||||
requires :address,
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:address][:desc] }
|
||||
optional :description,
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:description][:desc] }
|
||||
optional :state,
|
||||
values: { value: %w[active disabled], message: 'admin.whitelistedsmartcontract.invalid_state' },
|
||||
default: 'active',
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:state][:desc] }
|
||||
end
|
||||
post '/whitelisted_smart_contracts' do
|
||||
admin_authorize! :create, WhitelistedSmartContract
|
||||
|
||||
whitelisted_address = ::WhitelistedSmartContract.new(declared(params))
|
||||
if whitelisted_address.save
|
||||
present whitelisted_address, with: API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
else
|
||||
body errors: whitelisted_address.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update whitelisted_smart_contract.' do
|
||||
success API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
end
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.whitelistedsmartcontract.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:id][:desc] }
|
||||
optional :blockchain_key,
|
||||
values: { value: -> { ::Blockchain.pluck(:key) }, message: 'admin.whitelistedsmartcontract.blockchain_key_doesnt_exist' },
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:blockchain_key][:desc] }
|
||||
optional :description,
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:description][:desc] }
|
||||
optional :address,
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:address][:desc] }
|
||||
optional :state,
|
||||
values: { value: %w[active disabled], message: 'admin.whitelistedsmartcontract.invalid_state' },
|
||||
desc: -> { API::V2::Admin::Entities::WhitelistedSmartContract.documentation[:state][:desc] }
|
||||
end
|
||||
put '/whitelisted_smart_contracts' do
|
||||
admin_authorize! :update, WhitelistedSmartContract
|
||||
whitelisted_address = ::WhitelistedSmartContract.find(params[:id])
|
||||
declared_params = declared(params, include_missing: false)
|
||||
|
||||
if whitelisted_address.update(declared_params)
|
||||
present whitelisted_address, with: API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
else
|
||||
body errors: whitelisted_address.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Process whitelisted smart contracts from csv' do
|
||||
success API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
end
|
||||
params do
|
||||
requires :file,
|
||||
type: File,
|
||||
desc: -> {'CSV file with whitelisted smart contracts data'}
|
||||
end
|
||||
post '/whitelisted_smart_contracts/csv' do
|
||||
admin_authorize! :create, WhitelistedSmartContract
|
||||
count = 0
|
||||
|
||||
CSV.parse(params[:file][:tempfile], headers: true, quote_empty: false).each do |row|
|
||||
row = row.to_h.compact.symbolize_keys!
|
||||
address = row[:address]
|
||||
blockchain_key = row[:blockchain_key]
|
||||
description = row[:description]
|
||||
next if address.blank? || blockchain_key.blank? || ::Blockchain.pluck(:key).exclude?(blockchain_key)
|
||||
|
||||
::WhitelistedSmartContract.create!(description: description, address: address,
|
||||
blockchain_key: blockchain_key, state: 'active')
|
||||
count += 1
|
||||
end
|
||||
|
||||
present ::WhitelistedSmartContract.last(count), with: API::V2::Admin::Entities::WhitelistedSmartContract
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
127
app/api/v2/admin/withdraw_limits.rb
Normal file
127
app/api/v2/admin/withdraw_limits.rb
Normal file
@@ -0,0 +1,127 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class WithdrawLimits < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
desc 'Returns withdraw limits table as paginated collection',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::WithdrawLimit
|
||||
params do
|
||||
optional :group,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:group][:desc] },
|
||||
coerce_with: ->(c) { c.strip.downcase }
|
||||
optional :kyc_level,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:kyc_level][:desc] }
|
||||
optional :kind,
|
||||
values: { value: ->(v) { (Array.wrap(v) - WithdrawLimit.kind.values).blank? }, message: 'limit.kind.invalid' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:kind][:desc] }
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/withdraw_limits' do
|
||||
admin_authorize! :read, WithdrawLimit
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:group, :kyc_level, :kind)
|
||||
.build
|
||||
|
||||
search = WithdrawLimit.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
present paginate(search.result), with: API::V2::Entities::WithdrawLimit
|
||||
end
|
||||
|
||||
desc 'It creates withdraw limits record',
|
||||
success: API::V2::Entities::WithdrawLimit
|
||||
params do
|
||||
requires :limit_24_hour,
|
||||
type: { value: BigDecimal, message: 'admin.withdraw_limit.non_decimal_limit_24_hour' },
|
||||
values: { value: ->(p) { p && p >= 0 }, message: 'admin.withdraw_limit.invalid_limit_24_hour' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:limit_24_hour][:desc] }
|
||||
requires :limit_1_month,
|
||||
type: { value: BigDecimal, message: 'admin.withdraw_limit.non_decimal_limit_1_month' },
|
||||
values: { value: ->(p) { p && p >= 0 }, message: 'admin.withdraw_limit.invalid_limit_1_month' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:limit_1_month][:desc] }
|
||||
requires :kind,
|
||||
values: { value: ->(v) { (Array.wrap(v) - WithdrawLimit.kind.values).blank? }, message: 'limit.kind.invalid' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:kind][:desc] }
|
||||
optional :group,
|
||||
type: String,
|
||||
default: ::WithdrawLimit::ANY,
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:group][:desc] }
|
||||
optional :kyc_level,
|
||||
type: String,
|
||||
default: ::WithdrawLimit::ANY,
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:kyc_level][:desc] }
|
||||
end
|
||||
post '/withdraw_limits' do
|
||||
admin_authorize! :create, WithdrawLimit
|
||||
|
||||
withdraw_limit = ::WithdrawLimit.new(declared(params))
|
||||
if withdraw_limit.save
|
||||
present withdraw_limit, with: API::V2::Entities::WithdrawLimit
|
||||
status 201
|
||||
else
|
||||
body errors: withdraw_limit.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'It updates withdraw limits record',
|
||||
success: API::V2::Entities::WithdrawLimit
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.withdraw_limit.non_integer_id' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:id][:desc] }
|
||||
optional :limit_24_hour,
|
||||
type: { value: BigDecimal, message: 'admin.withdraw_limit.non_decimal_limit_24_hour' },
|
||||
values: { value: ->(p) { p && p >= 0 }, message: 'admin.withdraw_limit.invalid_limit_24_hour' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:limit_24_hour][:desc] }
|
||||
optional :limit_1_month,
|
||||
type: { value: BigDecimal, message: 'admin.withdraw_limit.non_decimal_limit_1_month' },
|
||||
values: { value: ->(p) { p && p >= 0 }, message: 'admin.withdraw_limit.invalid_limit_1_month' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:limit_1_month][:desc] }
|
||||
optional :kind,
|
||||
values: { value: ->(v) { (Array.wrap(v) - WithdrawLimit.kind.values).blank? }, message: 'limit.kind.invalid' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:kind][:desc] }
|
||||
optional :kyc_level,
|
||||
type: String,
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:kyc_level][:desc] }
|
||||
optional :group,
|
||||
type: String,
|
||||
coerce_with: ->(c) { c.strip.downcase },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:group][:desc] }
|
||||
end
|
||||
put '/withdraw_limits' do
|
||||
admin_authorize! :update, WithdrawLimit
|
||||
|
||||
withdraw_limit = ::WithdrawLimit.find(params[:id])
|
||||
if withdraw_limit.update(declared(params, include_missing: false))
|
||||
present withdraw_limit, with: API::V2::Entities::WithdrawLimit
|
||||
else
|
||||
body errors: withdraw_limit.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'It deletes withdraw limits record',
|
||||
success: API::V2::Entities::WithdrawLimit
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.withdraw_limit.non_integer_id' },
|
||||
desc: -> { API::V2::Entities::WithdrawLimit.documentation[:id][:desc] }
|
||||
end
|
||||
delete '/withdraw_limits/:id' do
|
||||
admin_authorize! :delete, WithdrawLimit
|
||||
|
||||
present WithdrawLimit.destroy(params[:id]), with: API::V2::Entities::WithdrawLimit
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
149
app/api/v2/admin/withdraws.rb
Normal file
149
app/api/v2/admin/withdraws.rb
Normal file
@@ -0,0 +1,149 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Admin
|
||||
class Withdraws < Grape::API
|
||||
helpers ::API::V2::Admin::Helpers
|
||||
|
||||
desc 'Get all withdraws, result is paginated.',
|
||||
is_array: true,
|
||||
success: API::V2::Admin::Entities::Withdraw
|
||||
params do
|
||||
optional :state,
|
||||
values: { value: ->(v) { (Array.wrap(v) - Withdraw::STATES.map(&:to_s)).blank? }, message: 'admin.withdraw.invalid_state' },
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:state][:desc] }
|
||||
optional :id,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:id][:desc] }
|
||||
optional :txid,
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:blockchain_txid][:desc] }
|
||||
optional :tid,
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:tid][:desc] }
|
||||
optional :confirmations,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:confirmations][:desc] }
|
||||
optional :rid,
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:rid][:desc] }
|
||||
optional :wallet_type,
|
||||
values: { value: ->(v) { (Array.wrap(v.to_sym) - Wallet.gateways).blank? }, message: 'admin.withdraw.invalid_wallet_type' },
|
||||
desc: -> { 'Select withdraw that can be processed from wallets with given type e.g. patiry' }
|
||||
use :uid
|
||||
use :currency
|
||||
use :currency_type
|
||||
use :date_picker
|
||||
use :pagination
|
||||
use :ordering
|
||||
end
|
||||
get '/withdraws' do
|
||||
admin_authorize! :read, Withdraw
|
||||
|
||||
ransack_params = Helpers::RansackBuilder.new(params)
|
||||
.eq(:id, :txid, :rid, :tid)
|
||||
.translate(uid: :member_uid, currency: :currency_id)
|
||||
.with_daterange
|
||||
.merge(type_eq: params[:type].present? ? "Withdraws::#{params[:type].capitalize}" : nil)
|
||||
.merge(aasm_state_in: params[:state])
|
||||
.build
|
||||
|
||||
search = Withdraw.ransack(ransack_params)
|
||||
search.sorts = "#{params[:order_by]} #{params[:ordering]}"
|
||||
|
||||
if params[:wallet_type].present?
|
||||
present paginate(search.result
|
||||
.where(currency: Currency.joins(:wallets)
|
||||
.where(wallets: { id: Wallet.where(gateway: params[:wallet_type]) }))),
|
||||
with: API::V2::Admin::Entities::Withdraw
|
||||
else
|
||||
present paginate(search.result), with: API::V2::Admin::Entities::Withdraw
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Get withdraw by ID.',
|
||||
success: API::V2::Admin::Entities::Withdraw
|
||||
params do
|
||||
requires :id,
|
||||
type: { value: Integer, message: 'admin.withdraw.non_integer_id' },
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:id][:desc] }
|
||||
end
|
||||
get '/withdraws/:id' do
|
||||
admin_authorize! :read, Withdraw
|
||||
|
||||
withdraw = Withdraw.find_by!(id: params[:id])
|
||||
present withdraw,
|
||||
with: API::V2::Admin::Entities::Withdraw,
|
||||
with_beneficiary: true
|
||||
end
|
||||
|
||||
desc 'Take an action on the withdrawal.',
|
||||
success: API::V2::Admin::Entities::Withdraw
|
||||
params do
|
||||
requires :id,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:id][:desc] }
|
||||
requires :action,
|
||||
type: String,
|
||||
values: { value: -> { ::Withdraw.aasm.events.map(&:name).map(&:to_s) }, message: 'admin.withdraw.invalid_action' },
|
||||
desc: "Valid actions are #{::Withdraw.aasm.events.map(&:name)}."
|
||||
given action: ->(action) { %w[load dispatch success].include?(action) } do
|
||||
optional :txid,
|
||||
type: String,
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:blockchain_txid][:desc] }
|
||||
end
|
||||
end
|
||||
post '/withdraws/actions' do
|
||||
admin_authorize! :update, Withdraw
|
||||
|
||||
declared_params = declared(params, include_missing: false)
|
||||
|
||||
withdraw = Withdraw.find(declared_params[:id])
|
||||
|
||||
if withdraw.currency.fiat? && declared_params[:txid].present?
|
||||
error!({ errors: ['admin.withdraw.redundant_txid'] }, 422)
|
||||
end
|
||||
|
||||
transited = withdraw.transaction do
|
||||
withdraw.update!(txid: declared_params[:txid]) if declared_params[:txid].present?
|
||||
withdraw.public_send("#{declared_params[:action]}!").tap do |success|
|
||||
raise ActiveRecord::Rollback unless success
|
||||
end
|
||||
rescue StandardError
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
|
||||
if transited
|
||||
present withdraw, with: API::V2::Admin::Entities::Withdraw
|
||||
else
|
||||
body errors: ["admin.withdraw.cannot_#{declared_params[:action]}"]
|
||||
status 422
|
||||
end
|
||||
end
|
||||
|
||||
desc 'Update withdraw request',
|
||||
success: API::V2::Admin::Entities::Withdraw
|
||||
params do
|
||||
requires :id,
|
||||
type: Integer,
|
||||
desc: -> { API::V2::Admin::Entities::Withdraw.documentation[:id][:desc] }
|
||||
optional :metadata,
|
||||
type: JSON,
|
||||
desc: 'Optional metadata to be applied to the transaction.'
|
||||
end
|
||||
put '/withdraws' do
|
||||
admin_authorize! :update, Withdraw
|
||||
|
||||
declared_params = declared(params, include_missing: false)
|
||||
withdraw = Withdraw.find(declared_params[:id])
|
||||
|
||||
declared_params[:metadata] = withdraw.metadata.merge(declared_params[:metadata]) if declared_params[:metadata].present?
|
||||
if withdraw.update(declared_params)
|
||||
present withdraw, with: API::V2::Admin::Entities::Withdraw
|
||||
else
|
||||
body errors: withdraw.errors.full_messages
|
||||
status 422
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
17
app/api/v2/auth/utils.rb
Normal file
17
app/api/v2/auth/utils.rb
Normal file
@@ -0,0 +1,17 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Auth
|
||||
module Utils
|
||||
class << self
|
||||
def cache
|
||||
# Simply use rack-attack cache wrapper
|
||||
@cache ||= Rack::Attack::Cache.new
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
49
app/api/v2/coin_gecko/entities/orderbook.rb
Normal file
49
app/api/v2/coin_gecko/entities/orderbook.rb
Normal file
@@ -0,0 +1,49 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
module Entities
|
||||
class Orderbook < API::V2::Entities::Base
|
||||
expose(
|
||||
:ticker_id,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'A pair such as "BTC_ETH", with delimiter between different cryptoassets.'
|
||||
}
|
||||
) do |orderbook|
|
||||
orderbook.market.underscore_name
|
||||
end
|
||||
|
||||
expose(
|
||||
:timestamp,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Unix timestamp in milliseconds for when the last updated time occurred'
|
||||
}
|
||||
) do
|
||||
DateTime.now.strftime('%Q').to_i
|
||||
end
|
||||
|
||||
expose(
|
||||
:asks,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
is_array: true,
|
||||
desc: 'An array containing 2 elements. The offer price and quantity for each bid order.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:bids,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
is_array: true,
|
||||
desc: 'An array containing 2 elements. The ask price and quantity for each ask order.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
41
app/api/v2/coin_gecko/entities/pair.rb
Normal file
41
app/api/v2/coin_gecko/entities/pair.rb
Normal file
@@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
module Entities
|
||||
class Pair < API::V2::Entities::Base
|
||||
expose(
|
||||
:ticker_id,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Identifier of a ticker with delimiter to separate base/target, eg. BTC_ETH.'
|
||||
}
|
||||
) do |market|
|
||||
market.underscore_name
|
||||
end
|
||||
|
||||
expose(
|
||||
:base,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Symbol/currency code of a the base cryptoasset, eg. BTC.'
|
||||
}
|
||||
) do |market|
|
||||
market[:base_unit].upcase
|
||||
end
|
||||
|
||||
expose(
|
||||
:target,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Symbol/currency code of the target cryptoasset, eg. ETH.'
|
||||
}
|
||||
) do |market|
|
||||
market[:quote_unit].upcase
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
111
app/api/v2/coin_gecko/entities/ticker.rb
Normal file
111
app/api/v2/coin_gecko/entities/ticker.rb
Normal file
@@ -0,0 +1,111 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
module Entities
|
||||
class Ticker < API::V2::Entities::Base
|
||||
expose(
|
||||
:ticker_id,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Identifier of a ticker with delimiter to separate base/target, eg. BTC_ETH.'
|
||||
}
|
||||
) do |ticker|
|
||||
ticker[:market].underscore_name
|
||||
end
|
||||
|
||||
expose(
|
||||
:base_currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Symbol/currency code of base pair, eg. BTC.'
|
||||
}
|
||||
) do |ticker|
|
||||
ticker[:market][:base_unit].upcase
|
||||
end
|
||||
|
||||
expose(
|
||||
:target_currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Symbol/currency code of target pair, eg. ETH.'
|
||||
}
|
||||
) do |ticker|
|
||||
ticker[:market][:quote_unit].upcase
|
||||
end
|
||||
|
||||
expose(
|
||||
:last_price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Last transacted price of base currency based on given target currency.'
|
||||
}
|
||||
) do |ticker|
|
||||
ticker[:last]
|
||||
end
|
||||
|
||||
expose(
|
||||
:base_volume,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: '24 hour trading volume in base pair volume.'
|
||||
}
|
||||
) do |ticker|
|
||||
ticker[:amount]
|
||||
end
|
||||
|
||||
expose(
|
||||
:target_volume,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: '24 hour trading volume in base pair volume.'
|
||||
}
|
||||
) do |ticker|
|
||||
ticker[:volume]
|
||||
end
|
||||
|
||||
expose(
|
||||
:bid,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Current highest bid price.'
|
||||
}
|
||||
) do |ticker|
|
||||
OrderBid.get_depth(ticker[:market].id).flatten.first.to_d
|
||||
end
|
||||
|
||||
expose(
|
||||
:ask,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Current lowest ask price.'
|
||||
}
|
||||
) do |ticker|
|
||||
OrderAsk.get_depth(ticker[:market].id).flatten.first.to_d
|
||||
end
|
||||
|
||||
expose(
|
||||
:high,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'The highest trade price during last 24 hours (0.0 if no trades executed during last 24 hours).'
|
||||
}
|
||||
) do |ticker|
|
||||
ticker[:high]
|
||||
end
|
||||
|
||||
expose(
|
||||
:low,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'The lowest trade price during last 24 hours (0.0 if no trades executed during last 24 hours).'
|
||||
}
|
||||
) do |ticker|
|
||||
ticker[:low]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
22
app/api/v2/coin_gecko/helpers.rb
Normal file
22
app/api/v2/coin_gecko/helpers.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
module Helpers
|
||||
MILLISECONDS_IN_SECOND = 1000
|
||||
|
||||
def format_trade(trade)
|
||||
{
|
||||
trade_id: trade[:id],
|
||||
price: trade[:price],
|
||||
base_volume: trade[:amount],
|
||||
target_volume: trade[:total],
|
||||
trade_timestamp: trade[:created_at] * MILLISECONDS_IN_SECOND,
|
||||
type: trade[:taker_type]
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
43
app/api/v2/coin_gecko/historical_trades.rb
Normal file
43
app/api/v2/coin_gecko/historical_trades.rb
Normal file
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
class HistoricalTrades < Grape::API
|
||||
desc 'Get recent trades on market'
|
||||
params do
|
||||
requires :ticker_id,
|
||||
type: String,
|
||||
desc: 'A pair such as "LTC_BTC"',
|
||||
coerce_with: ->(name) { name.strip.split('_').join.downcase }
|
||||
optional :type,
|
||||
type: String,
|
||||
values: { value: %w(buy sell), message: 'coingecko.historical_trades.invalid_type' },
|
||||
desc: 'To indicate nature of trade - buy/sell'
|
||||
optional :limit,
|
||||
type: Integer,
|
||||
values: { value: 0..1000, message: 'coingecko.historical_trades.invalid_limit' },
|
||||
desc: 'Number of historical trades to retrieve from time of query. [0, 200, 500...]. 0 returns full history'
|
||||
optional :start_time,
|
||||
type: Integer,
|
||||
desc: '',
|
||||
coerce_with: ->(start_time) { Time.parse(start_time).to_i }
|
||||
optional :end_time,
|
||||
type: Integer,
|
||||
desc: '',
|
||||
coerce_with: ->(end_time) { Time.parse(end_time).to_i }
|
||||
end
|
||||
get '/historical_trades' do
|
||||
market = ::Market.find(params[:ticker_id])
|
||||
|
||||
filters = declared(params, include_missing: false)
|
||||
.except(:ticker_id, :limit)
|
||||
|
||||
Trade.public_from_influx(market.id, params[:limit], filters).each_with_object({'buy' => [], 'sell' => []}) do |trade, hash|
|
||||
hash[trade[:taker_type]] << format_trade(trade)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
20
app/api/v2/coin_gecko/mount.rb
Normal file
20
app/api/v2/coin_gecko/mount.rb
Normal file
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
class Mount < Grape::API
|
||||
PREFIX = '/coingecko'
|
||||
|
||||
before { set_ets_context! }
|
||||
|
||||
helpers CoinGecko::Helpers
|
||||
|
||||
mount CoinGecko::Pairs
|
||||
mount CoinGecko::Tickers
|
||||
mount CoinGecko::Orderbook
|
||||
mount CoinGecko::HistoricalTrades
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
39
app/api/v2/coin_gecko/orderbook.rb
Normal file
39
app/api/v2/coin_gecko/orderbook.rb
Normal file
@@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
class Orderbook < Grape::API
|
||||
class OrderBook < Struct.new(:market, :asks, :bids); end
|
||||
|
||||
desc 'Get depth or specified market'
|
||||
params do
|
||||
requires :ticker_id,
|
||||
type: String,
|
||||
desc: 'A pair such as "LTC_BTC"',
|
||||
coerce_with: ->(name) { name.strip.split('_').join.downcase }
|
||||
optional :depth,
|
||||
type: { value: Integer, message: 'coingecko.market_depth.non_integer_depth' },
|
||||
values: { value: 0..1000, message: 'coingecko.market_depth.invalid_depth' },
|
||||
desc: 'Orders depth quantity: [0, 100, 200, 500...]'
|
||||
end
|
||||
|
||||
get '/orderbook' do
|
||||
market = ::Market.find(params[:ticker_id])
|
||||
asks = OrderAsk.get_depth(market.id)
|
||||
bids = OrderBid.get_depth(market.id)
|
||||
|
||||
# Depth = 100 means 50 for each bid/ask side
|
||||
# Not defined or 0 = full order book
|
||||
unless params[:depth].to_d.zero?
|
||||
asks = asks[0, params[:depth]/2]
|
||||
bids = bids[0, params[:depth]/2]
|
||||
end
|
||||
|
||||
orderbook = OrderBook.new market, asks, bids
|
||||
present orderbook, with: API::V2::CoinGecko::Entities::Orderbook
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
16
app/api/v2/coin_gecko/pairs.rb
Normal file
16
app/api/v2/coin_gecko/pairs.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
class Pairs < Grape::API
|
||||
desc 'Get list of all available trading pairs'
|
||||
get "/pairs" do
|
||||
present ::Rails.cache.fetch(:markets_coingecko, expires_in: 60) { ::Market.enabled.ordered },
|
||||
with: API::V2::CoinGecko::Entities::Pair
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
21
app/api/v2/coin_gecko/tickers.rb
Normal file
21
app/api/v2/coin_gecko/tickers.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinGecko
|
||||
class Tickers < Grape::API
|
||||
desc 'Get list of all available trading pairs'
|
||||
get "/tickers" do
|
||||
tickers = ::Rails.cache.fetch(:markets_tickers_coingecko, expires_in: 60) do
|
||||
::Market.enabled.ordered.inject([]) do |hash, market|
|
||||
hash << TickersService[market].ticker.merge(market: market)
|
||||
end
|
||||
end
|
||||
|
||||
present tickers, with: API::V2::CoinGecko::Entities::Ticker
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
16
app/api/v2/coin_market_cap/assets.rb
Normal file
16
app/api/v2/coin_market_cap/assets.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinMarketCap
|
||||
class Assets < Grape::API
|
||||
desc 'Details on crypto currencies available on the exchange'
|
||||
get '/assets' do
|
||||
::Rails.cache.fetch(:currencies_cmc, expires_in: 600) do
|
||||
format_currencies(Currency.visible.coins.ordered.map)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
93
app/api/v2/coin_market_cap/helpers.rb
Normal file
93
app/api/v2/coin_market_cap/helpers.rb
Normal file
@@ -0,0 +1,93 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinMarketCap
|
||||
module Helpers
|
||||
MILLISECONDS_IN_SECOND = 1000
|
||||
|
||||
def format_summary(ticker, market)
|
||||
lowest_ask = OrderAsk.get_depth(market.id)
|
||||
highest_bid = OrderBid.get_depth(market.id)
|
||||
{
|
||||
trading_pairs: market.underscore_name, # mandatory [string]: Identifier of a ticker with delimiter to separate base/quote
|
||||
base_currency: market.base_unit.upcase, # recommended [string]: Symbol/currency code of base currency
|
||||
quote_currency: market.quote_unit.upcase, # recommended [string]: Symbol/currency code of base currency
|
||||
last_price: ticker[:last], # mandatory [decimal]: Last transacted price of base currency based on given quote currency
|
||||
lowest_ask: lowest_ask.flatten.first.to_d, # mandatory [decimal]: Lowest Ask price of base currency based on given quote currency
|
||||
highest_bid: highest_bid.flatten.first.to_d, # mandatory [decimal]: Highest bid price of base currency based on given quote currency
|
||||
base_volume: ticker[:amount], # mandatory [decimal]: 24-hr volume of market pair denoted in BASE currency
|
||||
quote_volume: ticker[:volume], # mandatory [decimal]: 24-hr volume of market pair denoted in QUOTE currency
|
||||
price_change_percent_24h: price_change_percent_24h(ticker), # mandatory [decimal]: 24-hr % price change of market pair
|
||||
highest_price_24h: ticker[:high], # mandatory [decimal]: Highest price of base currency based on given quote currency in the last 24-hrs
|
||||
lowest_price_24h: ticker[:low] # mandatory [decimal]: Lowest price of base currency based on given quote currency in the last 24-hrs
|
||||
}
|
||||
end
|
||||
|
||||
def format_tickers(markets)
|
||||
markets.each_with_object({}) do |market, h|
|
||||
ticker = TickersService[market].ticker
|
||||
unified_base_crypto_id = market.base.coin? ? unified_cryptoasset_id(market.base_unit) : nil
|
||||
unified_quote_crypto_id = market.quote.coin? ? unified_cryptoasset_id(market.quote_unit) : nil
|
||||
h[market.underscore_name.to_s] = {
|
||||
base_id: unified_base_crypto_id, # recommended [integer]: The quote pair Unified Cryptoasset ID
|
||||
quote_id: unified_quote_crypto_id, # recommended [integer]: The base pair Unified Cryptoasset ID
|
||||
last_price: ticker[:last], # mandatory [decimal]: Last transacted price of base currency based on given quote currenc
|
||||
base_volume: ticker[:amount], # mandatory [decimal]: 24-hour trading volume denoted in BASE currency
|
||||
quote_volume: ticker[:volume], # mandatory [decimal]: 24 hour trading volume denoted in QUOTE currency
|
||||
isFrozen: market.state == 'enabled' ? 0 : 1 # recommended [integer]: Indicates if the market is currently enabled (0) or disabled (1)
|
||||
}.compact
|
||||
end
|
||||
end
|
||||
|
||||
def format_trade(trade)
|
||||
{
|
||||
trade_id: trade[:id], # mandatory [integer]: A unique ID associated with the trade for the currency pair transaction
|
||||
price: trade[:price], # mandatory [decimal]: Last transacted price of base currency based on given quote currency
|
||||
base_volume: trade[:amount], # mandatory [decimal]: Transaction amount in BASE currency
|
||||
quote_volume: trade[:total], # mandatory [decimal]: Transaction amount in QUOTE currency
|
||||
timestamp: trade[:created_at] * MILLISECONDS_IN_SECOND, # mandatory [integer]: Unix timestamp in milliseconds for when the transaction occurred
|
||||
type: trade[:taker_type] # mandatory [string]: Used to determine whether or not the transaction originated as a buy or sell
|
||||
}
|
||||
end
|
||||
|
||||
def format_orderbook(asks, bids)
|
||||
{
|
||||
timestamp: DateTime.now.strftime('%Q').to_i, # mandotory [decimal]: Unix timestamp in milliseconds for when the last updated time occurred
|
||||
asks: asks, # mandotory [decimal]: The offer price and quantity for each bid order
|
||||
bids: bids # mandotory [decimal]: The ask price and quantity for each ask order.
|
||||
}
|
||||
end
|
||||
|
||||
def format_currencies(currencies)
|
||||
currencies.each_with_object({}) do |currency, h|
|
||||
h[currency.id.upcase.to_s] = {
|
||||
name: currency.name, # recommended [string]: Full name of cryptocurrency
|
||||
unified_cryptoasset_id: unified_cryptoasset_id(currency.id), # recommended [integer]: Unique ID of cryptocurrency assigned by Unified Cryptoasset ID
|
||||
can_withdraw: currency.withdrawal_enabled, # recommended [boolean]: Identifies whether withdrawals are enabled or disabled
|
||||
can_deposit: currency.deposit_enabled, # recommended [boolean]: Identifies whether deposits are enabled or disabled
|
||||
min_withdraw: currency.min_withdraw_amount # recommended [decimal]: Identifies the single minimum withdrawal amount of a cryptocurrency
|
||||
}.compact
|
||||
end
|
||||
end
|
||||
|
||||
# Only for crypto currencies
|
||||
def unified_cryptoasset_id(currency_id)
|
||||
# System will get response in such format [{:id=>1,:name=>"Bitcoin",:symbol=>"BTC"}]
|
||||
::CoinMarketCap.default_client.get(symbol: currency_id)[0][:id]
|
||||
rescue ::Faraday::Error, ::CoinMarketCap::Error => e
|
||||
Rails.logger.warn e
|
||||
# In format methods we skipped all nil values with compact method
|
||||
# As unified_cryptoasset_id is only recommended field
|
||||
nil
|
||||
end
|
||||
|
||||
# System use this methods instead of ticker[:price_change_percent]
|
||||
# because we dont need percentage symbols here
|
||||
def price_change_percent_24h(ticker)
|
||||
ticker[:open].to_d.zero? ? '0.0' : (ticker[:last].to_d - ticker[:open].to_d) / ticker[:open].to_d
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
19
app/api/v2/coin_market_cap/mount.rb
Normal file
19
app/api/v2/coin_market_cap/mount.rb
Normal file
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinMarketCap
|
||||
class Mount < Grape::API
|
||||
before { set_ets_context! }
|
||||
|
||||
helpers CoinMarketCap::Helpers
|
||||
|
||||
mount CoinMarketCap::Summary
|
||||
mount CoinMarketCap::Assets
|
||||
mount CoinMarketCap::Ticker
|
||||
mount CoinMarketCap::Trades
|
||||
mount CoinMarketCap::Orderbook
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
35
app/api/v2/coin_market_cap/orderbook.rb
Normal file
35
app/api/v2/coin_market_cap/orderbook.rb
Normal file
@@ -0,0 +1,35 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinMarketCap
|
||||
class Orderbook < Grape::API
|
||||
desc 'Get depth or specified market'
|
||||
params do
|
||||
requires :market_pair,
|
||||
type: String,
|
||||
desc: 'A pair such as "LTC_BTC"',
|
||||
coerce_with: ->(name) { name.strip.split('_').join.downcase }
|
||||
optional :depth,
|
||||
type: { value: Integer, message: 'coinmarketcap.market_depth.non_integer_depth' },
|
||||
values: { value: 0..500, message: 'coinmarketcap.market_depth.invalid_depth' },
|
||||
desc: 'Orders depth quantity: [0,5,10,20,50,100,500]'
|
||||
end
|
||||
get "/orderbook/:market_pair" do
|
||||
market = ::Market.find(params[:market_pair])
|
||||
asks = OrderAsk.get_depth(market.id)
|
||||
bids = OrderBid.get_depth(market.id)
|
||||
|
||||
# Depth = 100 means 50 for each bid/ask side
|
||||
# Not defined or 0 = full order book
|
||||
unless params[:depth].to_d.zero?
|
||||
asks = asks[0, params[:depth]/2]
|
||||
bids = bids[0, params[:depth]/2]
|
||||
end
|
||||
|
||||
format_orderbook(asks, bids)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
18
app/api/v2/coin_market_cap/summary.rb
Normal file
18
app/api/v2/coin_market_cap/summary.rb
Normal file
@@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinMarketCap
|
||||
class Summary < Grape::API
|
||||
desc 'Overview of market data for all tickers and all market pairs on the exchange'
|
||||
get '/summary' do
|
||||
::Rails.cache.fetch(:markets_summary_cmc, expires_in: 60) do
|
||||
::Market.enabled.ordered.map do |market|
|
||||
format_summary(TickersService[market].ticker, market)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
16
app/api/v2/coin_market_cap/ticker.rb
Normal file
16
app/api/v2/coin_market_cap/ticker.rb
Normal file
@@ -0,0 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinMarketCap
|
||||
class Ticker < Grape::API
|
||||
desc 'Get 24-hour pricing and volume summary for each market pair'
|
||||
get '/ticker' do
|
||||
::Rails.cache.fetch(:markets_tickers_cmc, expires_in: 60) do
|
||||
format_tickers(::Market.enabled.ordered)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
21
app/api/v2/coin_market_cap/trades.rb
Normal file
21
app/api/v2/coin_market_cap/trades.rb
Normal file
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module CoinMarketCap
|
||||
class Trades < Grape::API
|
||||
desc 'Get recent trades on market'
|
||||
params do
|
||||
requires :market_pair,
|
||||
type: String,
|
||||
desc: 'A pair such as "LTC_BTC"',
|
||||
coerce_with: ->(name) { name.strip.split('_').join.downcase }
|
||||
end
|
||||
get "/trades/:market_pair" do
|
||||
market = ::Market.find(params[:market_pair])
|
||||
Trade.public_from_influx(market.id).map { |trade| format_trade(trade) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
22
app/api/v2/constraints.rb
Normal file
22
app/api/v2/constraints.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Constraints
|
||||
class << self
|
||||
def included(base)
|
||||
rate_limit = ENV.fetch("PEATIO_RATE_LIMIT_5MIN", 6000).to_i
|
||||
apply_rules!(rate_limit)
|
||||
base.use Rack::Attack
|
||||
end
|
||||
|
||||
def apply_rules!(rate_limit)
|
||||
Rack::Attack.throttle 'Limit number of calls to API', limit: rate_limit, period: 5.minutes do |req|
|
||||
req.env['api_v2.authentic_member_email']
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
50
app/api/v2/entities/account.rb
Normal file
50
app/api/v2/entities/account.rb
Normal file
@@ -0,0 +1,50 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Account < Base
|
||||
expose(
|
||||
:currency_id,
|
||||
as: :currency,
|
||||
documentation: {
|
||||
desc: 'Currency code.',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:balance,
|
||||
format_with: :decimal,
|
||||
documentation: {
|
||||
desc: 'Account balance.',
|
||||
type: BigDecimal
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:locked,
|
||||
format_with: :decimal,
|
||||
documentation: {
|
||||
desc: 'Account locked funds.',
|
||||
type: BigDecimal
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:deposit_address,
|
||||
if: ->(account, _options) { account.currency.coin? },
|
||||
using: API::V2::Entities::PaymentAddress,
|
||||
documentation: {
|
||||
desc: 'User deposit address',
|
||||
type: String
|
||||
}
|
||||
) do |account, options|
|
||||
wallet = Wallet.deposit_wallet(account.currency_id)
|
||||
::PaymentAddress.find_by(wallet: wallet, member: options[:current_user], remote: false)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
13
app/api/v2/entities/base.rb
Normal file
13
app/api/v2/entities/base.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Base < Grape::Entity
|
||||
format_with(:iso8601) {|t| t.to_time.in_time_zone(::Rails.configuration.time_zone).iso8601 if t }
|
||||
format_with(:decimal) {|d| d.to_s('F') if d }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
88
app/api/v2/entities/beneficiary.rb
Normal file
88
app/api/v2/entities/beneficiary.rb
Normal file
@@ -0,0 +1,88 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Beneficiary < Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
desc: 'Beneficiary Identifier in Database',
|
||||
type: Integer
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:currency_id,
|
||||
as: :currency,
|
||||
documentation: {
|
||||
desc: 'Beneficiary currency code.',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:uid,
|
||||
documentation: {
|
||||
desc: 'Beneficiary owner',
|
||||
type: String
|
||||
}
|
||||
) { |b| b.member.uid }
|
||||
|
||||
expose(
|
||||
:name,
|
||||
documentation: {
|
||||
desc: 'Human rememberable name which refer beneficiary.',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:description,
|
||||
documentation: {
|
||||
desc: 'Human rememberable description of beneficiary.',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
|
||||
# expose(
|
||||
# :data,
|
||||
# documentation: {
|
||||
# desc: 'Bank Account details for fiat Beneficiary in JSON format.'\
|
||||
# 'For crypto it\'s blockchain address.',
|
||||
# type: JSON
|
||||
# }
|
||||
# ) do |beneficiary|
|
||||
# beneficiary.currency.fiat? ? beneficiary.masked_data : beneficiary.data
|
||||
# end
|
||||
expose(
|
||||
:data,
|
||||
documentation: {
|
||||
desc: 'Bank Account details for fiat Beneficiary in JSON format.'\
|
||||
'For crypto it\'s blockchain address.',
|
||||
type: JSON
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
desc: 'Defines either beneficiary active - user can use it to withdraw money'\
|
||||
'or pending - requires beneficiary activation with pin.',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:sent_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
desc: 'Time when last pin was sent',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
38
app/api/v2/entities/bonus.rb
Normal file
38
app/api/v2/entities/bonus.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Bonus < Base
|
||||
expose(
|
||||
:bonus_member_id,
|
||||
as: :uid,
|
||||
documentation: {
|
||||
desc: 'user_uid.',
|
||||
type: String
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:amount,
|
||||
format_with: :decimal,
|
||||
documentation: {
|
||||
desc: 'Bonus account',
|
||||
type: BigDecimal
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
as: :date,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
desc: 'created date',
|
||||
type: Date
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
188
app/api/v2/entities/currency.rb
Normal file
188
app/api/v2/entities/currency.rb
Normal file
@@ -0,0 +1,188 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Currency < Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
desc: 'Currency code.',
|
||||
type: String,
|
||||
values: -> { ::Currency.visible.codes },
|
||||
example: -> { ::Currency.visible.first.id }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:name,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency name',
|
||||
example: -> { ::Currency.visible.first.name }
|
||||
},
|
||||
if: -> (currency){ currency.name.present? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:description,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency description',
|
||||
example: -> { ::Currency.visible.first.id }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:homepage,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency homepage',
|
||||
example: -> { ::Currency.visible.first.id }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:price,
|
||||
documentation: {
|
||||
desc: 'Currency current price'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:explorer_transaction,
|
||||
documentation: {
|
||||
desc: 'Currency transaction exprorer url template',
|
||||
example: 'https://testnet.blockchain.info/tx/'
|
||||
},
|
||||
if: -> (currency){ currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:explorer_address,
|
||||
documentation: {
|
||||
desc: 'Currency address exprorer url template',
|
||||
example: 'https://testnet.blockchain.info/address/'
|
||||
},
|
||||
if: -> (currency){ currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:type,
|
||||
documentation: {
|
||||
type: String,
|
||||
values: -> { ::Currency.types },
|
||||
desc: 'Currency type',
|
||||
example: -> { ::Currency.visible.first.type }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:deposit_enabled,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency deposit possibility status (true/false).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:withdrawal_enabled,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Currency withdrawal possibility status (true/false).'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:deposit_fee,
|
||||
documentation: {
|
||||
desc: 'Currency deposit fee',
|
||||
example: -> { ::Currency.visible.first.deposit_fee }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:min_deposit_amount,
|
||||
documentation: {
|
||||
desc: 'Minimal deposit amount',
|
||||
example: -> { ::Currency.visible.first.min_deposit_amount }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:withdraw_fee,
|
||||
documentation: {
|
||||
desc: 'Currency withdraw fee',
|
||||
example: -> { ::Currency.visible.first.withdraw_fee }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:min_withdraw_amount,
|
||||
documentation: {
|
||||
desc: 'Minimal withdraw amount',
|
||||
example: -> { ::Currency.visible.first.min_withdraw_amount }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:withdraw_limit_24h,
|
||||
documentation: {
|
||||
desc: 'Currency 24h withdraw limit',
|
||||
example: -> { ::Currency.visible.first.withdraw_limit_24h }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:withdraw_limit_72h,
|
||||
documentation: {
|
||||
desc: 'Currency 72h withdraw limit',
|
||||
example: -> { ::Currency.visible.first.withdraw_limit_72h }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:base_factor,
|
||||
documentation: {
|
||||
desc: 'Currency base factor',
|
||||
example: -> { ::Currency.visible.first.base_factor }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:precision,
|
||||
documentation: {
|
||||
desc: 'Currency precision',
|
||||
example: -> { ::Currency.visible.first.precision }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:position,
|
||||
documentation: {
|
||||
desc: 'Position used for defining currencies order',
|
||||
example: -> { ::Currency.visible.first.precision }
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:icon_url,
|
||||
documentation: {
|
||||
desc: 'Currency icon',
|
||||
example: 'https://upload.wikimedia.org/wikipedia/commons/0/05/Ethereum_logo_2014.svg'
|
||||
},
|
||||
if: -> (currency){ currency.icon_url.present? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:min_confirmations,
|
||||
if: ->(currency) { currency.coin? },
|
||||
documentation: {
|
||||
desc: 'Number of confirmations required for confirming deposit or withdrawal'
|
||||
}
|
||||
) { |c| c.blockchain.min_confirmations }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
104
app/api/v2/entities/deposit.rb
Normal file
104
app/api/v2/entities/deposit.rb
Normal file
@@ -0,0 +1,104 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Deposit < Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Unique deposit id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:currency_id,
|
||||
as: :currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit currency id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:amount,
|
||||
format_with: :decimal,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Deposit amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:fee,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Deposit fee.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:txid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit transaction id.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:confirmations,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: 'Number of deposit confirmations.'
|
||||
},
|
||||
if: ->(deposit) { deposit.currency.coin? }
|
||||
)
|
||||
|
||||
expose(
|
||||
:aasm_state,
|
||||
as: :state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit state.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:transfer_type,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Deposit transfer type'
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetime when deposit was created.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:completed_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetime when deposit was completed..'
|
||||
}
|
||||
)
|
||||
expose(
|
||||
:tid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The shared transaction ID'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
73
app/api/v2/entities/deposit_limit.rb
Normal file
73
app/api/v2/entities/deposit_limit.rb
Normal file
@@ -0,0 +1,73 @@
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class DepositLimit < API::V2::Entities::Base
|
||||
expose(
|
||||
:id,
|
||||
documentation:{
|
||||
type: Integer,
|
||||
desc: 'Unique deposit limit table identifier in database.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:group,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'Member group for define deposit limits.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:kyc_level,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'KYC level for define deposit limits.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:kind,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: 'kind for define deposit limits.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:limit_24_hour,
|
||||
documentation:{
|
||||
type: BigDecimal,
|
||||
desc: '24 hours deposit limit.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:limit_1_month,
|
||||
documentation:{
|
||||
type: BigDecimal,
|
||||
desc: '1 month deposit limit.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'deposit limit table created time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'deposit limit table updated time in iso8601 format.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
98
app/api/v2/entities/internal_transfer.rb
Normal file
98
app/api/v2/entities/internal_transfer.rb
Normal file
@@ -0,0 +1,98 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class InternalTransfer < Base
|
||||
|
||||
expose(
|
||||
:currency_id,
|
||||
as: :currency,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The currency code.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:sender_username,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The internal transfer sender.'
|
||||
}
|
||||
) do |transfer|
|
||||
transfer.sender&.username
|
||||
end
|
||||
|
||||
expose(
|
||||
:receiver_username,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The internal transfer receiver.'
|
||||
}
|
||||
) do |transfer|
|
||||
transfer.receiver&.username
|
||||
end
|
||||
|
||||
expose(
|
||||
:sender_uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The internal transfer sender.'
|
||||
}
|
||||
) do |transfer|
|
||||
transfer.sender.uid
|
||||
end
|
||||
|
||||
expose(
|
||||
:receiver_uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The internal transfer receiver.'
|
||||
}
|
||||
) do |transfer|
|
||||
transfer.receiver.uid
|
||||
end
|
||||
|
||||
expose(
|
||||
:direction, ## call method from model
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The internal transfer direction (incoming or outcoming internal transfer).'
|
||||
}
|
||||
) do |transfer, options|
|
||||
transfer.direction(options[:current_user])
|
||||
end
|
||||
|
||||
expose(
|
||||
:amount,
|
||||
format_with: :decimal,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: 'Internal transfer Amount.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
as: :status,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The internal transfer state.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'The datetimes for the internal transfer.'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
93
app/api/v2/entities/market.rb
Normal file
93
app/api/v2/entities/market.rb
Normal file
@@ -0,0 +1,93 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Market < Base
|
||||
expose(
|
||||
:id,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Unique market id. It's always in the form of xxxyyy,"\
|
||||
"where xxx is the base currency code, yyy is the quote"\
|
||||
"currency code, e.g. 'btcusd'. All available markets can"\
|
||||
"be found at /api/v2/markets."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:name,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Market name.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:base_unit,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Market Base unit."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:quote_unit,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Market Quote unit."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:min_price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Minimum order price."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:max_price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Maximum order price."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:min_amount,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Minimum order amount."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:amount_precision,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Precision for order amount."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:price_precision,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Precision for order price."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Market state defines if user can see/trade on current market."
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
38
app/api/v2/entities/member.rb
Normal file
38
app/api/v2/entities/member.rb
Normal file
@@ -0,0 +1,38 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Member < Base
|
||||
expose(
|
||||
:uid,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Member UID.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:email,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: 'Member email.'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:accounts,
|
||||
using: API::V2::Entities::Account,
|
||||
documentation: {
|
||||
type: 'API::V2::Entities::Account',
|
||||
is_array: true,
|
||||
desc: 'Member accounts.'
|
||||
}
|
||||
) do |m|
|
||||
m.accounts.includes(:currency)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
166
app/api/v2/entities/order.rb
Normal file
166
app/api/v2/entities/order.rb
Normal file
@@ -0,0 +1,166 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class Order < Base
|
||||
expose(
|
||||
:id,
|
||||
documentation:{
|
||||
type: Integer,
|
||||
desc: "Unique order id."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:uuid,
|
||||
documentation:{
|
||||
type: String,
|
||||
desc: "Unique order UUID."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:side,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Either 'sell' or 'buy'."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:ord_type,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Type of order, either 'limit' or 'market'."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Price for each unit. e.g."\
|
||||
"If you want to sell/buy 1 btc at 3000 usd, the price is '3000.0'"
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:avg_price,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Average execution price, average of price in trades."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "One of 'wait', 'done', or 'cancel'."\
|
||||
"An order in 'wait' is an active order, waiting fulfillment;"\
|
||||
"a 'done' order is an order fulfilled;"\
|
||||
"'cancel' means the order has been canceled."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:market_id,
|
||||
as: :market,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "The market in which the order is placed, e.g. 'btcusd'."\
|
||||
"All available markets can be found at /api/v2/markets."
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
expose(
|
||||
:created_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Order create time in iso8601 format."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:updated_at,
|
||||
format_with: :iso8601,
|
||||
documentation: {
|
||||
type: String,
|
||||
desc: "Order updated time in iso8601 format."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:origin_volume,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "The amount user want to sell/buy."\
|
||||
"An order could be partially executed,"\
|
||||
"e.g. an order sell 5 btc can be matched with a buy 3 btc order,"\
|
||||
"left 2 btc to be sold; in this case the order's volume would be '5.0',"\
|
||||
"its remaining_volume would be '2.0', its executed volume is '3.0'."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:volume,
|
||||
as: :remaining_volume,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "The remaining volume, see 'volume'."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:executed_volume,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "The executed volume, see 'volume'."
|
||||
}
|
||||
) do |order, _options|
|
||||
order.origin_volume - order.volume
|
||||
end
|
||||
|
||||
expose(
|
||||
:maker_fee,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Fee for maker."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:taker_fee,
|
||||
documentation: {
|
||||
type: BigDecimal,
|
||||
desc: "Fee for taker."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:trades_count,
|
||||
documentation: {
|
||||
type: Integer,
|
||||
desc: "Count of trades."
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:trades,
|
||||
documentation: {
|
||||
type: 'API::V2::Entities::Trade',
|
||||
is_array: true,
|
||||
desc: "Trades wiht this order."
|
||||
},
|
||||
if: { type: :full }
|
||||
) do |order, _options|
|
||||
API::V2::Entities::Trade.represent order.trades, side: order.side
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
32
app/api/v2/entities/order_book.rb
Normal file
32
app/api/v2/entities/order_book.rb
Normal file
@@ -0,0 +1,32 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_dependency 'v2/entities/order'
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class OrderBook < Base
|
||||
expose(
|
||||
:asks,
|
||||
using: Order,
|
||||
documentation: {
|
||||
type: 'API::V2::Entities::Order',
|
||||
is_array: true,
|
||||
desc: 'Asks in orderbook'
|
||||
}
|
||||
)
|
||||
|
||||
expose(
|
||||
:bids,
|
||||
using: Order,
|
||||
documentation: {
|
||||
type: 'API::V2::Entities::Order',
|
||||
is_array: true,
|
||||
desc: 'Bids in orderbook'
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
41
app/api/v2/entities/payment_address.rb
Normal file
41
app/api/v2/entities/payment_address.rb
Normal file
@@ -0,0 +1,41 @@
|
||||
# encoding: UTF-8
|
||||
# frozen_string_literal: true
|
||||
|
||||
module API
|
||||
module V2
|
||||
module Entities
|
||||
class PaymentAddress < Base
|
||||
expose(
|
||||
:currencies,
|
||||
documentation: {
|
||||
desc: 'Currencies codes.',
|
||||
is_array: true,
|
||||
example: -> { ::Currency.visible.codes }
|
||||
}
|
||||
) do |pa|
|
||||
pa.wallet.currencies.codes
|
||||
end
|
||||
|
||||
expose(
|
||||
:address,
|
||||
documentation: {
|
||||
desc: 'Payment address.',
|
||||
type: String
|
||||
}
|
||||
) do |pa, options|
|
||||
options[:address_format] ? pa.format_address(options[:address_format]) : pa.address
|
||||
end
|
||||
|
||||
expose(
|
||||
:state,
|
||||
documentation: {
|
||||
desc: 'Payment address state.',
|
||||
type: String
|
||||
}
|
||||
) do |pa|
|
||||
pa.address.present? ? 'active' : 'pending'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user