diff --git a/app/assets/javascripts/requests.coffee b/app/assets/javascripts/requests.coffee new file mode 100644 index 0000000..24f83d1 --- /dev/null +++ b/app/assets/javascripts/requests.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/stylesheets/requests.scss b/app/assets/stylesheets/requests.scss new file mode 100644 index 0000000..45c818a --- /dev/null +++ b/app/assets/stylesheets/requests.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the requests controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index fd7c718..42b9a09 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -5,5 +5,4 @@ class PagesController < ApplicationController def landing @statistics = Statistics.new end - end diff --git a/app/controllers/requests_controller.rb b/app/controllers/requests_controller.rb new file mode 100644 index 0000000..3c606b2 --- /dev/null +++ b/app/controllers/requests_controller.rb @@ -0,0 +1,26 @@ +class RequestsController < ApplicationController + load_and_authorize_resource :user, only: :index + + before_action :load_request, only: [:confirm, :decline] + authorize_resource :request, only: [:confirm, :decline] + + def index + @requests = User.find(params[:user_id]).incoming_requests.group_by(&:status) + end + + def confirm + @request.confirm! + redirect_to user_requests_path(@request.debtor) + end + + def decline + @request.decline! + redirect_to user_requests_path(@request.debtor) + end + + private + + def load_request + @request = Request.find params[:request_id] + end +end diff --git a/app/controllers/transactions_controller.rb b/app/controllers/transactions_controller.rb index 2aad2b2..fe4cdc3 100644 --- a/app/controllers/transactions_controller.rb +++ b/app/controllers/transactions_controller.rb @@ -9,13 +9,22 @@ class TransactionsController < ApplicationController def create @transaction = Transaction.new(transaction_params) @transaction.reverse if @transaction.amount < 0 - authorize!(:create, @transaction) - if @transaction.save - render json: @transaction, status: :created + if can? :create, @transaction + if @transaction.save + render json: @transaction, status: :created + else + render json: @transaction.errors.full_messages, + status: :unprocessable_entity + end else - render json: @transaction.errors.full_messages, - status: :unprocessable_entity + request = Request.new @transaction.info + if request.save + render json: request, status: :created + else + render json: request.errors.full_messages, + status: :unprocessable_entity + end end end diff --git a/app/helpers/requests_helper.rb b/app/helpers/requests_helper.rb new file mode 100644 index 0000000..53ac95c --- /dev/null +++ b/app/helpers/requests_helper.rb @@ -0,0 +1,2 @@ +module RequestsHelper +end diff --git a/app/models/request.rb b/app/models/request.rb new file mode 100644 index 0000000..f6c33d7 --- /dev/null +++ b/app/models/request.rb @@ -0,0 +1,33 @@ +class Request < ActiveRecord::Base + belongs_to :debtor, class_name: 'User' + belongs_to :creditor, class_name: 'User' + belongs_to :issuer, polymorphic: true + + validates :amount, numericality: { greater_than: 0 } + validate :different_debtor_creditor + + enum status: [:open, :confirmed, :declined] + + def confirm! + return unless open? + + Transaction.create attributes.symbolize_keys.extract!( + :debtor_id, :creditor_id, :issuer_id, :issuer_type, :amount, :message + ) + update_attributes status: :confirmed + end + + def decline! + return unless open? + + update_attributes status: :declined + end + + private + + def different_debtor_creditor + if self.debtor == self.creditor + self.errors.add :base, "Can't write money to yourself" + end + end +end diff --git a/app/models/transaction.rb b/app/models/transaction.rb index 7d82df8..bc31c92 100644 --- a/app/models/transaction.rb +++ b/app/models/transaction.rb @@ -42,6 +42,10 @@ class Transaction < ActiveRecord::Base self.amount *= -1 end + def info + attributes.symbolize_keys.extract!(:debtor_id, :creditor_id, :issuer_id, :issuer_type, :message, :amount) + end + private def recalculate_balances diff --git a/app/models/user.rb b/app/models/user.rb index d3c234b..65afd6a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -18,6 +18,10 @@ class User < ActiveRecord::Base class_name: 'Transaction', foreign_key: 'creditor_id' has_many :outgoing_transactions, class_name: 'Transaction', foreign_key: 'debtor_id' + has_many :incoming_requests, + class_name: 'Request', foreign_key: 'debtor_id' + has_many :outgoing_requests, + class_name: 'Request', foreign_key: 'debtor_id' has_many :issued_transactions, as: :issuer, class_name: 'Transaction' diff --git a/app/models/user_ability.rb b/app/models/user_ability.rb index 196d854..51fff98 100644 --- a/app/models/user_ability.rb +++ b/app/models/user_ability.rb @@ -5,7 +5,8 @@ class UserAbility return unless user can :manage, :all if user.penning? - can :read, user, id: user.id + can :read, user, id: user.id + can :manage, Request, user_id: user.id can :create, Transaction do |t| t.debtor == user && t.amount <= Rails.application.config.maximum_amount end diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index bdd6542..249bd97 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -17,6 +17,8 @@ - if current_user.penning %li.pure-menu-item =link_to "Zeus", User.zeus, class: "pure-menu-link" + %li.pure-menu-item + = link_to 'Requests', user_requests_path(current_user), class: 'pure-menu-link' .pure-u-1 = render 'partials/flash' = yield diff --git a/app/views/requests/_index.html.haml b/app/views/requests/_index.html.haml new file mode 100644 index 0000000..6b33984 --- /dev/null +++ b/app/views/requests/_index.html.haml @@ -0,0 +1,25 @@ +%h4= title +%table.pure-table + %thead + %tr + %th Peer + %th Issuer + %th Amount + %th Message + - if actions + %th Accept + %th Decline + %tbody + - (requests || []).each do |r| + %tr + %td= r.creditor.name + %td= r.issuer.name + %td= "€#{r.amount/100.0}" + %td= r.message + - if actions + %td + = link_to request_confirm_path(r), method: :post do + %span.glyphicon.glyphicon-ok + %td + = link_to request_decline_path(r), method: :post do + %span.glyphicon.glyphicon-remove diff --git a/app/views/requests/index.html.haml b/app/views/requests/index.html.haml new file mode 100644 index 0000000..e56d57c --- /dev/null +++ b/app/views/requests/index.html.haml @@ -0,0 +1,3 @@ += render 'index', actions: true, title: 'Open Requests', requests: @requests['open'] += render 'index', actions: false, title: 'Confirmed Requests', requests: @requests['confirmed'] += render 'index', actions: false, title: 'Declined Requests', requests: @requests['declined'] diff --git a/app/views/transactions/_new.html.haml b/app/views/transactions/_new.html.haml index cb4be2c..9b2a6db 100644 --- a/app/views/transactions/_new.html.haml +++ b/app/views/transactions/_new.html.haml @@ -13,7 +13,7 @@ %span.input-group-addon %span.glyphicon.glyphicon-euro = f.number_field :euros, value: amount(@transaction.amount), - placeholder: "Amount", step: 0.01, min: (0.01 unless current_user.penning), + placeholder: "Amount", step: 0.01, class: "form-control", size: 20, required: true, max: (Rails.application.config.maximum_amount/100 unless current_user.penning) = f.submit "Send it!", class: "pure-button pure-button-primary btn" diff --git a/config/routes.rb b/config/routes.rb index e4d543e..9c68f28 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,7 +6,12 @@ Rails.application.routes.draw do root to: 'pages#landing' resources :transactions, only: [:index, :create] - resources :users, only: [:show, :index] + resources :users, only: [:index, :show] do + resources :requests, only: [:index], shallow: true do + post :confirm + post :decline + end + end get 'datatables/:id' => 'datatables#transactions_for_user', as: "user_transactions" end diff --git a/db/migrate/20170109123717_create_requests.rb b/db/migrate/20170109123717_create_requests.rb new file mode 100644 index 0000000..1f1eca3 --- /dev/null +++ b/db/migrate/20170109123717_create_requests.rb @@ -0,0 +1,18 @@ +class CreateRequests < ActiveRecord::Migration + def change + create_table :requests do |t| + t.references :debtor, index: true, null: false + t.references :creditor, index: true, null: false + t.references :issuer, polymorphic: true, index: true, null: false + t.integer :amount, null: false, default: 0 + t.string :message + + t.integer :status, default: 0 + + t.timestamps null: false + end + + add_foreign_key :request, :users, column: :creditor_id + add_foreign_key :request, :users, column: :debtor_id + end +end diff --git a/db/schema.rb b/db/schema.rb index d784c74..7bbf575 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20150914095049) do +ActiveRecord::Schema.define(version: 20170109123717) do create_table "clients", force: :cascade do |t| t.string "name", null: false @@ -23,6 +23,22 @@ ActiveRecord::Schema.define(version: 20150914095049) do add_index "clients", ["key"], name: "index_clients_on_key" add_index "clients", ["name"], name: "index_clients_on_name" + create_table "requests", force: :cascade do |t| + t.integer "debtor_id", null: false + t.integer "creditor_id", null: false + t.integer "issuer_id", null: false + t.string "issuer_type", null: false + t.integer "amount", default: 0, null: false + t.string "message" + t.integer "status", default: 0 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "requests", ["creditor_id"], name: "index_requests_on_creditor_id" + add_index "requests", ["debtor_id"], name: "index_requests_on_debtor_id" + add_index "requests", ["issuer_type", "issuer_id"], name: "index_requests_on_issuer_type_and_issuer_id" + create_table "transactions", force: :cascade do |t| t.integer "debtor_id", null: false t.integer "creditor_id", null: false diff --git a/spec/controllers/requests_controller_spec.rb b/spec/controllers/requests_controller_spec.rb new file mode 100644 index 0000000..f5fc12f --- /dev/null +++ b/spec/controllers/requests_controller_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe RequestsController, type: :controller do + +end diff --git a/spec/factories/requests.rb b/spec/factories/requests.rb new file mode 100644 index 0000000..e7d79e9 --- /dev/null +++ b/spec/factories/requests.rb @@ -0,0 +1,6 @@ +FactoryGirl.define do + factory :request do + + end + +end diff --git a/spec/helpers/requests_helper_spec.rb b/spec/helpers/requests_helper_spec.rb new file mode 100644 index 0000000..a2ab326 --- /dev/null +++ b/spec/helpers/requests_helper_spec.rb @@ -0,0 +1,15 @@ +require 'rails_helper' + +# Specs in this file have access to a helper object that includes +# the RequestsHelper. For example: +# +# describe RequestsHelper do +# describe "string concat" do +# it "concats two strings with spaces" do +# expect(helper.concat_strings("this","that")).to eq("this that") +# end +# end +# end +RSpec.describe RequestsHelper, type: :helper do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/spec/models/request_spec.rb b/spec/models/request_spec.rb new file mode 100644 index 0000000..8ccca08 --- /dev/null +++ b/spec/models/request_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Request, type: :model do + pending "add some examples to (or delete) #{__FILE__}" +end