mirror of
https://github.com/hakanensari/frankfurter.git
synced 2024-10-29 23:32:35 +01:00
Rebrand, serve HTML
This commit is contained in:
parent
b30c23d0d8
commit
2c91173ecf
@ -1,5 +1,4 @@
|
|||||||
APP_ENV=production
|
APP_ENV=production
|
||||||
HTTPS_METHOD=noredirect
|
|
||||||
LETSENCRYPT_EMAIL=foo@bar.com
|
LETSENCRYPT_EMAIL=foo@bar.com
|
||||||
LETSENCRYPT_HOST=example.com,www.example.com
|
LETSENCRYPT_HOST=example.com
|
||||||
VIRTUAL_HOST=example.com,www.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
|
language: ruby
|
||||||
rvm:
|
rvm:
|
||||||
- 2.5.0
|
- 2.5
|
||||||
|
|
||||||
addons:
|
addons:
|
||||||
postgresql: "9.6"
|
postgresql: "9.6"
|
||||||
before_install:
|
before_install:
|
||||||
- "psql -c 'create database fixer_test;' -U postgres"
|
- "psql -c 'create database frankfurter_test;' -U postgres"
|
||||||
|
|
||||||
bundler_args: "--without development"
|
bundler_args: "--without development"
|
||||||
env:
|
env:
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
FROM ruby:2.5.0
|
FROM ruby:2.5.1
|
||||||
|
|
||||||
RUN mkdir /app
|
RUN mkdir /app
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
ADD Gemfile /app/Gemfile
|
ADD Gemfile /app/Gemfile
|
||||||
ADD Gemfile.lock /app/Gemfile.lock
|
ADD Gemfile.lock /app/Gemfile.lock
|
||||||
RUN bundle install --jobs=8 --without development
|
RUN bundle install --jobs=8 --without development test
|
||||||
ADD . /app
|
ADD . /app
|
||||||
CMD ["unicorn", "-c", "./config/unicorn.rb"]
|
CMD ["unicorn", "-c", "./config/unicorn.rb"]
|
||||||
|
7
Gemfile
7
Gemfile
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
source 'http://rubygems.org'
|
source 'http://rubygems.org'
|
||||||
|
|
||||||
ruby '2.5.0'
|
ruby '2.5.1'
|
||||||
|
|
||||||
gem 'oj'
|
gem 'oj'
|
||||||
gem 'ox'
|
gem 'ox'
|
||||||
@ -14,7 +14,12 @@ gem 'sinatra'
|
|||||||
gem 'unicorn'
|
gem 'unicorn'
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
|
gem 'guard'
|
||||||
|
gem 'guard-livereload'
|
||||||
|
gem 'guard-minitest'
|
||||||
gem 'pry'
|
gem 'pry'
|
||||||
|
gem 'rack-livereload'
|
||||||
|
gem 'shotgun'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :test do
|
group :test do
|
||||||
|
90
Gemfile.lock
90
Gemfile.lock
@ -7,19 +7,58 @@ GEM
|
|||||||
coderay (1.1.2)
|
coderay (1.1.2)
|
||||||
crack (0.4.3)
|
crack (0.4.3)
|
||||||
safe_yaml (~> 1.0.0)
|
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
|
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)
|
hashdiff (0.3.7)
|
||||||
|
http_parser.rb (0.6.0)
|
||||||
|
jaro_winkler (1.4.0)
|
||||||
kgio (2.11.2)
|
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)
|
method_source (0.9.0)
|
||||||
minitest (5.11.3)
|
minitest (5.11.3)
|
||||||
minitest-around (0.4.1)
|
minitest-around (0.4.1)
|
||||||
minitest (~> 5.0)
|
minitest (~> 5.0)
|
||||||
|
multi_json (1.13.1)
|
||||||
mustermann (1.0.2)
|
mustermann (1.0.2)
|
||||||
oj (3.5.0)
|
nenv (0.3.0)
|
||||||
ox (2.9.0)
|
notiffany (0.1.1)
|
||||||
|
nenv (~> 0.1)
|
||||||
|
shellany (~> 0.0)
|
||||||
|
oj (3.6.2)
|
||||||
|
ox (2.9.2)
|
||||||
parallel (1.12.1)
|
parallel (1.12.1)
|
||||||
parser (2.5.0.4)
|
parser (2.5.1.0)
|
||||||
ast (~> 2.4.0)
|
ast (~> 2.4.0)
|
||||||
pg (1.0.0)
|
pg (1.0.0)
|
||||||
powerpack (0.1.1)
|
powerpack (0.1.1)
|
||||||
@ -27,16 +66,23 @@ GEM
|
|||||||
coderay (~> 1.1.0)
|
coderay (~> 1.1.0)
|
||||||
method_source (~> 0.9.0)
|
method_source (~> 0.9.0)
|
||||||
public_suffix (3.0.2)
|
public_suffix (3.0.2)
|
||||||
rack (2.0.4)
|
raabro (1.1.5)
|
||||||
|
rack (2.0.5)
|
||||||
rack-cors (1.0.2)
|
rack-cors (1.0.2)
|
||||||
rack-protection (2.0.1)
|
rack-livereload (0.3.17)
|
||||||
rack
|
rack
|
||||||
rack-test (0.8.3)
|
rack-protection (2.0.2)
|
||||||
|
rack
|
||||||
|
rack-test (1.0.0)
|
||||||
rack (>= 1.0, < 3)
|
rack (>= 1.0, < 3)
|
||||||
rainbow (3.0.0)
|
rainbow (3.0.0)
|
||||||
raindrops (0.19.0)
|
raindrops (0.19.0)
|
||||||
rake (12.3.1)
|
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)
|
parallel (~> 1.10)
|
||||||
parser (>= 2.5)
|
parser (>= 2.5)
|
||||||
powerpack (~> 0.1)
|
powerpack (~> 0.1)
|
||||||
@ -44,28 +90,33 @@ GEM
|
|||||||
ruby-progressbar (~> 1.7)
|
ruby-progressbar (~> 1.7)
|
||||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||||
ruby-progressbar (1.9.0)
|
ruby-progressbar (1.9.0)
|
||||||
rufus-scheduler (3.4.2)
|
ruby_dep (1.5.0)
|
||||||
et-orbi (~> 1.0)
|
rufus-scheduler (3.5.0)
|
||||||
|
fugit (~> 1.1, >= 1.1.1)
|
||||||
safe_yaml (1.0.4)
|
safe_yaml (1.0.4)
|
||||||
sequel (5.6.0)
|
sequel (5.9.0)
|
||||||
sequel_pg (1.8.1)
|
sequel_pg (1.8.2)
|
||||||
pg (>= 0.18.0)
|
pg (>= 0.18.0)
|
||||||
sequel (>= 4.34.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)
|
mustermann (~> 1.0)
|
||||||
rack (~> 2.0)
|
rack (~> 2.0)
|
||||||
rack-protection (= 2.0.1)
|
rack-protection (= 2.0.2)
|
||||||
tilt (~> 2.0)
|
tilt (~> 2.0)
|
||||||
|
thor (0.20.0)
|
||||||
thread_safe (0.3.6)
|
thread_safe (0.3.6)
|
||||||
tilt (2.0.8)
|
tilt (2.0.8)
|
||||||
tzinfo (1.2.5)
|
tzinfo (1.2.5)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
unicode-display_width (1.3.0)
|
unicode-display_width (1.4.0)
|
||||||
unicorn (5.4.0)
|
unicorn (5.4.0)
|
||||||
kgio (~> 2.6)
|
kgio (~> 2.6)
|
||||||
raindrops (~> 0.7)
|
raindrops (~> 0.7)
|
||||||
vcr (4.0.0)
|
vcr (4.0.0)
|
||||||
webmock (3.3.0)
|
webmock (3.4.2)
|
||||||
addressable (>= 2.3.6)
|
addressable (>= 2.3.6)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
hashdiff
|
hashdiff
|
||||||
@ -74,24 +125,29 @@ PLATFORMS
|
|||||||
ruby
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
|
guard
|
||||||
|
guard-livereload
|
||||||
|
guard-minitest
|
||||||
minitest
|
minitest
|
||||||
minitest-around
|
minitest-around
|
||||||
oj
|
oj
|
||||||
ox
|
ox
|
||||||
pry
|
pry
|
||||||
rack-cors
|
rack-cors
|
||||||
|
rack-livereload
|
||||||
rack-test
|
rack-test
|
||||||
rake
|
rake
|
||||||
rubocop
|
rubocop
|
||||||
rufus-scheduler
|
rufus-scheduler
|
||||||
sequel_pg
|
sequel_pg
|
||||||
|
shotgun
|
||||||
sinatra
|
sinatra
|
||||||
unicorn
|
unicorn
|
||||||
vcr
|
vcr
|
||||||
webmock
|
webmock
|
||||||
|
|
||||||
RUBY VERSION
|
RUBY VERSION
|
||||||
ruby 2.5.0p0
|
ruby 2.5.1p57
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
1.16.1
|
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.
|
Rates are updated around 4PM CET every working day.
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require './config/environment'
|
require './config/environment'
|
||||||
require 'api'
|
require 'web/server'
|
||||||
|
|
||||||
run Sinatra::Application
|
run Sinatra::Application
|
||||||
|
@ -4,4 +4,4 @@ require 'pg'
|
|||||||
require 'sequel'
|
require 'sequel'
|
||||||
|
|
||||||
Sequel.single_threaded = true
|
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
|
restart: unless-stopped
|
||||||
web:
|
web:
|
||||||
env_file: .env
|
env_file: .env
|
||||||
image: hakanensari/fixer
|
image: hakanensari/frankfurter
|
||||||
logging:
|
logging:
|
||||||
options:
|
options:
|
||||||
max-size: "50m"
|
max-size: "50m"
|
||||||
@ -12,7 +12,7 @@ services:
|
|||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
scheduler:
|
scheduler:
|
||||||
env_file: .env
|
env_file: .env
|
||||||
image: hakanensari/fixer
|
image: hakanensari/frankfurter
|
||||||
logging:
|
logging:
|
||||||
options:
|
options:
|
||||||
max-size: "50m"
|
max-size: "50m"
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
require 'currency'
|
require 'currency'
|
||||||
|
|
||||||
class Quotation
|
class Quote
|
||||||
DEFAULT_BASE = 'EUR'
|
DEFAULT_BASE = 'EUR'
|
||||||
|
|
||||||
def initialize(amount: 1.0,
|
def initialize(amount: 1.0,
|
||||||
@ -15,7 +15,7 @@ class Quotation
|
|||||||
@symbols = symbols
|
@symbols = symbols
|
||||||
end
|
end
|
||||||
|
|
||||||
def quote
|
def to_h
|
||||||
{ base: @base, date: date, rates: calculate_rates }
|
{ base: @base, date: date, rates: calculate_rates }
|
||||||
end
|
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
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require 'oj'
|
require 'oj'
|
||||||
require 'sinatra'
|
|
||||||
require 'rack/cors'
|
require 'rack/cors'
|
||||||
|
require 'sinatra'
|
||||||
|
|
||||||
require 'query'
|
require 'query'
|
||||||
require 'quotation'
|
require 'quote'
|
||||||
|
|
||||||
use Rack::Cors do
|
use Rack::Cors do
|
||||||
allow do
|
allow do
|
||||||
@ -27,16 +27,17 @@ configure :test do
|
|||||||
end
|
end
|
||||||
|
|
||||||
helpers do
|
helpers do
|
||||||
def quotation
|
def quote
|
||||||
@quotation ||= begin
|
@quote ||= begin
|
||||||
query = Query.new(params)
|
query = Query.new(params)
|
||||||
Quotation.new(query.to_h)
|
Quote.new(query.to_h)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def jsonp(data)
|
def json(data)
|
||||||
json = Oj.dump(data, mode: :compat)
|
json = Oj.dump(data, mode: :compat)
|
||||||
callback = params.delete('callback')
|
callback = params.delete('callback')
|
||||||
|
|
||||||
if callback
|
if callback
|
||||||
content_type :js
|
content_type :js
|
||||||
"#{callback}(#{json})"
|
"#{callback}(#{json})"
|
||||||
@ -52,22 +53,27 @@ options '*' do
|
|||||||
end
|
end
|
||||||
|
|
||||||
get '*' do
|
get '*' do
|
||||||
cache_control :public, :must_revalidate, max_age: 900
|
cache_control :public
|
||||||
pass
|
pass
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/' do
|
get '/' do
|
||||||
jsonp source: 'https://github.com/hakanensari/fixer'
|
erb :index
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/latest' do
|
get '/(?:latest|current)', mustermann_opts: { type: :regexp } do
|
||||||
last_modified quotation.date
|
last_modified quote.date
|
||||||
jsonp quotation.quote
|
json quote.to_h
|
||||||
end
|
end
|
||||||
|
|
||||||
get '/(?<date>\d{4}-\d{2}-\d{2})', mustermann_opts: { type: :regexp } do
|
get '/(?<date>\d{4}-\d{2}-\d{2})', mustermann_opts: { type: :regexp } do
|
||||||
last_modified quotation.date
|
last_modified quote.date
|
||||||
jsonp quotation.quote
|
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
|
end
|
||||||
|
|
||||||
not_found do
|
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_relative 'helper'
|
||||||
require 'rack/test'
|
require 'rack/test'
|
||||||
require 'api'
|
require 'web/server'
|
||||||
|
|
||||||
describe 'the API' do
|
describe 'the API' do
|
||||||
include Rack::Test::Methods
|
include Rack::Test::Methods
|
||||||
|
@ -1,35 +1,35 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'helper'
|
require_relative 'helper'
|
||||||
require 'quotation'
|
require 'quote'
|
||||||
|
|
||||||
describe Quotation do
|
describe Quote do
|
||||||
describe 'by default' do
|
describe 'by default' do
|
||||||
it 'quotes against the Euro' do
|
it 'quotes against the Euro' do
|
||||||
quotation = Quotation.new
|
quote = Quote.new
|
||||||
rates = quotation.quote[:rates]
|
rates = quote.to_h[:rates]
|
||||||
rates.keys.wont_include 'EUR'
|
rates.keys.wont_include 'EUR'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when given a base' do
|
describe 'when given a base' do
|
||||||
it 'quotes against that base' do
|
it 'quotes against that base' do
|
||||||
quotation = Quotation.new(base: 'USD')
|
quote = Quote.new(base: 'USD')
|
||||||
rates = quotation.quote[:rates]
|
rates = quote.to_h[:rates]
|
||||||
rates.keys.wont_include 'USD'
|
rates.keys.wont_include 'USD'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sorts rates' do
|
it 'sorts rates' do
|
||||||
quotation = Quotation.new(base: 'USD')
|
quote = Quote.new(base: 'USD')
|
||||||
rates = quotation.quote[:rates]
|
rates = quote.to_h[:rates]
|
||||||
rates.keys.must_equal rates.keys.sort
|
rates.keys.must_equal rates.keys.sort
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when given symbols' do
|
describe 'when given symbols' do
|
||||||
it 'quotes rates only for given symbols' do
|
it 'quotes rates only for given symbols' do
|
||||||
quotation = Quotation.new(symbols: ['USD'])
|
quote = Quote.new(symbols: ['USD'])
|
||||||
rates = quotation.quote[:rates]
|
rates = quote.to_h[:rates]
|
||||||
rates.keys.must_include 'USD'
|
rates.keys.must_include 'USD'
|
||||||
rates.keys.wont_include 'GBP'
|
rates.keys.wont_include 'GBP'
|
||||||
end
|
end
|
||||||
@ -37,16 +37,16 @@ describe Quotation do
|
|||||||
|
|
||||||
describe 'when given an amount' do
|
describe 'when given an amount' do
|
||||||
it 'quotes for that amount' do
|
it 'quotes for that amount' do
|
||||||
quotation = Quotation.new(amount: 100)
|
quote = Quote.new(amount: 100)
|
||||||
rates = quotation.quote[:rates]
|
rates = quote.to_h[:rates]
|
||||||
rates['USD'].must_be :>, 10
|
rates['USD'].must_be :>, 10
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'when given an amount and symbols' do
|
describe 'when given an amount and symbols' do
|
||||||
it 'quotes for that amount' do
|
it 'quotes for that amount' do
|
||||||
quotation = Quotation.new(amount: 100, symbols: ['USD'])
|
quote = Quote.new(amount: 100, symbols: ['USD'])
|
||||||
rates = quotation.quote[:rates]
|
rates = quote.to_h[:rates]
|
||||||
rates['USD'].must_be :>, 10
|
rates['USD'].must_be :>, 10
|
||||||
end
|
end
|
||||||
end
|
end
|
@ -1,40 +1,40 @@
|
|||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
require_relative 'helper'
|
require_relative '../helper'
|
||||||
require 'rack/test'
|
require 'rack/test'
|
||||||
require 'api'
|
require 'web/server'
|
||||||
|
|
||||||
describe 'the API' do
|
describe 'the server' do
|
||||||
include Rack::Test::Methods
|
include Rack::Test::Methods
|
||||||
|
|
||||||
let(:app) { Sinatra::Application }
|
let(:app) { Sinatra::Application }
|
||||||
let(:json) { Oj.load(last_response.body) }
|
let(:json) { Oj.load(last_response.body) }
|
||||||
let(:headers) { last_response.headers }
|
let(:headers) { last_response.headers }
|
||||||
|
|
||||||
it 'describes itself' do
|
it 'has a homepage' do
|
||||||
get '/'
|
get '/'
|
||||||
last_response.must_be :ok?
|
last_response.must_be :ok?
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns latest quotes' do
|
it 'returns current quotes' do
|
||||||
get '/latest'
|
get '/current'
|
||||||
last_response.must_be :ok?
|
last_response.must_be :ok?
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets base currency' do
|
it 'sets base currency' do
|
||||||
get '/latest'
|
get '/current'
|
||||||
res = Oj.load(last_response.body)
|
res = Oj.load(last_response.body)
|
||||||
get '/latest?base=USD'
|
get '/current?from=USD'
|
||||||
json.wont_equal res
|
json.wont_equal res
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'sets base amount' do
|
it 'sets base amount' do
|
||||||
get '/latest?amount=10'
|
get '/current?amount=10'
|
||||||
json['rates']['USD'].must_be :>, 10
|
json['rates']['USD'].must_be :>, 10
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'filters symbols' do
|
it 'filters symbols' do
|
||||||
get '/latest?symbols=USD'
|
get '/current?to=USD'
|
||||||
json['rates'].keys.must_equal %w[USD]
|
json['rates'].keys.must_equal %w[USD]
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -50,21 +50,21 @@ describe 'the API' do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'returns a cache control header' do
|
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
|
get path
|
||||||
headers['Cache-Control'].wont_be_nil
|
headers['Cache-Control'].wont_be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns a last modified header' do
|
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
|
get path
|
||||||
headers['Last-Modified'].wont_be_nil
|
headers['Last-Modified'].wont_be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'allows cross-origin requests' do
|
it 'allows cross-origin requests' do
|
||||||
%w[/ /latest /2012-11-20].each do |path|
|
%w[/ /current /2012-11-20].each do |path|
|
||||||
header 'Origin', '*'
|
header 'Origin', '*'
|
||||||
get path
|
get path
|
||||||
assert headers.key?('Access-Control-Allow-Methods')
|
assert headers.key?('Access-Control-Allow-Methods')
|
||||||
@ -72,7 +72,7 @@ describe 'the API' do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'responds to preflight requests' do
|
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 'Origin', '*'
|
||||||
header 'Access-Control-Request-Method', 'GET'
|
header 'Access-Control-Request-Method', 'GET'
|
||||||
header 'Access-Control-Request-Headers', 'Content-Type'
|
header 'Access-Control-Request-Headers', 'Content-Type'
|
||||||
@ -82,7 +82,14 @@ describe 'the API' do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'converts an amount' do
|
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
|
json['rates']['USD'].must_be :>, 100
|
||||||
end
|
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
|
end
|
Loading…
Reference in New Issue
Block a user