Skip to content

Commit

Permalink
Use Ruby instead of JS for getting donations data from Notion
Browse files Browse the repository at this point in the history
  • Loading branch information
patbl committed Feb 8, 2024
1 parent 451352b commit b719229
Showing 9 changed files with 128 additions and 1,315 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -17,6 +17,8 @@ jobs:
- name: Install and Build 🔧 # This example project is built using npm and outputs the result to the 'build' folder. Replace with the commands required to build your project, or remove this step entirely if your site is pre-built.
run: |
bundle exec middleman build
env:
NOTION_DONATIONS_UPDATER_API_KEY: ${{ secrets.NOTION_DONATIONS_UPDATER_API_KEY }}

- name: Deploy 🚀
uses: JamesIves/github-pages-deploy-action@v4
79 changes: 39 additions & 40 deletions lib/donation.rb
Original file line number Diff line number Diff line change
@@ -1,82 +1,81 @@
# frozen_string_literal: true

class Donation
ATTRIBUTES = [
{name: :organization},
{name: :date},
{name: :amount},
{name: :is_grant, new_name: :grant?, default: false},
{name: :is_daf_contribution, new_name: :daf_contribution?, default: false},
{name: :note, default: nil},
:organization,
:date,
:amount,
:is_grant,
:is_daf_contribution,
:note,
]
def initialize(values)
ATTRIBUTES.each do |attrs|
original_name = attrs.fetch(:name)
new_name = attrs.fetch(:new_name, original_name)
value = values.fetch(original_name) { attrs.fetch(:default) }
if new_name == :date
value = Date.parse(value)
end
instance_variable_set(:"@#{original_name}", value)
define_singleton_method(new_name) do
instance_variable_get(:"@#{original_name}")
ATTRIBUTES.each do |attr|
value = values.fetch(attr)
instance_variable_set(:"@#{attr}", value)
define_singleton_method(attr) do
instance_variable_get(:"@#{attr}")
end
end
end

def amount_donated_by_me
grant? ? 0 : amount
is_grant ? 0 : amount
end

def amount_received_by_charity
daf_contribution? ? 0 : amount
is_daf_contribution ? 0 : amount
end

def formatted_date
date.strftime("%-d %B %Y")
end

def url
case organization
when "EA Giving Group donor-advised fund"
return "/misc/other/donations/ea_giving_group.html"
end

return unless (org_data = organizations[organization])

org_data.fetch("url")
org_data = self.class.organizations[organization] or return
org_data.fetch(:url)
end

def organizations
@organizations ||= File.read("lib/organizations.json")
.then { |json| JSON.parse(json) }
.index_by { |org| org.fetch("name") }
def self.organizations
@organizations ||= donation_data_fetcher
.organizations
.index_by { |org| org.fetch(:name) }
end

def css_class
if grant?
if is_grant
"text-grey-dark"
end
end

def self.load_donations(*)
File.read("lib/donations.json")
.then { |json| JSON.parse(json) }
def self.load_donations
@donations ||= donation_data_fetcher
.donations
.map { |donation| Donation.new(donation.symbolize_keys) }
.sort_by { |donation| [donation.date, donation.organization, donation.amount, donation.note] }
.reverse
end

def self.total_donated_by_me(show_hidden:)
load_donations(show_hidden: show_hidden).sum(&:amount_donated_by_me)
def self.total_donated_by_me
load_donations.sum(&:amount_donated_by_me)
end

def self.total_received_by_charities(show_hidden:)
load_donations(show_hidden: show_hidden).sum(&:amount_received_by_charity)
def self.total_received_by_charities
load_donations.sum(&:amount_received_by_charity)
end

def self.donations_by_year(show_hidden:)
load_donations(show_hidden: show_hidden)
def self.donations_by_year
load_donations
.group_by { |donation| donation.date.year }
.sort_by(&:first)
.reverse
end

def self.donation_data_fetcher
@donation_data_fetcher ||= DonationDataFetcher.new(
ENV['NOTION_DONATIONS_UPDATER_API_KEY'],
"6721200455be4b7c820df4a9ce51fd30",
"738df83195f74d66b466c71519dc5a1b",
)
end
end
84 changes: 84 additions & 0 deletions lib/donation_data_fetcher.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# frozen_string_literal: true

require "json"
require "net/http"
require "uri"

class DonationDataFetcher
API_VERSION = "2022-06-28"
BASE_URL = "https://api.notion.com/v1"

attr_accessor :auth_token, :donations_database_id, :organizations_database_id

def initialize(auth_token, donations_database_id, organizations_database_id)
@auth_token = auth_token
@donations_database_id = donations_database_id
@organizations_database_id = organizations_database_id
end

def organizations
organizations = fetch_all_items(organizations_database_id)
organization_infos = organizations.map { |org|
{
id: org.dig!("id"),
name: org.dig!("properties", "organization", "title", 0, "plain_text"),
url: org.dig!("properties", "URL", "url"),
}
}.sort_by { |info| info.fetch(:name) }
end

def donations
donations = fetch_all_items(
donations_database_id,
filter: { property: "hidden", checkbox: { equals: false } },
)
donation_infos = donations.map { |donation|
{
date: donation.dig!("properties", "date", "date", "start").then { |date| Date.parse(date) },
is_grant: donation.dig!("properties", "grant", "checkbox"),
is_daf_contribution: donation.dig!("properties", "DAF contribution", "checkbox"),
amount: donation.dig!("properties", "amount", "number"),
organization: donation.dig!("properties", "organization name", "rollup", "array", 0, "title", 0, "plain_text"),
note: donation.dig!("properties", "public note", "rich_text")[0]&.fetch("plain_text"),
}
}.sort_by { |info| info.values_at(:date, :organization, :amount) }.
reverse
end

private

def fetch_all_items(database_id, filter: nil)
items = []
start_cursor = nil
loop do
response = query_database(database_id, start_cursor, filter)
items.concat(response["results"])
start_cursor = response["next_cursor"]
break unless start_cursor
end
items.map { |item| Data[item] }
end

def query_database(database_id, start_cursor = nil, filter = nil)
uri = URI("#{BASE_URL}/databases/#{database_id}/query")
request = Net::HTTP::Post.new(uri.request_uri, {
"Authorization" => "Bearer #{@auth_token}",
"Notion-Version" => API_VERSION,
"Content-Type" => "application/json",
}).tap do |req|
body = { start_cursor: start_cursor, filter: filter }.compact
req.body = body.to_json
end
response = Net::HTTP.new(uri.host, uri.port).then do |http|
http.use_ssl = true
http.request(request)
end
JSON.parse(response.body)
end

Data = Struct.new(:hash) do
def dig!(*keys)
keys.reduce(hash) { |h, key| h.fetch(key) }
end
end
end
Loading

0 comments on commit b719229

Please sign in to comment.