diff --git a/app/graphql/inputs/create_stake_order_attributes_input.rb b/app/graphql/inputs/create_stake_order_attributes_input.rb new file mode 100644 index 0000000..f54a2c7 --- /dev/null +++ b/app/graphql/inputs/create_stake_order_attributes_input.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true +module Inputs + class CreateStakeOrderAttributesInput < Types::BaseInputObject + argument :currency_id, ID, required: true + argument :pool_name, String, required: true + argument :amount, String, "Amount to be paid", required: true + end +end diff --git a/app/graphql/mutations/create_stake_order.rb b/app/graphql/mutations/create_stake_order.rb new file mode 100644 index 0000000..6f815b2 --- /dev/null +++ b/app/graphql/mutations/create_stake_order.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true +module Mutations + class CreateStakeOrder < BaseMutation + field :order, Types::StakeOrderType, null: true + + argument :order, Inputs::CreateStakeOrderAttributesInput, required: true + + def resolve(order:) + currency_id = decode_id(order[:currency_id]) + amount = BigDecimal(order[:amount]) + + ActiveRecord::Base.transaction do + current_user + .balances + .find_by!(currency_id: currency_id) + .withdrawal!(amount) + + record = StakeOrder.create!( + amount: amount, + currency_id: currency_id, + pool_name: order[:pool_name], + user_id: current_user.id, + ) + + { order: record } + rescue ActiveRecord::RecordInvalid => e + { errors: Resolvers::ModelErrors.from_active_record_model(e.record) } + end + end + end +end diff --git a/app/graphql/types/mutation_type.rb b/app/graphql/types/mutation_type.rb index a1405bb..8ecc552 100644 --- a/app/graphql/types/mutation_type.rb +++ b/app/graphql/types/mutation_type.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true module Types class MutationType < Types::BaseObject + field :create_stake_order, mutation: Mutations::CreateStakeOrder field :create_sell_crypto_order, mutation: Mutations::CreateSellCryptoOrder field :create_buy_crypto_order, mutation: Mutations::CreateBuyCryptoOrder end diff --git a/app/models/stake_order.rb b/app/models/stake_order.rb index bc6e491..182824a 100644 --- a/app/models/stake_order.rb +++ b/app/models/stake_order.rb @@ -4,26 +4,30 @@ # # Table name: stake_orders # -# id :bigint not null, primary key -# amount :decimal(20, 10) default(0.0), not null -# pool_name :string not null -# status :string not null -# created_at :datetime not null -# updated_at :datetime not null -# user_id :bigint not null +# id :bigint not null, primary key +# amount :decimal(20, 10) default(0.0), not null +# pool_name :string not null +# status :string not null +# created_at :datetime not null +# updated_at :datetime not null +# currency_id :bigint +# user_id :bigint not null # # Indexes # -# index_stake_orders_on_user_id (user_id) +# index_stake_orders_on_currency_id (currency_id) +# index_stake_orders_on_user_id (user_id) # # Foreign Keys # +# fk_rails_... (currency_id => currencies.id) # fk_rails_... (user_id => users.id) # class StakeOrder < ApplicationRecord include Processable belongs_to :user + belongs_to :currency validates :pool_name, presence: true validates :amount, presence: true diff --git a/db/migrate/20210816174637_add_currency_to_stake_order.rb b/db/migrate/20210816174637_add_currency_to_stake_order.rb new file mode 100644 index 0000000..46f4e49 --- /dev/null +++ b/db/migrate/20210816174637_add_currency_to_stake_order.rb @@ -0,0 +1,6 @@ +# frozen_string_literal: true +class AddCurrencyToStakeOrder < ActiveRecord::Migration[6.1] + def change + add_reference(:stake_orders, :currency, foreign_key: true) + end +end diff --git a/db/schema.rb b/db/schema.rb index adbc574..89afce2 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2021_08_16_032056) do +ActiveRecord::Schema.define(version: 2021_08_16_174637) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -111,6 +111,8 @@ ActiveRecord::Schema.define(version: 2021_08_16_032056) do t.decimal "amount", precision: 20, scale: 10, default: "0.0", null: false t.datetime "created_at", precision: 6, null: false t.datetime "updated_at", precision: 6, null: false + t.bigint "currency_id" + t.index ["currency_id"], name: "index_stake_orders_on_currency_id" t.index ["user_id"], name: "index_stake_orders_on_user_id" end @@ -145,6 +147,7 @@ ActiveRecord::Schema.define(version: 2021_08_16_032056) do add_foreign_key "fiat_balances", "users" add_foreign_key "sell_crypto_orders", "currencies" add_foreign_key "sell_crypto_orders", "users" + add_foreign_key "stake_orders", "currencies" add_foreign_key "stake_orders", "users" add_foreign_key "user_documents", "users" end diff --git a/db/seeds.rb b/db/seeds.rb index 8008fe1..1d01dc6 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -2,19 +2,11 @@ AdminUser.create!(email: "admin@example.com", password: "password") -user = User.create!( +Currency.create!(name: "CAKE") + +User.create!( first_name: "Test", last_name: "User", email: "user@example.com", password: "password" ) - -currency = Currency.create!(name: "CAKE") - -Balance.create!( - user_id: user.id, - currency_id: currency.id, - amount: rand * 10000 -) - -FiatBalance.create!(user_id: user.id, amount_cents: 15000) diff --git a/erd.svg b/erd.svg index 1428561..28dd21c 100644 --- a/erd.svg +++ b/erd.svg @@ -4,180 +4,188 @@ - - + + XStake - -XStake domain model + +XStake domain model m_AdminUser - -AdminUser - -email -string ∗ U -encrypted_password -string ∗ -remember_created_at -datetime -reset_password_sent_at -datetime -reset_password_token -string + +AdminUser + +email +string ∗ U +encrypted_password +string ∗ +remember_created_at +datetime +reset_password_sent_at +datetime +reset_password_token +string m_Balance - -Balance - -amount -decimal (20,10) ∗ -currency_id -integer (8) ∗ FK -user_id -integer (8) ∗ FK + +Balance + +amount +decimal (20,10) ∗ +currency_id +integer (8) ∗ FK +user_id +integer (8) ∗ FK m_BuyCryptoOrder - -BuyCryptoOrder - -currency_id -integer (8) ∗ FK -paid_amount_cents -integer ∗ -received_amount -decimal (20,10) ∗ -status -string ∗ -user_id -integer (8) ∗ FK + +BuyCryptoOrder + +currency_id +integer (8) ∗ FK +paid_amount_cents +integer ∗ +received_amount +decimal (20,10) ∗ +status +string ∗ +user_id +integer (8) ∗ FK m_Currency - -Currency - -name -string ∗ + +Currency + +name +string ∗ m_Currency->m_Balance - - + + m_Currency->m_BuyCryptoOrder - - + + m_SellCryptoOrder - -SellCryptoOrder - -currency_id -integer (8) ∗ FK -paid_amount -decimal (20,10) ∗ -received_amount_cents -integer ∗ -status -string ∗ -user_id -integer (8) ∗ FK + +SellCryptoOrder + +currency_id +integer (8) ∗ FK +paid_amount +decimal (20,10) ∗ +received_amount_cents +integer ∗ +status +string ∗ +user_id +integer (8) ∗ FK m_Currency->m_SellCryptoOrder - - - - - -m_FiatBalance - -FiatBalance - -amount_cents -integer ∗ -amount_currency -string ∗ -user_id -integer (8) ∗ FK + + m_StakeOrder - -StakeOrder - -amount -decimal (20,10) ∗ -pool_name -string ∗ -status -string ∗ -user_id -integer (8) ∗ FK + +StakeOrder + +amount +decimal (20,10) ∗ +currency_id +integer (8) FK +pool_name +string ∗ +status +string ∗ +user_id +integer (8) ∗ FK + + + +m_Currency->m_StakeOrder + + + + + +m_FiatBalance + +FiatBalance + +amount_cents +integer ∗ +amount_currency +string ∗ +user_id +integer (8) ∗ FK m_User - -User - -email -string ∗ U -encrypted_password -string ∗ -first_name -string ∗ -last_name -string ∗ -remember_created_at -datetime -reset_password_sent_at -datetime -reset_password_token -string + +User + +email +string ∗ U +encrypted_password +string ∗ +first_name +string ∗ +last_name +string ∗ +remember_created_at +datetime +reset_password_sent_at +datetime +reset_password_token +string m_User->m_Balance - - + + m_User->m_BuyCryptoOrder - - + + m_User->m_FiatBalance - + m_User->m_SellCryptoOrder - - + + m_User->m_StakeOrder - - + + @@ -193,8 +201,8 @@ m_User->m_UserDocument - - + + diff --git a/spec/factories/stake_orders.rb b/spec/factories/stake_orders.rb index ef6182f..67d19b8 100644 --- a/spec/factories/stake_orders.rb +++ b/spec/factories/stake_orders.rb @@ -4,20 +4,23 @@ # # Table name: stake_orders # -# id :bigint not null, primary key -# amount :decimal(20, 10) default(0.0), not null -# pool_name :string not null -# status :string not null -# created_at :datetime not null -# updated_at :datetime not null -# user_id :bigint not null +# id :bigint not null, primary key +# amount :decimal(20, 10) default(0.0), not null +# pool_name :string not null +# status :string not null +# created_at :datetime not null +# updated_at :datetime not null +# currency_id :bigint +# user_id :bigint not null # # Indexes # -# index_stake_orders_on_user_id (user_id) +# index_stake_orders_on_currency_id (currency_id) +# index_stake_orders_on_user_id (user_id) # # Foreign Keys # +# fk_rails_... (currency_id => currencies.id) # fk_rails_... (user_id => users.id) # FactoryBot.define do diff --git a/spec/graphql/mutations/create_stake_order_spec.rb b/spec/graphql/mutations/create_stake_order_spec.rb new file mode 100644 index 0000000..fb2417d --- /dev/null +++ b/spec/graphql/mutations/create_stake_order_spec.rb @@ -0,0 +1,123 @@ +# frozen_string_literal: true + +require "rails_helper" + +RSpec.describe(Mutations::CreateStakeOrder, type: :mutation) do + let(:query_string) do + <<~GQL + mutation( + $currencyId: ID!, + $amount: String!, + $poolName: String!, + ) { + createStakeOrder(input: { + order: { + currencyId: $currencyId, + amount: $amount, + poolName: $poolName, + } + }) { + errors { + fullMessages + fieldName + messages + path + } + order { + poolName + status + amount + } + } + } + GQL + end + + context "when the user has enough balance" do + it "withdraws from his account and creates a buy order" do + currency = create(:currency) + user = create( + :user, + balances: [ + build(:balance, currency: currency, amount: 1.0034), + ] + ) + + currency_global_id = GraphQL::Schema::UniqueWithinType.encode("Currency", currency.id) + + variables = { + "currencyId": currency_global_id, + "amount": "0.80", + "poolName": "CAKE/BNB", + "status": "PROCESSING", + } + + context = { current_user: user } + + result = XStakeSchema.execute( + query_string, + variables: variables, + context: context + ).to_h.with_indifferent_access + + expect(result).to(eq({ + "data" => { + "createStakeOrder" => { + "errors" => nil, + "order" => { + "status" => "PROCESSING", + "amount" => "0.8", + "poolName" => "CAKE/BNB", + }, + }, + }, + })) + + expect(user.balances.first.reload.amount.to_s).to(eq("0.2034")) + end + end + + context "when the user does not have enough balance" do + it "returns withdrawl error" do + currency = create(:currency) + user = create( + :user, + balances: [ + build(:balance, currency: currency, amount: 0.0034), + ] + ) + + currency_global_id = GraphQL::Schema::UniqueWithinType.encode("Currency", currency.id) + + variables = { + "currencyId": currency_global_id, + "amount": "0.80", + "poolName": "CAKE/BNB", + } + + context = { current_user: user } + + result = XStakeSchema.execute( + query_string, + variables: variables, + context: context + ).to_h.with_indifferent_access + + expect(result).to(eq({ + "data" => { + "createStakeOrder" => { + "errors" => [{ + "fullMessages" => ["Quantia saldo insuficiente"], + "fieldName" => "amount", + "messages" => ["saldo insuficiente"], + "path" => ["attributes", "amount"], + }], + "order" => nil, + }, + }, + })) + + expect(user.balances.first.reload.amount.to_s).to(eq("0.0034")) + end + end +end diff --git a/spec/models/stake_order_spec.rb b/spec/models/stake_order_spec.rb index c2f0cf5..2cb1448 100644 --- a/spec/models/stake_order_spec.rb +++ b/spec/models/stake_order_spec.rb @@ -4,20 +4,23 @@ # # Table name: stake_orders # -# id :bigint not null, primary key -# amount :decimal(20, 10) default(0.0), not null -# pool_name :string not null -# status :string not null -# created_at :datetime not null -# updated_at :datetime not null -# user_id :bigint not null +# id :bigint not null, primary key +# amount :decimal(20, 10) default(0.0), not null +# pool_name :string not null +# status :string not null +# created_at :datetime not null +# updated_at :datetime not null +# currency_id :bigint +# user_id :bigint not null # # Indexes # -# index_stake_orders_on_user_id (user_id) +# index_stake_orders_on_currency_id (currency_id) +# index_stake_orders_on_user_id (user_id) # # Foreign Keys # +# fk_rails_... (currency_id => currencies.id) # fk_rails_... (user_id => users.id) # require "rails_helper" @@ -30,5 +33,6 @@ RSpec.describe(StakeOrder, type: :model) do describe "associations" do it { is_expected.to(belong_to(:user)) } + it { is_expected.to(belong_to(:currency)) } end end