diff --git a/Gemfile b/Gemfile index 1d3f88a5..45f50ee9 100644 --- a/Gemfile +++ b/Gemfile @@ -83,4 +83,5 @@ group :test do gem 'rspec-rails' gem 'factory_girl_rails' gem 'poltergeist' + gem 'timecop' end diff --git a/Gemfile.lock b/Gemfile.lock index 9d04d9c1..27cc69ab 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -284,6 +284,7 @@ GEM thread_safe (0.1.3) atomic tilt (1.4.1) + timecop (0.7.1) tins (0.13.1) treetop (1.4.15) polyglot @@ -345,6 +346,7 @@ DEPENDENCIES sdoc sqlite3 therubyracer + timecop turbolinks twitter-bootstrap-rails! uglifier (>= 1.3.0) diff --git a/app/controllers/distributions_controller.rb b/app/controllers/distributions_controller.rb index 809fca4c..f032c7e3 100644 --- a/app/controllers/distributions_controller.rb +++ b/app/controllers/distributions_controller.rb @@ -30,9 +30,9 @@ def show def send_transaction @distribution.send_transaction! - redirect_to [@project, @distribution], notice: "Transaction sent" + redirect_to [@project, @distribution], flash: {notice: "Transaction sent"} rescue RuntimeError => e - redirect_to [@project, @distribution], error: e.message + redirect_to [@project, @distribution], flash: {error: e.message} end private diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 8ed0650e..079c8349 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -16,7 +16,7 @@ def index def update if @user.update(users_params) - redirect_to @user, notice: 'Your information saved!' + redirect_to @user, notice: 'Your information was saved.' else render :show, alert: 'Error updating peercoin address' end diff --git a/app/models/distribution.rb b/app/models/distribution.rb index 4df3fdbf..6d2f2077 100644 --- a/app/models/distribution.rb +++ b/app/models/distribution.rb @@ -7,21 +7,21 @@ class Distribution < ActiveRecord::Base scope :error, -> { where(is_error: true) } def sent? - !!txid + sent_at or txid end def total_amount - JSON.parse(data).values.map(&:to_d).sum if data + tips.map(&:amount).compact.sum end def send_transaction! Distribution.transaction do lock! - return if sent? - return if is_error? - return if project.disabled? + raise "Already sent" if sent? + raise "Transaction already sent and failed" if is_error? + raise "Project disabled" if project.disabled? - update_attribute :is_error, true # it's a lock to prevent duplicates + update!(sent_at: Time.now, is_error: true) # it's a lock to prevent duplicates end data = generate_data @@ -31,8 +31,7 @@ def send_transaction! txid = BitcoinDaemon.instance.send_many(project.address_label, JSON.parse(data)) - update_attribute :txid, txid - update_attribute :is_error, false + update!(txid: txid, is_error: false) end def generate_data @@ -42,4 +41,8 @@ def generate_data end outs.to_json end + + def all_addresses_known? + tips.all? { |tip| tip.user.bitcoin_address.present? } + end end diff --git a/app/models/tip.rb b/app/models/tip.rb index 95eafd8f..3888b06d 100644 --- a/app/models/tip.rb +++ b/app/models/tip.rb @@ -91,6 +91,7 @@ def notify_user end def notify_user_if_just_decided + return if distribution_id notify_user if amount_was.nil? and amount end diff --git a/app/views/distributions/index.html.haml b/app/views/distributions/index.html.haml index fd564a4c..28a73812 100644 --- a/app/views/distributions/index.html.haml +++ b/app/views/distributions/index.html.haml @@ -1,14 +1,20 @@ -%h1 Last Withdrawals +%h1 Distributions +- if @project + %h2= @project.name %p %table.table %thead %tr %th Created At + %th Sent at %th Transaction %th Result + %th %tbody - @distributions.each do |distribution| %tr %td= l distribution.created_at, format: :short - %td= link_to distribution.txid, transaction_url(distribution.txid), target: '_blank' - %td= distribution.is_error ? "Error" : "Success" + %td= l distribution.sent_at, format: :short if distribution.sent_at + %td= link_to distribution.txid, transaction_url(distribution.txid), target: '_blank' if distribution.txid.present? + %td= distribution.is_error ? "Error" : "Success" if distribution.sent? + %td= link_to "Details", [distribution.project, distribution], class: "btn btn-success" diff --git a/app/views/distributions/show.html.haml b/app/views/distributions/show.html.haml index 234dbd38..dc9c1808 100644 --- a/app/views/distributions/show.html.haml +++ b/app/views/distributions/show.html.haml @@ -16,7 +16,20 @@ %td.address= tip.user.bitcoin_address %td.amount= btc_human tip.amount %td.percentage= number_to_percentage(tip.amount.to_f * 100 / total, precision: 1) - - if @distribution.sent? - Sent + - if @distribution.is_error? + %p.alert.alert-danger + The transaction failed. + - elsif @distribution.sent? + %p.alert.alert-success + Transaction sent + - if @distribution.sent_at + on #{l(@distribution.sent_at)} + - elsif !@distribution.all_addresses_known? + %p.alert.alert-warning + The transaction cannot be sent because some addresses are missing. Ask the recipients to log in with their GitHub account. - elsif can? :send_transaction, @distribution = button_to "Send the transaction", send_transaction_project_distribution_path(@project, @distribution), class: "btn btn-primary", data: {confirm: "#{total.to_f / COIN} peercoins will be sent. Are you sure?"} + + - if @distribution.data.present? + %h4 Raw data + %pre= @distribution.data diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index b148a5ea..bfff7c55 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -19,42 +19,11 @@ = link_to "New distribution", new_project_distribution_path(@project), class: "btn btn-warning" .row - .col-md-4 - - if @project.disabled? - .panel.panel-danger - .panel-heading - %h4.panel-title - Project Disabled - .panel-body.text-center - %p - This project has been disabled. - It doesn't accept donation and it will not distribute tips. - - if (reason = @project.disabled_reason).present? - %p Reason: #{reason} - - - else - .panel.panel-default.project-panel - .panel-heading - %h4.panel-title - Project Sponsors - .panel-body.text-center - %p To give to this project, send peercoins to this address: - %p.bitcoin-address - = @project.bitcoin_address - %p - = image_tag qrcode_project_path(@project, format: :svg), alt: @project.bitcoin_address, class: 'project qrcode' - %p #{100-(CONFIG["our_fee"]*100).round}% of deposited funds will be used to tip for new commits. .col-md-8 - unless @project.description.blank? %h3= @project.description - unless @project.detailed_description.blank? = render_markdown @project.detailed_description - %h4 Balance - = btc_human @project.available_amount - - if @project.auto_tip_commits? - (each new commit receives #{(CONFIG["tip"]*100).round}% of available balance) - - if (unconfirmed_amount = @project.unconfirmed_amount) > 0 - (#{btc_human unconfirmed_amount} unconfirmed) - if @project.tipping_policies_text.try(:text).present? %h4 Tipping policies @@ -138,3 +107,53 @@ %p= link_to image_tag(project_url(@project, format: :svg), alt: 'Peer4Commit'), project_url(@project) %p %input.form-control{type: 'text', value: "[![tip for next commit](#{project_url(@project, format: :svg)})](#{project_url(@project)})"} + + .col-md-4 + - if @project.disabled? + .panel.panel-danger + .panel-heading + %h4.panel-title + Project Disabled + .panel-body.text-center + %p + This project has been disabled. + It doesn't accept donation and it will not distribute tips. + - if (reason = @project.disabled_reason).present? + %p Reason: #{reason} + + - else + .panel.panel-default.project-panel + .panel-heading + %h4.panel-title + Project informations + .panel-body.text-center + %table.table.text-left + %tr + %td Funds + %td= btc_human @project.available_amount + %tr + %td Distributions + %td + %ul.list-unstyled#distribution-list + - @project.distributions.order(created_at: :desc).limit(5).each do |distribution| + %li.distribution-link + - label = btc_human(distribution.total_amount) + - if distribution.sent? + - label << " sent #{time_ago_in_words(distribution.sent_at)} ago" + - else + - label << " not sent" + = link_to label, [@project, distribution] + = link_to "All distributions", project_distributions_path(@project) + + + .panel.panel-default.project-panel + .panel-heading + %h4.panel-title + Donate + .panel-body.text-center + %p To give to this project, send peercoins to this address: + %p.bitcoin-address + = @project.bitcoin_address + %p + = image_tag qrcode_project_path(@project, format: :svg), alt: @project.bitcoin_address, class: 'project qrcode' + %p #{100-(CONFIG["our_fee"]*100).round}% of deposited funds will be used to tip for new commits. diff --git a/config/routes.rb b/config/routes.rb index 0395ee6c..5f9cb206 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -17,7 +17,7 @@ end resources :projects, :only => [:new, :show, :index, :create, :edit, :update] do resources :tips, :only => [:index] - resources :distributions, :only => [:new, :create, :show] do + resources :distributions, :only => [:new, :create, :show, :index] do get :recipient_suggestions, on: :collection post :send_transaction, on: :member end diff --git a/db/migrate/20140531080839_add_sent_at_to_distribution.rb b/db/migrate/20140531080839_add_sent_at_to_distribution.rb new file mode 100644 index 00000000..dc47d0fa --- /dev/null +++ b/db/migrate/20140531080839_add_sent_at_to_distribution.rb @@ -0,0 +1,5 @@ +class AddSentAtToDistribution < ActiveRecord::Migration + def change + add_column :distributions, :sent_at, :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index c04232a3..3852f4f2 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: 20140530132209) do +ActiveRecord::Schema.define(version: 20140531080839) do create_table "cold_storage_transfers", force: true do |t| t.integer "project_id" @@ -58,6 +58,7 @@ t.datetime "updated_at" t.integer "project_id" t.integer "fee" + t.datetime "sent_at" end add_index "distributions", ["project_id"], name: "index_distributions_on_project_id" diff --git a/features/distribution.feature b/features/distribution.feature index c8ae7ef8..67e946ba 100644 --- a/features/distribution.feature +++ b/features/distribution.feature @@ -1,6 +1,6 @@ Feature: Fundraisers can distribute funds @javascript - Scenario: + Scenario: Send distribution to a single user who has set his address Given a GitHub user "bob" who has set his address to "mxWfjaZJTNN5QKeZZYQ5HW3vgALFBsnuG1" Given a project managed by "alice" @@ -23,12 +23,14 @@ Feature: Fundraisers can distribute funds When the tipper is started Then no coins should have been sent + Given the current time is "2014-03-01 12:35:02 UTC" When I click on "Send the transaction" - Then these amounts should have been sent from the account of the project: + Then I should see "Transaction sent" + And I should see "Transaction sent on Sat, 01 Mar 2014 12:35:02 +0000" + And these amounts should have been sent from the account of the project: | address | amount | | mxWfjaZJTNN5QKeZZYQ5HW3vgALFBsnuG1 | 10.0 | And the project balance should be "490" - And I should see "Sent" @javascript Scenario: Send distribution to multiple users @@ -60,11 +62,56 @@ Feature: Fundraisers can distribute funds Then no coins should have been sent When I click on "Send the transaction" - Then these amounts should have been sent from the account of the project: + Then I should see "Transaction sent" + And these amounts should have been sent from the account of the project: | address | amount | | mxWfjaZJTNN5QKeZZYQ5HW3vgALFBsnuG1 | 10.0 | | mi9SLroAgc8eUNuLwnZmdyqWdShbNtvr3n | 13.56 | And the project balance should be "476.44" - And I should see "Sent" - Scenario: Send to an user without address + @javascript + Scenario: Send to an user without an address + Given a GitHub user "bob" + + Given a project managed by "alice" + And our fee is "0" + And a deposit of "500" + + Given I'm logged in as "alice" + And I go to the project page + And I click on "New distribution" + And I type "bob" in the recipient field + And I select the recipient "bob (GitHub user)" + And I fill the amount to "bob (GitHub user)" with "10" + And I click on "Save" + + Then I should see these distribution lines: + | recipient | address | amount | percentage | + | bob (GitHub user) | | 10 | 100.0 | + And I should see "Total amount: 10.00 PPC" + And I should not see "Send the transaction" + And I should see "The transaction cannot be sent because some addresses are missing" + + And no email should have been sent + + When the tipper is started + Then no coins should have been sent + + When I log out + And I log in as "bob" + And I set my address to "mnVba8qrpy5uxYD7dV4NZMQPWjgdt2QC1i" + + When I log out + And I log in as "alice" + And I go to the project page + And I click on the last distribution + Then I should see these distribution lines: + | recipient | address | amount | percentage | + | bob (GitHub user) | mnVba8qrpy5uxYD7dV4NZMQPWjgdt2QC1i | 10 | 100.0 | + + When I click on "Send the transaction" + Then I should see "Transaction sent" + And these amounts should have been sent from the account of the project: + | address | amount | + | mnVba8qrpy5uxYD7dV4NZMQPWjgdt2QC1i | 10.0 | + And the project balance should be "490.00" diff --git a/features/step_definitions/common.rb b/features/step_definitions/common.rb index 015917e6..63033515 100644 --- a/features/step_definitions/common.rb +++ b/features/step_definitions/common.rb @@ -6,6 +6,10 @@ ActionMailer::Base.deliveries.size.should eq(arg1.to_i) end +Then(/^no email should have been sent$/) do + ActionMailer::Base.deliveries.should eq([]) +end + When(/^the email counters are reset$/) do ActionMailer::Base.deliveries.clear end @@ -136,3 +140,12 @@ def find_new_commit(id) Given(/^an illustration of the history is:$/) do |string| # not checked end + +Given(/^the current time is "(.*?)"$/) do |arg1| + Timecop.travel(Time.parse(arg1)) +end + +After do + Timecop.return +end + diff --git a/features/step_definitions/distribution.rb b/features/step_definitions/distribution.rb index fd61736c..82df70db 100644 --- a/features/step_definitions/distribution.rb +++ b/features/step_definitions/distribution.rb @@ -3,6 +3,10 @@ create(:user, email: "#{arg1}@example.com", nickname: arg1, bitcoin_address: arg2) end +Given(/^a GitHub user "([^"]*?)"$/) do |arg1| + create(:user, email: "#{arg1}@example.com", nickname: arg1, bitcoin_address: nil) +end + Given(/^I type "(.*?)" in the recipient field$/) do |arg1| fill_in "add-recipients-input", with: arg1 end @@ -23,7 +27,7 @@ table.hashes.each do |row| tr = find("#distribution-show-page tr", text: row["recipient"]) tr.find(".recipient").should have_content(row["recipient"]) - tr.find(".address").should have_content(row["address"]) + tr.find(".address").text.should eq(row["address"]) tr.find(".amount").should have_content(row["amount"]) tr.find(".percentage").should have_content(row["percentage"]) end @@ -48,3 +52,14 @@ BitcoinDaemon.instance.list_transactions("*").should eq([]) end +When(/^I set my address to "(.*?)"$/) do |arg1| + visit user_path(@current_user) + fill_in "Peercoin address", with: arg1 + click_on "Update" + page.should have_content "Your information was saved" +end + +When(/^I click on the last distribution$/) do + find("#distribution-list .distribution-link:first-child").click +end + diff --git a/features/step_definitions/web.rb b/features/step_definitions/web.rb index ca696208..5630459e 100644 --- a/features/step_definitions/web.rb +++ b/features/step_definitions/web.rb @@ -10,6 +10,7 @@ visit root_path click_on "Sign in" page.should have_content("Successfully authenticated") + @current_user = User.find_by(nickname: arg1) end Given(/^I'm logged in on GitHub as "(.*?)"$/) do |arg1| @@ -33,6 +34,16 @@ end end +When(/^I log out$/) do + click_on "Sign Out" + page.should have_content "Signed out successfully" +end + +When(/^I log in as "(.*?)"$/) do |arg1| + step "I'm logged in as \"#{arg1}\"" +end + + When(/^I visit the home page$/) do visit '/' end @@ -80,7 +91,7 @@ end Then(/^I should see the project balance is "(.*?)"$/) do |arg1| - page.should have_content("Balance #{arg1}") + page.should have_content("Funds #{arg1}") end Then(/^I should see a link "(.*?)" to "(.*?)"$/) do |arg1, arg2|