Merge pull request 'Add basic donation records' (#18) from feature/donation_records into master
continuous-integration/drone/push Build is passing Details

Reviewed-on: #18
This commit is contained in:
Râu Cao 2020-12-21 14:46:50 +00:00
commit bcf5172956
30 changed files with 419 additions and 10 deletions

View File

@ -0,0 +1,40 @@
ul.donations {
list-style: none;
li {
margin-bottom: 2rem;
display: grid;
grid-row-gap: 0.5rem;
grid-column-gap: 2rem;
grid-template-columns: 1fr 1fr;
grid-template-areas:
"date amount-btc"
"public-name amounts-fiat";
h3 {
grid-area: "date";
margin-bottom: 0;
}
p {
margin-bottom: 0;
&.amount-btc {
grid-area: amount-btc;
text-align: right;
font-family: monospace;
font-size: 1.25rem;
}
&.amounts-fiat {
grid-area: amounts-fiat;
text-align: right;
font-family: monospace;
font-size: 0.85rem;
color: #888;
}
&.public-name {
grid-area: public-name;
}
}
}
}

View File

@ -22,10 +22,6 @@ form {
color: #bc0101;
}
.actions {
margin-top: 2rem;
}
.accept-terms {
margin-top: 2rem;
font-size: 0.85rem;

View File

@ -143,6 +143,10 @@ main {
margin-bottom: 3rem;
}
}
.actions {
margin-top: 2rem;
}
}
.grid {

View File

@ -0,0 +1,74 @@
class Admin::DonationsController < Admin::BaseController
before_action :set_donation, only: [:show, :edit, :update, :destroy]
# GET /donations
# GET /donations.json
def index
@donations = Donation.all
end
# GET /donations/1
# GET /donations/1.json
def show
end
# GET /donations/new
def new
@donation = Donation.new
end
# GET /donations/1/edit
def edit
end
# POST /donations
# POST /donations.json
def create
@donation = Donation.new(donation_params)
respond_to do |format|
if @donation.save
format.html { redirect_to admin_donation_url(@donation), notice: 'Donation was successfully created.' }
format.json { render :show, status: :created, location: @donation }
else
format.html { render :new }
format.json { render json: @donation.errors, status: :unprocessable_entity }
end
end
end
# PATCH/PUT /donations/1
# PATCH/PUT /donations/1.json
def update
respond_to do |format|
if @donation.update(donation_params)
format.html { redirect_to admin_donation_url(@donation), notice: 'Donation was successfully updated.' }
format.json { render :show, status: :ok, location: @donation }
else
format.html { render :edit }
format.json { render json: @donation.errors, status: :unprocessable_entity }
end
end
end
# DELETE /donations/1
# DELETE /donations/1.json
def destroy
@donation.destroy
respond_to do |format|
format.html { redirect_to admin_donations_url, notice: 'Donation was successfully destroyed.' }
format.json { head :no_content }
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_donation
@donation = Donation.find(params[:id])
end
# Only allow a list of trusted parameters through.
def donation_params
params.require(:donation).permit(:user_id, :amount_sats, :amount_eur, :amount_usd, :public_name, :paid_at)
end
end

View File

@ -0,0 +1,9 @@
class DonationsController < ApplicationController
before_action :require_user_signed_in
# GET /donations
# GET /donations.json
def index
@donations = current_user.donations.completed
end
end

View File

@ -1,2 +1,5 @@
module ApplicationHelper
def sats_to_btc(sats)
sats.to_f / 100000000
end
end

View File

@ -0,0 +1,2 @@
module DonationsHelper
end

13
app/models/donation.rb Normal file
View File

@ -0,0 +1,13 @@
class Donation < ApplicationRecord
# Relations
belongs_to :user
# Validations
validates_presence_of :amount_sats
# Hooks
# TODO before_create :store_fiat_value
#Scopes
scope :completed, -> { where.not(paid_at: nil) }
end

View File

@ -3,6 +3,7 @@ class User < ApplicationRecord
# Relations
has_many :invitations, dependent: :destroy
has_many :donations, dependent: :nullify
validates_uniqueness_of :cn
validates_length_of :cn, :minimum => 3

View File

@ -2,6 +2,7 @@
<p>
Ohai there, admin human.
</p>
<p>
<%= link_to 'LDAP users', admin_ldap_users_path %>
</p>
<ul>
<li><%= link_to 'LDAP users', admin_ldap_users_path %></li>
<li><%= link_to 'Donations', admin_donations_path %></li>
</ul>

View File

@ -0,0 +1,2 @@
json.extract! donation, :id, :user_id, :amount_sats, :amount_eur, :amount_usd, :public_name, :created_at, :updated_at
json.url donation_url(donation, format: :json)

View File

@ -0,0 +1,60 @@
<%= form_with(url: url, model: donation, local: true) do |form| %>
<% if donation.errors.any? %>
<div id="error_explanation">
<h3><%= pluralize(donation.errors.count, "error") %> prohibited this donation from being saved:</h3>
<ul>
<% donation.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<p>
<%= form.label :user_id %>
<%= form.collection_select :user_id, User.where(ou: "kosmos.org").order(:cn), :id, :cn %>
</p>
</div>
<div class="field">
<p>
<%= form.label :amount_sats, "Amount BTC (sats)" %>
<%= form.number_field :amount_sats %>
</p>
</div>
<div class="field">
<p>
<%= form.label :amount_eur, "Amount EUR (cents)" %>
<%= form.number_field :amount_eur %>
</p>
</div>
<div class="field">
<p>
<%= form.label :amount_usd, "Amount USD (cents)"%>
<%= form.number_field :amount_usd %>
</p>
</div>
<div class="field">
<p>
<%= form.label :public_name %>
<%= form.text_field :public_name %>
</p>
</div>
<div class="field">
<p>
<%= form.label :paid_at %>
<%= form.text_field :paid_at %>
</p>
</div>
<div class="actions">
<p>
<%= form.submit %>
</p>
</div>
<% end %>

View File

@ -0,0 +1,8 @@
<h2>Editing Donation</h2>
<%= render 'form', donation: @donation, url: admin_donation_path(@donation) %>
<p class="actions">
<%= link_to 'Show', admin_donation_path(@donation) %> |
<%= link_to 'Back', admin_donations_path %>
<p>

View File

@ -0,0 +1,41 @@
<h2>Donations</h2>
<% if @donations.any? %>
<table>
<thead>
<tr>
<th>User</th>
<th>Amount BTC</th>
<th>in EUR</th>
<th>in USD</th>
<th>Public name</th>
<th>Date</th>
<th colspan="3"></th>
</tr>
</thead>
<tbody>
<% @donations.each do |donation| %>
<tr>
<td><%= donation.user.cn %></td>
<td><%= sats_to_btc donation.amount_sats %> BTC</td>
<td><%= number_to_currency donation.amount_eur / 100, unit: "" %></td>
<td><%= number_to_currency donation.amount_usd / 100, unit: "" %></td>
<td><%= donation.public_name %></td>
<td><%= donation.paid_at ? donation.paid_at.strftime("%Y-%m-%d") : "" %></td>
<td><%= link_to 'Show', admin_donation_path(donation) %></td>
<td><%= link_to 'Edit', edit_admin_donation_path(donation) %></td>
<td><%= link_to 'Destroy', admin_donation_path(donation), method: :delete, data: { confirm: 'Are you sure?' } %></td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p>
No donations yet.
</p>
<% end %>
<p class="actions">
<%= link_to 'Record an out-of-system donation', new_admin_donation_path %>
</p>

View File

@ -0,0 +1 @@
json.array! @donations, partial: "donations/donation", as: :donation

View File

@ -0,0 +1,7 @@
<h2>New Donation</h2>
<%= render 'form', donation: @donation, url: admin_donations_path %>
<p class="actions">
<%= link_to 'Back', admin_donations_path %>
</p>

View File

@ -0,0 +1,34 @@
<p id="notice"><%= notice %></p>
<p>
<strong>User:</strong>
<%= @donation.user_id %>
</p>
<p>
<strong>Amount sats:</strong>
<%= @donation.amount_sats %>
</p>
<p>
<strong>Amount eur:</strong>
<%= @donation.amount_eur %>
</p>
<p>
<strong>Amount usd:</strong>
<%= @donation.amount_usd %>
</p>
<p>
<strong>Public name:</strong>
<%= @donation.public_name %>
</p>
<p>
<strong>Date:</strong>
<%= @donation.paid_at %>
</p>
<%= link_to 'Edit', edit_admin_donation_path(@donation) %> |
<%= link_to 'Back', admin_donations_path %>

View File

@ -0,0 +1 @@
json.partial! "donations/donation", donation: @donation

View File

@ -0,0 +1,38 @@
<section>
<h2>Donations</h2>
<p>
Your financial contributions to the development and
upkeep of Kosmos software and services.
</p>
</section>
<section>
<% if @donations.any? %>
<ul class="donations">
<% @donations.each do |donation| %>
<li>
<h3>
<%= donation.paid_at.strftime("%B %d, %Y") %>
</h3>
<p class="amount-btc">
<%= sats_to_btc donation.amount_sats %> BTC
</p>
<p class="amounts-fiat">
(~ <%= number_to_currency donation.amount_eur / 100, unit: "" %> EUR)
</p>
<p class="public-name">
<% if donation.public_name.present? %>
Public name: <%= donation.public_name %>
<% else %>
Anonymous
<% end %>
</p>
</li>
<% end %>
</ul>
<% else %>
<p>
No donations to show.
</p>
<% end %>
</section>

View File

@ -0,0 +1 @@
json.array! @donations, partial: "donations/donation", as: :donation

View File

@ -29,7 +29,7 @@
<thead>
<tr>
<th>URL</th>
<th>Used at</th>
<th>Accepted</th>
<th>Invited user</th>
</tr>
</thead>
@ -37,7 +37,7 @@
<% @invitations_used.each do |invitation| %>
<tr>
<td><%= invitation_url(invitation.token) %></td>
<td><%= invitation.used_at %></td>
<td><%= invitation.used_at.strftime("%Y-%m-%d") %></td>
<td><%= User.find(invitation.invited_user_id).address %></td>
</tr>
<% end %>

View File

@ -1,4 +1,5 @@
Rails.application.routes.draw do
resources :donations
devise_for :users
get 'welcome', to: 'welcome#index'
@ -16,6 +17,7 @@ Rails.application.routes.draw do
namespace :admin do
root to: 'dashboard#index'
get 'ldap_users', to: 'ldap_users#index'
resources :donations
end
# Letter Opener (open "sent" emails in dev and staging)

View File

@ -8,6 +8,7 @@ class CreateInvitations < ActiveRecord::Migration[6.0]
t.timestamps
end
add_index :invitations, :user_id
add_index :invitations, :invited_user_id
end

View File

@ -0,0 +1,15 @@
class CreateDonations < ActiveRecord::Migration[6.0]
def change
create_table :donations do |t|
t.integer :user_id
t.integer :amount_sats
t.integer :amount_eur
t.integer :amount_usd
t.string :public_name
t.timestamps
end
add_index :donations, :user_id
end
end

View File

@ -0,0 +1,5 @@
class AddPaidAtToDonations < ActiveRecord::Migration[6.0]
def change
add_column :donations, :paid_at, :datetime
end
end

View File

@ -10,7 +10,19 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2020_11_30_132533) do
ActiveRecord::Schema.define(version: 2020_12_19_121808) do
create_table "donations", force: :cascade do |t|
t.integer "user_id"
t.integer "amount_sats"
t.integer "amount_eur"
t.integer "amount_usd"
t.string "public_name"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.datetime "paid_at"
t.index ["user_id"], name: "index_donations_on_user_id"
end
create_table "invitations", force: :cascade do |t|
t.string "token"

View File

@ -0,0 +1,9 @@
FactoryBot.define do
factory :donation do
user_id { 1 }
amount_sats { 100000 }
amount_eur { 10 }
amount_usd { 13 }
public_name { nil }
end
end

View File

@ -0,0 +1,9 @@
require 'rails_helper'
describe ApplicationHelper do
describe "sats_to_btc" do
it "converts satoshis to BTC" do
expect(helper.sats_to_btc(120000000)).to eq(1.2)
end
end
end

View File

@ -0,0 +1,15 @@
require 'rails_helper'
# Specs in this file have access to a helper object that includes
# the DonationsHelper. For example:
#
# describe DonationsHelper 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 DonationsHelper, type: :helper do
pending "add some examples to (or delete) #{__FILE__}"
end

View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe Donation, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end