taunchpad 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +28 -0
  3. data/Rakefile +32 -0
  4. data/app/controllers/concerns/exception_handlers.rb +19 -0
  5. data/app/controllers/concerns/jwt_payload.rb +19 -0
  6. data/app/controllers/concerns/response.rb +25 -0
  7. data/app/controllers/launchpad/api/v2/admin/base_controller.rb +22 -0
  8. data/app/controllers/launchpad/api/v2/admin/ieo/orders_controller.rb +29 -0
  9. data/app/controllers/launchpad/api/v2/admin/ieo/sales_controller.rb +112 -0
  10. data/app/controllers/launchpad/api/v2/private/base_controller.rb +14 -0
  11. data/app/controllers/launchpad/api/v2/private/ieo/orders_controller.rb +97 -0
  12. data/app/controllers/launchpad/api/v2/private/ieo/sales_controller.rb +22 -0
  13. data/app/controllers/launchpad/api/v2/public/base_controller.rb +13 -0
  14. data/app/controllers/launchpad/api/v2/public/ieo/sales_controller.rb +56 -0
  15. data/app/controllers/launchpad/application_controller.rb +10 -0
  16. data/app/helpers/launchpad/application_helper.rb +4 -0
  17. data/app/models/launchpad/application_record.rb +5 -0
  18. data/app/models/launchpad/ieo.rb +21 -0
  19. data/app/models/launchpad/ieo/order.rb +576 -0
  20. data/app/models/launchpad/ieo/sale.rb +371 -0
  21. data/app/models/launchpad/ieo/sale_pair.rb +83 -0
  22. data/app/services/barong/management_api_v2/client.rb +33 -0
  23. data/app/services/management_api_v2/client.rb +73 -0
  24. data/app/services/management_api_v2/exception.rb +25 -0
  25. data/app/services/peatio/management_api_v2/client.rb +49 -0
  26. data/app/workers/launchpad/ieo/order_execute_worker.rb +26 -0
  27. data/app/workers/launchpad/ieo/order_refund_worker.rb +19 -0
  28. data/app/workers/launchpad/ieo/order_release_worker.rb +22 -0
  29. data/app/workers/launchpad/ieo/sale_cancel_worker.rb +20 -0
  30. data/app/workers/launchpad/ieo/sale_currency_list_worker.rb +19 -0
  31. data/app/workers/launchpad/ieo/sale_distribute_worker.rb +20 -0
  32. data/app/workers/launchpad/ieo/sale_finish_worker.rb +21 -0
  33. data/app/workers/launchpad/ieo/sale_pair_list_worker.rb +21 -0
  34. data/app/workers/launchpad/ieo/sale_release_funds_worker.rb +23 -0
  35. data/app/workers/launchpad/ieo/sale_start_worker.rb +21 -0
  36. data/config/initializers/active_model.rb +13 -0
  37. data/config/initializers/api_pagination.rb +33 -0
  38. data/config/initializers/inflections.rb +19 -0
  39. data/config/routes.rb +35 -0
  40. data/db/migrate/20191120145404_create_launchpad_ieo.rb +52 -0
  41. data/db/migrate/20200814114105_add_fees_policy_in_sale.rb +5 -0
  42. data/lib/launchpad.rb +10 -0
  43. data/lib/launchpad/engine.rb +17 -0
  44. data/lib/launchpad/precision_validator.rb +25 -0
  45. data/lib/launchpad/version.rb +3 -0
  46. data/lib/tasks/launchpad_tasks.rake +4 -0
  47. metadata +229 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5d65be3730ac435f0d3c272bd0ba55e52ec0d80752a94507b2edbf82bb40ba11
