mirror of
https://github.com/hakanensari/frankfurter.git
synced 2024-11-23 11:32:28 +01:00
Rebrand, serve HTML
This commit is contained in:
parent
b30c23d0d8
commit
2c91173ecf
@ -1,5 +1,4 @@
|
||||
APP_ENV=production
|
||||
HTTPS_METHOD=noredirect
|
||||
LETSENCRYPT_EMAIL=foo@bar.com
|
||||
LETSENCRYPT_HOST=example.com,www.example.com
|
||||
VIRTUAL_HOST=example.com,www.example.com
|
||||
LETSENCRYPT_HOST=example.com
|
||||
VIRTUAL_HOST=example.com
|
||||
|
16
.eslintrc
Normal file
16
.eslintrc
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": ["airbnb-standard"],
|
||||
"globals": {
|
||||
"fetch": true,
|
||||
"hljs": true
|
||||
},
|
||||
"rules": {
|
||||
"no-new-func": [
|
||||
"off"
|
||||
],
|
||||
"no-param-reassign": [
|
||||
"error",
|
||||
{ "props": false }
|
||||
]
|
||||
}
|
||||
}
|
@ -1 +1 @@
|
||||
ruby 2.5.0
|
||||
ruby 2.5.1
|
||||
|
@ -3,12 +3,12 @@ sudo: false
|
||||
|
||||
language: ruby
|
||||
rvm:
|
||||
- 2.5.0
|
||||
- 2.5
|
||||
|
||||
addons:
|
||||
postgresql: "9.6"
|
||||
before_install:
|
||||
- "psql -c 'create database fixer_test;' -U postgres"
|
||||
- "psql -c 'create database frankfurter_test;' -U postgres"
|
||||
|
||||
bundler_args: "--without development"
|
||||
env:
|
||||
|
@ -1,9 +1,9 @@
|
||||
FROM ruby:2.5.0
|
||||
FROM ruby:2.5.1
|
||||
|
||||
RUN mkdir /app
|
||||
WORKDIR /app
|
||||
ADD Gemfile /app/Gemfile
|
||||
ADD Gemfile.lock /app/Gemfile.lock
|
||||
RUN bundle install --jobs=8 --without development
|
||||
RUN bundle install --jobs=8 --without development test
|
||||
ADD . /app
|
||||
CMD ["unicorn", "-c", "./config/unicorn.rb"]
|
||||
|
7
Gemfile
7
Gemfile
@ -2,7 +2,7 @@
|
||||
|
||||
source 'http://rubygems.org'
|
||||
|
||||
ruby '2.5.0'
|
||||
ruby '2.5.1'
|
||||
|
||||
gem 'oj'
|
||||
gem 'ox'
|
||||
@ -14,7 +14,12 @@ gem 'sinatra'
|
||||
gem 'unicorn'
|
||||
|
||||
group :development do
|
||||
gem 'guard'
|
||||
gem 'guard-livereload'
|
||||
gem 'guard-minitest'
|
||||
gem 'pry'
|
||||
gem 'rack-livereload'
|
||||
gem 'shotgun'
|
||||
end
|
||||
|
||||
group :test do
|
||||
|
90
Gemfile.lock
90
Gemfile.lock
@ -7,19 +7,58 @@ GEM
|
||||
coderay (1.1.2)
|
||||
crack (0.4.3)
|
||||
safe_yaml (~> 1.0.0)
|
||||
et-orbi (1.0.9)
|
||||
em-websocket (0.5.1)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0.6.0)
|
||||
et-orbi (1.1.2)
|
||||
tzinfo
|
||||
eventmachine (1.2.7)
|
||||
ffi (1.9.25)
|
||||
formatador (0.2.5)
|
||||
fugit (1.1.1)
|
||||
et-orbi (~> 1.1, >= 1.1.1)
|
||||
raabro (~> 1.1)
|
||||
guard (2.14.2)
|
||||
formatador (>= 0.2.4)
|
||||
listen (>= 2.7, < 4.0)
|
||||
lumberjack (>= 1.0.12, < 2.0)
|
||||
nenv (~> 0.1)
|
||||
notiffany (~> 0.0)
|
||||
pry (>= 0.9.12)
|
||||
shellany (~> 0.0)
|
||||
thor (>= 0.18.1)
|
||||
guard-compat (1.2.1)
|
||||
guard-livereload (2.5.2)
|
||||
em-websocket (~> 0.5)
|
||||
guard (~> 2.8)
|
||||
guard-compat (~> 1.0)
|
||||
multi_json (~> 1.8)
|
||||
guard-minitest (2.4.6)
|
||||
guard-compat (~> 1.2)
|
||||
minitest (>= 3.0)
|
||||
hashdiff (0.3.7)
|
||||
http_parser.rb (0.6.0)
|
||||
jaro_winkler (1.4.0)
|
||||
kgio (2.11.2)
|
||||
listen (3.1.5)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
ruby_dep (~> 1.2)
|
||||
lumberjack (1.0.13)
|
||||
method_source (0.9.0)
|
||||
minitest (5.11.3)
|
||||
minitest-around (0.4.1)
|
||||
minitest (~> 5.0)
|
||||
multi_json (1.13.1)
|
||||
mustermann (1.0.2)
|
||||
oj (3.5.0)
|
||||
ox (2.9.0)
|
||||
nenv (0.3.0)
|
||||
notiffany (0.1.1)
|
||||
nenv (~> 0.1)
|
||||
shellany (~> 0.0)
|
||||
oj (3.6.2)
|
||||
ox (2.9.2)
|
||||
parallel (1.12.1)
|
||||
parser (2.5.0.4)
|
||||
parser (2.5.1.0)
|
||||
ast (~> 2.4.0)
|
||||
pg (1.0.0)
|
||||
powerpack (0.1.1)
|
||||
@ -27,16 +66,23 @@ GEM
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.9.0)
|
||||
public_suffix (3.0.2)
|
||||
rack (2.0.4)
|
||||
raabro (1.1.5)
|
||||
rack (2.0.5)
|
||||
rack-cors (1.0.2)
|
||||
rack-protection (2.0.1)
|
||||
rack-livereload (0.3.17)
|
||||
rack
|
||||
rack-test (0.8.3)
|
||||
rack-protection (2.0.2)
|
||||
rack
|
||||
rack-test (1.0.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rainbow (3.0.0)
|
||||
raindrops (0.19.0)
|
||||
rake (12.3.1)
|
||||
rubocop (0.54.0)
|
||||
rb-fsevent (0.10.3)
|
||||
rb-inotify (0.9.10)
|
||||
ffi (>= 0.5.0, < 2)
|
||||
rubocop (0.57.0)
|
||||
jaro_winkler (~> 1.4.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 2.5)
|
||||
powerpack (~> 0.1)
|
||||
@ -44,28 +90,33 @@ GEM
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||
ruby-progressbar (1.9.0)
|
||||
rufus-scheduler (3.4.2)
|
||||
et-orbi (~> 1.0)
|
||||
ruby_dep (1.5.0)
|
||||
rufus-scheduler (3.5.0)
|
||||
fugit (~> 1.1, >= 1.1.1)
|
||||
safe_yaml (1.0.4)
|
||||
sequel (5.6.0)
|
||||
sequel_pg (1.8.1)
|
||||
sequel (5.9.0)
|
||||
sequel_pg (1.8.2)
|
||||
pg (>= 0.18.0)
|
||||
sequel (>= 4.34.0)
|
||||
sinatra (2.0.1)
|
||||
shellany (0.0.1)
|
||||
shotgun (0.9.2)
|
||||
rack (>= 1.0)
|
||||
sinatra (2.0.2)
|
||||
mustermann (~> 1.0)
|
||||
rack (~> 2.0)
|
||||
rack-protection (= 2.0.1)
|
||||
rack-protection (= 2.0.2)
|
||||
tilt (~> 2.0)
|
||||
thor (0.20.0)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.8)
|
||||
tzinfo (1.2.5)
|
||||
thread_safe (~> 0.1)
|
||||
unicode-display_width (1.3.0)
|
||||
unicode-display_width (1.4.0)
|
||||
unicorn (5.4.0)
|
||||
kgio (~> 2.6)
|
||||
raindrops (~> 0.7)
|
||||
vcr (4.0.0)
|
||||
webmock (3.3.0)
|
||||
webmock (3.4.2)
|
||||
addressable (>= 2.3.6)
|
||||
crack (>= 0.3.2)
|
||||
hashdiff
|
||||
@ -74,24 +125,29 @@ PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
guard
|
||||
guard-livereload
|
||||
guard-minitest
|
||||
minitest
|
||||
minitest-around
|
||||
oj
|
||||
ox
|
||||
pry
|
||||
rack-cors
|
||||
rack-livereload
|
||||
rack-test
|
||||
rake
|
||||
rubocop
|
||||
rufus-scheduler
|
||||
sequel_pg
|
||||
shotgun
|
||||
sinatra
|
||||
unicorn
|
||||
vcr
|
||||
webmock
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.5.0p0
|
||||
ruby 2.5.1p57
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.1
|
||||
|
13
Guardfile
Normal file
13
Guardfile
Normal file
@ -0,0 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
guard 'livereload' do
|
||||
extensions = %i[css js png gif jpg]
|
||||
watch(%r{lib/web/public/.+\.(#{extensions * '|'})})
|
||||
watch(%r{lib/web/views/.+\.erb$})
|
||||
end
|
||||
|
||||
guard :minitest do
|
||||
watch(%r{^spec/(.*)_spec\.rb$})
|
||||
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
||||
watch(%r{^spec/helper\.rb$}) { 'spec' }
|
||||
end
|
@ -1,8 +1,8 @@
|
||||
# Fixer
|
||||
# Frankfurter
|
||||
|
||||
[![Travis](https://travis-ci.org/hakanensari/fixer.svg)](https://travis-ci.org/hakanensari/fixer)
|
||||
[![Travis](https://travis-ci.org/hakanensari/frankfurter.svg)](https://travis-ci.org/hakanensari/frankfurter)
|
||||
|
||||
Fixer is a free API for current and historical foreign exchange rates [published by the European Central Bank](https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html).
|
||||
Frankfurter is a free API for current and historical foreign exchange rates [published by the European Central Bank](https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html).
|
||||
|
||||
Rates are updated around 4PM CET every working day.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require './config/environment'
|
||||
require 'api'
|
||||
require 'web/server'
|
||||
|
||||
run Sinatra::Application
|
||||
|
@ -4,4 +4,4 @@ require 'pg'
|
||||
require 'sequel'
|
||||
|
||||
Sequel.single_threaded = true
|
||||
Sequel.connect(ENV['DATABASE_URL'] || "postgres://localhost/fixer_#{App.env}")
|
||||
Sequel.connect(ENV['DATABASE_URL'] || "postgres://localhost/frankfurter_#{App.env}")
|
||||
|
@ -4,7 +4,7 @@ services:
|
||||
restart: unless-stopped
|
||||
web:
|
||||
env_file: .env
|
||||
image: hakanensari/fixer
|
||||
image: hakanensari/frankfurter
|
||||
logging:
|
||||
options:
|
||||
max-size: "50m"
|
||||
@ -12,7 +12,7 @@ services:
|
||||
restart: unless-stopped
|
||||
scheduler:
|
||||
env_file: .env
|
||||
image: hakanensari/fixer
|
||||
image: hakanensari/frankfurter
|
||||
logging:
|
||||
options:
|
||||
max-size: "50m"
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
require 'currency'
|
||||
|
||||
class Quotation
|
||||
class Quote
|
||||
DEFAULT_BASE = 'EUR'
|
||||
|
||||
def initialize(amount: 1.0,
|
||||
@ -15,7 +15,7 @@ class Quotation
|
||||
@symbols = symbols
|
||||
end
|
||||
|
||||
def quote
|
||||
def to_h
|
||||
{ base: @base, date: date, rates: calculate_rates }
|
||||
end
|
||||
|
BIN
lib/web/public/images/frankfurter-icon.png
Normal file
BIN
lib/web/public/images/frankfurter-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
BIN
lib/web/public/images/frankfurter.png
Normal file
BIN
lib/web/public/images/frankfurter.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
29
lib/web/public/javascripts/application.js
Normal file
29
lib/web/public/javascripts/application.js
Normal file
@ -0,0 +1,29 @@
|
||||
hljs.initHighlightingOnLoad();
|
||||
|
||||
// Make URLs clickable
|
||||
document.querySelectorAll('.http').forEach((element) => {
|
||||
element.onclick = () => {
|
||||
if (element.innerHTML.indexOf('\n') > -1) {
|
||||
const output = element.innerHTML.match(/^(.*)\n/)[1];
|
||||
element.innerHTML = output;
|
||||
} else {
|
||||
const url = element.innerHTML.match(/>(\/[^<]*)/)[1];
|
||||
fetch(url)
|
||||
.then((resp) => {
|
||||
const host = resp.url.match(/\/\/([^:/]+)/)[1];
|
||||
element.insertAdjacentHTML('beforeend', `\nHost: ${host}\nContent-Type: ${resp.headers.get('Content-Type')}\nContent-Length: ${resp.headers.get('Content-Length')}\n\n`);
|
||||
return resp.json();
|
||||
})
|
||||
.then((data) => {
|
||||
element.insertAdjacentHTML('beforeend', `${JSON.stringify(data, undefined, 4)}`);
|
||||
hljs.highlightBlock(element);
|
||||
});
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Make the JavaScript examples clickable
|
||||
document.querySelectorAll('.js').forEach((element) => {
|
||||
const code = element.innerHTML.replace(/>/g, '>');
|
||||
element.onclick = () => new Function(code)();
|
||||
});
|
84
lib/web/public/stylesheets/application.css
Normal file
84
lib/web/public/stylesheets/application.css
Normal file
@ -0,0 +1,84 @@
|
||||
body {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
}
|
||||
|
||||
.navbar .navbar-nav .nav-link {
|
||||
font-size: .875rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.section {
|
||||
padding-top: 1em;
|
||||
}
|
||||
|
||||
.hero .container {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hero .logo {
|
||||
padding-bottom: 1em;
|
||||
width: 120px;
|
||||
}
|
||||
|
||||
.footer .container {
|
||||
padding-bottom: 2em;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hljs {
|
||||
background-color: #f8f9fa;
|
||||
border: 2px solid #f8f9fa;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.hljs:hover {
|
||||
border-bottom-color: #dee2e6;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.hljs i {
|
||||
color: #ced4da;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
#carbonads {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/*
|
||||
@media screen and (max-width: 767px) {
|
||||
#carbonads {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
@media screen and (min-width: 768px) {
|
||||
#carbonads {
|
||||
background-color: #f0f0f0;
|
||||
position: absolute;
|
||||
right: 24px;
|
||||
top: 36px;
|
||||
width: 150px;
|
||||
}
|
||||
.carbon-img,
|
||||
.carbon-poweredby,
|
||||
.carbon-text {
|
||||
display: block;
|
||||
margin: 10px;
|
||||
}
|
||||
.carbon-text,
|
||||
.carbon-poweredby {
|
||||
line-height: 1.3;
|
||||
text-decoration: none;
|
||||
}
|
||||
.carbon-text {
|
||||
font-size: 13px;
|
||||
}
|
||||
.carbon-poweredby {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
*/
|
@ -1,11 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'oj'
|
||||
require 'sinatra'
|
||||
require 'rack/cors'
|
||||
require 'sinatra'
|
||||
|
||||
require 'query'
|
||||
require 'quotation'
|
||||
require 'quote'
|
||||
|
||||
use Rack::Cors do
|
||||
allow do
|
||||
@ -27,16 +27,17 @@ configure :test do
|
||||
end
|
||||
|
||||
helpers do
|
||||
def quotation
|
||||
@quotation ||= begin
|
||||
def quote
|
||||
@quote ||= begin
|
||||
query = Query.new(params)
|
||||
Quotation.new(query.to_h)
|
||||
Quote.new(query.to_h)
|
||||
end
|
||||
end
|
||||
|
||||
def jsonp(data)
|
||||
def json(data)
|
||||
json = Oj.dump(data, mode: :compat)
|
||||
callback = params.delete('callback')
|
||||
|
||||
if callback
|
||||
content_type :js
|
||||
"#{callback}(#{json})"
|
||||
@ -52,22 +53,27 @@ options '*' do
|
||||
end
|
||||
|
||||
get '*' do
|
||||
cache_control :public, :must_revalidate, max_age: 900
|
||||
cache_control :public
|
||||
pass
|
||||
end
|
||||
|
||||
get '/' do
|
||||
jsonp source: 'https://github.com/hakanensari/fixer'
|
||||
erb :index
|
||||
end
|
||||
|
||||
get '/latest' do
|
||||
last_modified quotation.date
|
||||
jsonp quotation.quote
|
||||
get '/(?:latest|current)', mustermann_opts: { type: :regexp } do
|
||||
last_modified quote.date
|
||||
json quote.to_h
|
||||
end
|
||||
|
||||
get '/(?<date>\d{4}-\d{2}-\d{2})', mustermann_opts: { type: :regexp } do
|
||||
last_modified quotation.date
|
||||
jsonp quotation.quote
|
||||
last_modified quote.date
|
||||
json quote.to_h
|
||||
end
|
||||
|
||||
get '/(?<start_date>\d{4}-\d{2}-\d{2})\.\.(?<end_date>\d{4}-\d{2}-\d{2})', mustermann_opts: { type: :regexp } do
|
||||
last_modified quote.end_date
|
||||
json quote.to_h
|
||||
end
|
||||
|
||||
not_found do
|
118
lib/web/views/index.erb
Normal file
118
lib/web/views/index.erb
Normal file
@ -0,0 +1,118 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="description" content="Frankfurter provides an open-source API for current and historical foreign exchange rates and currency conversion. The API tracks rates published daily by the European Central Bank.">
|
||||
<title>Frankfurter</title>
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/github.min.css">
|
||||
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.1/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Open+Sans:300,400">
|
||||
<link rel="stylesheet" href="/stylesheets/application.css">
|
||||
<link rel="shortcut icon" href="/images/frankfurter-icon.png">
|
||||
</head>
|
||||
<body>
|
||||
<nav class="navbar navbar-expand">
|
||||
<div class="container">
|
||||
<ul class="navbar-nav mx-sm-auto">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://www.ecb.europa.eu/stats/exchange/eurofxref/html/index.en.html" target="_blank">Data sets</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://status.frankfurter.app/" target="_blank">Status</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="https://github.com/hakanensari/frankfurter" target="_blank">Source</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
<section class="hero section">
|
||||
<div class="container">
|
||||
<h1 class="display-4">Frankfurter</h1>
|
||||
<p class="lead">
|
||||
Foreign exchange rates and currency conversion API
|
||||
</p>
|
||||
<img alt="" class="logo" src="/images/frankfurter.png">
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="introduction section">
|
||||
<div class="container">
|
||||
<p>
|
||||
Frankfurter is a lightweight API for querying current and historical forex rates published by the European Central Bank.
|
||||
</p>
|
||||
<p>
|
||||
Rates update around 4PM CET on every working day. Historical data goes back to early 1999.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="usage section">
|
||||
<div class="container">
|
||||
<h5>Usage</h5>
|
||||
<p>
|
||||
Get current foreign exchange rates.
|
||||
</p>
|
||||
<pre><code class="http hljs small">GET /current <i>HTTP/1.1</i></code></pre>
|
||||
<p>
|
||||
Get historical rates for any day since 1999.
|
||||
</p>
|
||||
<pre><code class="http hljs small">GET /1999-12-31 <i>HTTP/1.1</i></code></pre>
|
||||
<p>
|
||||
Rates are quoted against the Euro by default. Quote against a different
|
||||
currency by setting the <strong>from</strong> parameter in your request.
|
||||
</p>
|
||||
<pre><code class="http hljs small">GET /current?from=USD <i>HTTP/1.1</i></code></pre>
|
||||
<p>
|
||||
Request specific exchange rates by setting the <strong>to</strong> parameter.
|
||||
</p>
|
||||
<pre><code class="http hljs small">GET /current?to=USD,GBP <i>HTTP/1.1</i></code></pre>
|
||||
<p>
|
||||
Convert a specific value.
|
||||
</p>
|
||||
<pre><code class="http hljs small">GET /current?amount=1000&from=GBP&to=EUR <i>HTTP/1.1</i></code></pre>
|
||||
</div>
|
||||
</section>
|
||||
<section class="best-practices section">
|
||||
<div class="container">
|
||||
<h5>Best Practices</h5>
|
||||
<p>
|
||||
The primary use case of Frankfurter is client side. If you are converting currencies in the browser, you will appreciate the unencumbered access currently not possible with similar commercial services.
|
||||
</p>
|
||||
<pre><code class="js hljs small">// Fetch and display GBP/USD
|
||||
fetch('/current?from=GBP&to=USD')
|
||||
.then(resp => resp.json())
|
||||
.then((data) => { alert(`GBP/USD = ${data.rates.USD}`); });</code></pre>
|
||||
<p>
|
||||
If you are working with rates on the server side, download them off the European Central Bank to avoid unnecessary indirection.
|
||||
</p>
|
||||
<p>
|
||||
If you require more currencies or granular data, you are better off going with a commercial service.
|
||||
</p>
|
||||
<p>
|
||||
Cache results whenever possible.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="best-practices section">
|
||||
<div class="container">
|
||||
<h5>Notes</h5>
|
||||
<p>
|
||||
This project started out as <strong>Fixer</strong> in 2013. I renamed it to <strong>Frankfurter</strong> after selling the <a href="https://fixer.io" target="_blank">original domain</a> in early 2018.
|
||||
<p>
|
||||
The API is open source and comes with no warranty.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
<section class="footer section">
|
||||
<div class="container">
|
||||
<iframe src="https://ghbtns.com/github-btn.html?user=hakanensari&repo=frankfurter&type=star&count=true" frameborder="0" scrolling="0" width="120px" height="20px"></iframe>
|
||||
</div>
|
||||
</section>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
|
||||
<script src="/javascripts/application.js"></script>
|
||||
<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?zoneid=1673&serve=C6AILKT&placement=fixerio" id="_carbonads_js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
require_relative 'helper'
|
||||
require 'rack/test'
|
||||
require 'api'
|
||||
require 'web/server'
|
||||
|
||||
describe 'the API' do
|
||||
include Rack::Test::Methods
|
||||
|
@ -1,35 +1,35 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'helper'
|
||||
require 'quotation'
|
||||
require 'quote'
|
||||
|
||||
describe Quotation do
|
||||
describe Quote do
|
||||
describe 'by default' do
|
||||
it 'quotes against the Euro' do
|
||||
quotation = Quotation.new
|
||||
rates = quotation.quote[:rates]
|
||||
quote = Quote.new
|
||||
rates = quote.to_h[:rates]
|
||||
rates.keys.wont_include 'EUR'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when given a base' do
|
||||
it 'quotes against that base' do
|
||||
quotation = Quotation.new(base: 'USD')
|
||||
rates = quotation.quote[:rates]
|
||||
quote = Quote.new(base: 'USD')
|
||||
rates = quote.to_h[:rates]
|
||||
rates.keys.wont_include 'USD'
|
||||
end
|
||||
|
||||
it 'sorts rates' do
|
||||
quotation = Quotation.new(base: 'USD')
|
||||
rates = quotation.quote[:rates]
|
||||
quote = Quote.new(base: 'USD')
|
||||
rates = quote.to_h[:rates]
|
||||
rates.keys.must_equal rates.keys.sort
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when given symbols' do
|
||||
it 'quotes rates only for given symbols' do
|
||||
quotation = Quotation.new(symbols: ['USD'])
|
||||
rates = quotation.quote[:rates]
|
||||
quote = Quote.new(symbols: ['USD'])
|
||||
rates = quote.to_h[:rates]
|
||||
rates.keys.must_include 'USD'
|
||||
rates.keys.wont_include 'GBP'
|
||||
end
|
||||
@ -37,16 +37,16 @@ describe Quotation do
|
||||
|
||||
describe 'when given an amount' do
|
||||
it 'quotes for that amount' do
|
||||
quotation = Quotation.new(amount: 100)
|
||||
rates = quotation.quote[:rates]
|
||||
quote = Quote.new(amount: 100)
|
||||
rates = quote.to_h[:rates]
|
||||
rates['USD'].must_be :>, 10
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when given an amount and symbols' do
|
||||
it 'quotes for that amount' do
|
||||
quotation = Quotation.new(amount: 100, symbols: ['USD'])
|
||||
rates = quotation.quote[:rates]
|
||||
quote = Quote.new(amount: 100, symbols: ['USD'])
|
||||
rates = quote.to_h[:rates]
|
||||
rates['USD'].must_be :>, 10
|
||||
end
|
||||
end
|
@ -1,40 +1,40 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'helper'
|
||||
require_relative '../helper'
|
||||
require 'rack/test'
|
||||
require 'api'
|
||||
require 'web/server'
|
||||
|
||||
describe 'the API' do
|
||||
describe 'the server' do
|
||||
include Rack::Test::Methods
|
||||
|
||||
let(:app) { Sinatra::Application }
|
||||
let(:json) { Oj.load(last_response.body) }
|
||||
let(:headers) { last_response.headers }
|
||||
|
||||
it 'describes itself' do
|
||||
it 'has a homepage' do
|
||||
get '/'
|
||||
last_response.must_be :ok?
|
||||
end
|
||||
|
||||
it 'returns latest quotes' do
|
||||
get '/latest'
|
||||
it 'returns current quotes' do
|
||||
get '/current'
|
||||
last_response.must_be :ok?
|
||||
end
|
||||
|
||||
it 'sets base currency' do
|
||||
get '/latest'
|
||||
get '/current'
|
||||
res = Oj.load(last_response.body)
|
||||
get '/latest?base=USD'
|
||||
get '/current?from=USD'
|
||||
json.wont_equal res
|
||||
end
|
||||
|
||||
it 'sets base amount' do
|
||||
get '/latest?amount=10'
|
||||
get '/current?amount=10'
|
||||
json['rates']['USD'].must_be :>, 10
|
||||
end
|
||||
|
||||
it 'filters symbols' do
|
||||
get '/latest?symbols=USD'
|
||||
get '/current?to=USD'
|
||||
json['rates'].keys.must_equal %w[USD]
|
||||
end
|
||||
|
||||
@ -50,21 +50,21 @@ describe 'the API' do
|
||||
end
|
||||
|
||||
it 'returns a cache control header' do
|
||||
%w[/ /latest /2012-11-20].each do |path|
|
||||
%w[/ /current /2012-11-20].each do |path|
|
||||
get path
|
||||
headers['Cache-Control'].wont_be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns a last modified header' do
|
||||
%w[/latest /2012-11-20].each do |path|
|
||||
%w[/current /2012-11-20].each do |path|
|
||||
get path
|
||||
headers['Last-Modified'].wont_be_nil
|
||||
end
|
||||
end
|
||||
|
||||
it 'allows cross-origin requests' do
|
||||
%w[/ /latest /2012-11-20].each do |path|
|
||||
%w[/ /current /2012-11-20].each do |path|
|
||||
header 'Origin', '*'
|
||||
get path
|
||||
assert headers.key?('Access-Control-Allow-Methods')
|
||||
@ -72,7 +72,7 @@ describe 'the API' do
|
||||
end
|
||||
|
||||
it 'responds to preflight requests' do
|
||||
%w[/ /latest /2012-11-20].each do |path|
|
||||
%w[/ /current /2012-11-20].each do |path|
|
||||
header 'Origin', '*'
|
||||
header 'Access-Control-Request-Method', 'GET'
|
||||
header 'Access-Control-Request-Headers', 'Content-Type'
|
||||
@ -82,7 +82,14 @@ describe 'the API' do
|
||||
end
|
||||
|
||||
it 'converts an amount' do
|
||||
get '/latest?from=GBP&to=USD&amount=100'
|
||||
get '/current?from=GBP&to=USD&amount=100'
|
||||
json['rates']['USD'].must_be :>, 100
|
||||
end
|
||||
|
||||
it 'returns rates for a given period' do
|
||||
get '/2010-01-01..2010-12-31'
|
||||
json['start_date'].must_equal '2010-01-01'
|
||||
json['end_date'].must_equal '2010-12-31'
|
||||
json['rates'].wont_be empty
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user