taunchpad 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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