4
+ data.tar.gz: 3e96820f0162d890421b8adda89e5109207b3a63455f0f02f0bc272d432a999f
5
+ SHA512:
6
+ metadata.gz: 3f7966e8b4bf31b2353078a25a304fb6712a5efb055bb83b54462a8c548f806291881702963a448e91b8a71e17ead5813773f31008df3367fb07daa0cd96ab6b
7
+ data.tar.gz: 39394902fcbd300dbb3a59e9243ab7b0d078008217c2fb60639e9c75d525198f7591d6067be3ef9c7810341b953f456fe0762c24bad862b1599c1ff842de8593
data/README.md ADDED
@@ -0,0 +1,28 @@
1
+ # Launchpad
2
+ Short description and motivation.
3
+
4
+ ## Usage
5
+ How to use my plugin.
6
+
7
+ ## Installation
8
+ Add this line to your application's Gemfile:
9
+
10
+ ```ruby
11
+ gem 'launchpad'
12
+ ```
13
+
14
+ And then execute:
15
+ ```bash
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+ ```bash
21
+ $ gem install launchpad
22
+ ```
23
+
24
+ ## Contributing
25
+ Contribution directions go here.
26
+
27
+ ## License
28
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Launchpad'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ExceptionHandlers
4
+ def self.included(base)
5
+ base.instance_eval do
6
+
7
+ rescue_from ActiveRecord::RecordInvalid do |e|
8
+ record = e.record
9
+ class_name = record.model_name.singular
10
+ errors = record.errors.api_messages.map { |err| "#{class_name}.#{err}" }
11
+ errors_response(errors)
12
+ end
13
+
14
+ rescue_from ActiveRecord::RecordNotFound do |_e|
15
+ error_response("record.not_found", 404)
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JWTPayload
4
+ def jwt_payload
5
+ @jwt_payload ||= request.env.fetch("jwt.payload", {}).symbolize_keys
6
+ end
7
+
8
+ def uid
9
+ jwt_payload[:uid]
10
+ end
11
+
12
+ def email
13
+ jwt_payload[:email]
14
+ end
15
+
16
+ def role
17
+ jwt_payload[:role]
18
+ end
19
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Response
4
+ def json_response(object, status=:ok)
5
+ render json: object, status: status
6
+ end
7
+
8
+ def error_response(error, status=422)
9
+ data = "#{controller_namespace}.#{error}"
10
+ json_response({errors: [data]}, status)
11
+ end
12
+
13
+ def errors_response(errors, status=422)
14
+ data = errors.map { |error| "#{controller_namespace}.#{error}" }
15
+ json_response({errors: data}, status)
16
+ end
17
+
18
+ def not_found
19
+ render plain: "404 Not Found", status: 404
20
+ end
21
+
22
+ def controller_namespace
23
+ self.class.module_parent.name.split("::").last.underscore
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module Launchpad
5
+ module API
6
+ module V2
7
+ module Admin
8
+ class BaseController < ApplicationController
9
+ include JWTPayload
10
+
11
+ ADMIN_ROLES = %w[superadmin admin accountant compliance support technical].freeze
12
+
13
+ before_action :authorize_admin!
14
+
15
+ def authorize_admin!
16
+ not_found unless role.to_s.in?(ADMIN_ROLES)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Launchpad
4
+ module API
5
+ module V2
6
+ module Admin
7
+ module IEO
8
+ class OrdersController < BaseController
9
+ # GET /admin/orders
10
+ def index
11
+ ransack_params = {
12
+ uid_eq: params[:uid],
13
+ state_in: params[:state],
14
+ sale_id_eq: params[:sale_id],
15
+ sale_pair_id_eq: params[:sale_pair_id],
16
+ sale_pair_quote_currency_id_eq: params[:quote_unit],
17
+ sale_currency_id_eq: params[:base_unit]
18
+ }
19
+
20
+ orders = Launchpad::IEO::Order.ransack(ransack_params)
21
+
22
+ json_response(paginate(orders.result.includes(:sale, :sale_pair)), 200)
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Launchpad
4
+ module API
5
+ module V2
6
+ module Admin
7
+ module IEO
8
+ class SalesController < BaseController
9
+ # GET /admin/sales
10
+ def index
11
+ ransack_params = {
12
+ state_in: params[:state],
13
+ currency_id_eq: params[:currency_id],
14
+ name_cont_all: params[:name]
15
+ }
16
+
17
+ sales = Launchpad::IEO::Sale.ransack(ransack_params)
18
+ json_response(paginate(sales.result), 200)
19
+ end
20
+
21
+ def show
22
+ json_response(Launchpad::IEO::Sale.find(params[:id]), 200)
23
+ end
24
+
25
+ # POST /admin/sales
26
+ def create
27
+ if sale_params[:pairs].blank?
28
+ error_response("sale_pair.missing_or_empty_pairs", 422)
29
+ return
30
+ end
31
+
32
+ sale = nil
33
+ ActiveRecord::Base.transaction do
34
+ sale = Launchpad::IEO::Sale.create!(sale_params.except(:pairs))
35
+
36
+ sale_params[:pairs].each do |pair|
37
+ sale.pairs.create!(pair)
38
+ end
39
+ end
40
+
41
+ json_response(sale, 201)
42
+ end
43
+
44
+ # PUT /admin/sales/:id
45
+ def update
46
+ sale = Launchpad::IEO::Sale.find(params[:id])
47
+ states = Launchpad::IEO::Sale::EDITABLE_STATES
48
+
49
+ if sale.state.in?(states.keys.map(&:to_s))
50
+ sale.update!(sale_params.slice(*states[sale.state.to_sym]))
51
+ sale.send(:enqueue_distribute_job) if sale.ongoing? && sale_params["finishes_at"].present?
52
+ else
53
+ error_response("sale.non_editable", 422)
54
+ return
55
+ end
56
+
57
+ json_response(sale, 200)
58
+ end
59
+
60
+ def release_fund
61
+ sale = Launchpad::IEO::Sale.find(params[:id])
62
+ unless sale.finished?
63
+ error_response("launchpad_ieo_sale.state_mismatch", 422)
64
+ return
65
+ end
66
+ if sale_lockup_params[:release_fund].to_d.negative? || sale.lockup_percentage.to_d - sale_lockup_params[:release_fund].to_d < 0
67
+ error_response("launchpad_ieo_sale.invalid_lockup_percentage_or_negative", 422)
68
+ return
69
+ end
70
+ params.merge!(lockup_percentage: sale.lockup_percentage.to_d - sale_lockup_params[:release_fund].to_d)
71
+ sale.update!(sale_lockup_params.except(:release_fund))
72
+ sale.send(:enqueue_release_funds_job, sale_lockup_params[:release_fund].to_d)
73
+ json_response(sale, 200)
74
+ end
75
+
76
+ # PUT /admin/sales/:id/:event
77
+ def trigger_event
78
+ events = Launchpad::IEO::Sale.aasm.events.map { |e| e.name.to_s }
79
+ sale = Launchpad::IEO::Sale.find(params[:id])
80
+
81
+ begin
82
+ if params[:event].in?(events)
83
+ sale.send("#{params[:event]}!")
84
+ else
85
+ error_response("sale.event_not_found", 400)
86
+ return
87
+ end
88
+ rescue AASM::InvalidTransition
89
+ error_response("sale.transition_failed", 422)
90
+ return
91
+ end
92
+
93
+ json_response(sale, 200)
94
+ end
95
+
96
+ private
97
+
98
+ def sale_params
99
+ params.permit(:name, :description, :currency_id, :supply, :low_goal, :min_unit,
100
+ :commission, :owner_uid, :result, :lockup_percentage, :type, :min_amount,
101
+ :max_amount, :starts_at, :finishes_at, :fees_policy, pairs: %i[quote_currency_id price])
102
+ end
103
+
104
+ def sale_lockup_params
105
+ params.permit(:id, :lockup_percentage, :release_fund)
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,14 @@
1
+ # encoding: UTF-8
2
+ # frozen_string_literal: true
3
+
4
+ module Launchpad
5
+ module API
6
+ module V2
7
+ module Private
8
+ class BaseController < ApplicationController
9
+ include JWTPayload
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Launchpad
4
+ module API
5
+ module V2
6
+ module Private
7
+ module IEO
8
+ class OrdersController < BaseController
9
+ # POST /ieo/orders
10
+ # TODO: Improve handling of Management API exceptions.
11
+ def create
12
+ sale = Launchpad::IEO::Sale.public_states.find(order_params[:sale])
13
+ unless sale.ongoing?
14
+ error_response("order.sale_not_active", 422)
15
+ return
16
+ end
17
+
18
+ sale_pair = Launchpad::IEO::SalePair.find_by!(sale_id: sale.id, quote_currency_id: order_params[:quote_unit])
19
+
20
+ begin
21
+ response = Peatio::ManagementAPIV2::Client.new.balance(
22
+ uid: uid,
23
+ currency: order_params[:quote_unit]
24
+ )
25
+ rescue ManagementAPIV2::Exception
26
+ error_response("order.user_balance_checking_failed", 503)
27
+ return
28
+ end
29
+
30
+ if response[:balance].to_d < contribution(sale, order_params[:contribution].to_d)
31
+ error_response("order.insufficient_balance", 422)
32
+ return
33
+ end
34
+
35
+ order = Launchpad::IEO::Order.create!(
36
+ sale_pair_id: sale_pair.id,
37
+ uid: uid,
38
+ contribution: order_params[:contribution].to_d
39
+ )
40
+
41
+ begin
42
+ order.dispatch!
43
+
44
+ json_response(order, 201)
45
+ rescue ManagementAPIV2::Exception
46
+ order.reject!
47
+ error_response("order.transfers_creation_failed", 422)
48
+ end
49
+ end
50
+
51
+ # GET /ieo/orders
52
+ def index
53
+ ransack_params = {
54
+ uid_eq: uid,
55
+ state_in: params[:state],
56
+ sale_pair_sale_id_eq: params[:sale],
57
+ sale_pair_quote_currency_id_eq: params[:quote_unit],
58
+ sale_name_eq: params[:search]
59
+ }
60
+ ransack_params.merge!(created_at_gteq: Time.at(params[:time_from].to_i))\
61
+ unless params[:time_from].nil?
62
+ ransack_params.merge!(created_at_lteq: Time.at(params[:time_to].to_i))\
63
+ unless params[:time_to].nil?
64
+
65
+ orders = Launchpad::IEO::Order.ransack(ransack_params)
66
+ orders.sorts = "id desc"
67
+ json_response(paginate(orders.result.includes(:sale, :sale_pair)), 200)
68
+ end
69
+
70
+ # DELETE /ieo/orders/:id
71
+ def destroy
72
+ order = Launchpad::IEO::Order.find_by!(uid: uid, id: params[:id])
73
+
74
+ unless order.active?
75
+ error_response("order.non_active", 422)
76
+ return
77
+ end
78
+
79
+ order.cancel!
80
+ json_response(order, 200)
81
+ end
82
+
83
+ private
84
+
85
+ def contribution(sale, contrib)
86
+ sale.fees_policy.add? ? contrib + (contrib * sale.commission) : contrib
87
+ end
88
+
89
+ def order_params
90
+ params.permit(:sale, :quote_unit, :contribution)
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Launchpad
4
+ module API
5
+ module V2
6
+ module Private
7
+ module IEO
8
+ class SalesController < BaseController
9
+ def show
10
+ launchpad = Launchpad::IEO::Sale.find(params[:id])
11
+ additional_params = {
12
+ generic_bids: launchpad.orders.where("state = ? OR state = ? OR state = ?", 'active', 'completed', 'purchased').last(50).as_json(uid: true),
13
+ my_bids: launchpad.orders.where(uid: uid).as_json(uid: true).last(50),
14
+ }
15
+ json_response(launchpad.as_json.merge(additional_params), 200)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end