Update homepage

This commit is contained in:
Hakan Ensari 2018-09-11 17:59:18 +01:00
parent 8060f41566
commit 5789880a59
11 changed files with 200 additions and 244 deletions

3
.gitignore vendored
View File

@ -1,5 +1,6 @@
*.css*
.bundle
.env
.sass-cache
pkg
public/stylesheets/*.css*

View File

@ -9,6 +9,7 @@ gem 'ox'
gem 'puma'
gem 'rack-cors'
gem 'rake'
gem 'redcarpet'
gem 'rufus-scheduler'
gem 'sass'
gem 'sequel_pg'

View File

@ -44,6 +44,7 @@ GEM
rb-fsevent (0.10.3)
rb-inotify (0.9.10)
ffi (>= 0.5.0, < 2)
redcarpet (3.4.0)
rubocop (0.57.0)
jaro_winkler (~> 1.4.0)
parallel (~> 1.10)
@ -96,6 +97,7 @@ DEPENDENCIES
rack-cors
rack-test
rake
redcarpet
rubocop
rufus-scheduler
sass
@ -109,4 +111,4 @@ RUBY VERSION
ruby 2.5.1p57
BUNDLED WITH
1.16.2
1.16.4

View File

@ -2,63 +2,70 @@
[![Travis](https://travis-ci.org/hakanensari/frankfurter.svg)](https://travis-ci.org/hakanensari/frankfurter)
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).
Foreign exchange (forex) rates and currency conversion API
## Getting Started
Frankfurter is a free, open source API for current and historical foreign exchange rates. It tracks data published by the European Central Bank.
Rates are updated around 4PM CET every working day.
## Usage
## Examples
Get the current foreign exchange rates.
```http
GET /current
GET /latest HTTP/1.1
```
Get historical rates for any day since 1999.
```http
GET /2000-01-03
GET /2000-01-03 HTTP/1.1
```
Get historical rates for a time period.
```http
GET /2010-01-01..2010-01-31 HTTP/1.1
```
Rates quote against the Euro by default. Quote against a different currency.
```http
GET /current?from=USD
GET /latest?from=USD HTTP/1.1
```
Request specific exchange rates.
```http
GET /current?to=GBP
GET /latest?to=USD,GBP HTTP/1.1
```
Change the converted amount.
Convert a specific amount.
```http
GET /current?amount=100
GET /latest?amount=1000&from=GBP&to=USD HTTP/1.1
```
Finally, use all the above together.
With a full list of currencies, time series grow large in size. For better performance, use the to parameter to reduce the response weight.
```http
GET /current?from=EUR&to=GBP&amount=100
GET /2016-01-01..2016-12-31?from=GBP&to=USD HTTP/1.1
```
The primary use case is client side. For instance, with [money.js](https://openexchangerates.github.io/money.js/) in the browser
Here we return the current GBP/USD currency pair with JavaScript.
```js
let demo = () => {
let rate = fx(1).from("GBP").to("USD")
alert("£1 = $" + rate.toFixed(4))
}
fetch('https://yourdomain.com/current')
.then((resp) => resp.json())
.then((data) => fx.rates = data.rates)
.then(demo)
// Fetch and display GBP/USD
fetch('/latest?from=GBP&to=USD')
.then(resp => resp.json())
.then((data) => { alert(`GBPUSD = ${data.rates.USD}`); });
```
## Installation
Cache data whenever possible.
## Deployment
To build locally, type
@ -66,27 +73,19 @@ To build locally, type
docker-compose up -d
```
Now you can access the API at
Now you can access the API at `http://localhost:8080`.
```
http://localhost:8080
```
In production, first create a `.env` file based on [`.env.example`](.env.example). Then, run with
In production, create a [`.env`](.env.example) file and run with
```bash
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
```
To update to a newer image
To update to a newer image, run
```bash
docker-compose -f docker-compose.yml -f docker-compose.prod.yml pull
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
```
Within a few minutes, you will be able to access the API at
```
https://yourdomain.com:8080
```
Within a few minutes, you will access the API at `https://yourdomain.com`.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,109 +1,2 @@
body {
font-family: 'Open Sans', sans-serif;
}
strong {
background-color: #f8f9fa;
}
.navbar {
background-color: #337cf6;
font-size: .875rem;
padding-bottom: 0;
padding-top: 0;
}
.navbar .nav-link {
color: #fff;
}
.navbar .nav-link:hover {
color: #f8f9fa;
}
.container {
max-width: 600px;
}
.section {
padding-top: 1em;
}
.hero {
margin: 2em 0;
}
.hero .container {
text-align: center;
}
.hero .logo {
padding-bottom: 1em;
width: 100px;
}
.footer .container {
padding-bottom: 2em;
text-align: center;
}
.hljs {
background-color: #f8f9fa;
padding: 1em;
}
.hljs:hover {
cursor: pointer;
}
.hljs i {
color: #ced4da;
font-style: normal;
}
@media screen and (max-width: 767px) {
.hero .logo {
width: 75px;
}
#carbonads {
display: none;
}
}
@media screen and (min-width: 768px) {
#carbonads {
background-color: #f8f9fa;
position: absolute;
right: 24px;
top: 24px;
width: 150px;
}
.carbon-text {
color: #495057;
}
.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 {
color: #6c757d;
font-size: 11px;
}
}
body{background-color:#F08D5C;font-family:'Open Sans', sans-serif}.navbar .navbar-nav .nav-link{color:#fff;margin-bottom:2em;padding-left:12px;padding-right:12px}.navbar .navbar-nav .nav-link:hover{text-decoration:underline}.navbar .navbar-nav .nav-link .fa-fw{margin-right:6px}.content{margin-bottom:2em;max-width:720px}.content .logo img{padding-bottom:1em;width:120px}.content .logo,.content h1,.content h1+p+p{text-align:center}.content h1{font-size:56px}.content h1+p{display:none}.content h1+p+p{margin-bottom:2em}.content h1,.content h1+p+p{color:#FFF}.content h1+p+p,.content h2{font-size:20px}.content h2{font-weight:bold}code:not(.hljs){color:#8f1911}.hljs{border-radius:3px;padding:10px}.hljs.http{color:#2D4A53}.hljs i{color:#2D4A53}.hljs:hover{cursor:pointer}.footer{margin:2em 0;text-align:center}.footer .copyleft{display:inline-block;transform:rotate(180deg)}
/*# sourceMappingURL=application.css.map */

View File

@ -0,0 +1,98 @@
body {
background-color: #F08D5C;
font-family: 'Open Sans', sans-serif;
}
.navbar {
.navbar-nav {
.nav-link {
color: #fff;
margin-bottom: 2em;
padding-left: 12px;
padding-right: 12px;
&:hover {
text-decoration: underline;
}
.fa-fw {
margin-right: 6px;
}
}
}
}
.content {
margin-bottom: 2em;
max-width: 720px;
.logo {
img {
padding-bottom: 1em;
width: 120px;
}
}
.logo,
h1,
h1+p+p {
text-align: center;
}
h1 {
font-size: 56px;
&+p {
display: none;
}
&+p+p {
margin-bottom: 2em;
}
&,
&+p+p {
color: #FFF;
}
}
h1+p+p,
h2 {
font-size: 20px;
}
h2 {
font-weight: bold;
}
}
code:not(.hljs) {
color: #8f1911;
}
.hljs {
border-radius: 3px;
padding: 10px;
&.http {
color: #2D4A53;
}
i {
color: #2D4A53;
}
&:hover {
cursor: pointer;
}
}
.footer {
margin: 2em 0;
text-align: center;
.copyleft {
display:inline-block;
transform: rotate(180deg);
}
}

View File

@ -2,6 +2,7 @@
require 'oj'
require 'rack/cors'
require 'redcarpet'
require 'sass/plugin/rack'
require 'sinatra'
@ -15,11 +16,11 @@ use Rack::Cors do
end
end
Sass::Plugin.options[:style] = :compressed
css_location = File.join(Sinatra::Application.public_folder, 'stylesheets')
Sass::Plugin.options.update css_location: css_location,
style: :compressed
use Sass::Plugin::Rack
set :static_cache_control, [:public, max_age: 60]
configure :development do
set :show_exceptions, :after_handler
end
@ -33,12 +34,12 @@ configure :test do
end
helpers do
def versioned_stylesheet(stylesheet)
"/stylesheets/#{stylesheet}.css?" + File.mtime(File.join(Sinatra::Application.public_folder, 'stylesheets', 'sass', "#{stylesheet}.scss")).to_i.to_s
end
def versioned_javascript(javascript)
"/javascripts/#{javascript}.js?" + File.mtime(File.join(Sinatra::Application.public_folder, 'javascripts', "#{javascript}.js")).to_i.to_s
version = File.mtime(File.join(Sinatra::Application.public_folder,
'javascripts',
"#{javascript}.js")).to_i.to_s
"/javascripts/#{javascript}.js?#{version}"
end
def end_of_day_quote
@ -84,7 +85,13 @@ options '*' do
end
get '/' do
erb :index
# FIXME: We should cache this in production.
parser = Redcarpet::Markdown.new(Redcarpet::Render::HTML,
disable_indented_code_blocks: true,
fenced_code_blocks: true)
content = parser.render(File.read('README.md'))
erb :index, locals: { content: content }
end
get '/(?:latest|current)', mustermann_opts: { type: :regexp } do

View File

@ -1,122 +1,77 @@
<!doctype html>
<!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">
<meta name="description" content="Frankfurter is an open source API for current and historical foreign exchange rates. It tracks rates published daily by the European Central Bank.">
<title>Foreign exchange rates and currency conversion API | Frankfurter</title>
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/styles/solarized-dark.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,600">
<link rel="stylesheet" href="//use.fontawesome.com/releases/v5.3.1/css/all.css">
<link rel="stylesheet" href="//fonts.googleapis.com/css?family=Open+Sans">
<link rel="stylesheet" href="/stylesheets/application.css">
<link rel="shortcut icon" href="/images/favicon.ico">
<link rel="shortcut icon" href="/images/icon.png">
<% if ENV['GA_TRACKING_ID'] %>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=UA-36475354-1"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', <%= ENV['GA_TRACKING_ID'] %>);
</script>
<% end %>
</head>
<body>
<nav class="navbar navbar-expand">
<div class="container">
<ul class="navbar-nav mx-auto">
<li class="nav-item">
<a class="nav-link" href="https://github.com/hakanensari/frankfurter" target="_blank">Source code</a>
<a class="nav-link" href="https://github.com/hakanensari/frankfurter">
<i class="fab fa-github fa-fw fa-lg"></i>Source
</a>
</li>
<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>
<a class="nav-link" href="https://hub.docker.com/r/hakanensari/frankfurter/">
<i class="fab fa-docker fa-fw fa-lg"></i>Image
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://status.frankfurter.app/" target="_blank">Status</a>
<a class="nav-link" href="https://www.ecb.europa.eu/stats/exchange/eurofxref/html/index.en.html">
<i class="fas fa-file-alt fa-fw fa-lg"></i>Data sets
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://status.frankfurter.app/">
<i class="fas fa-signature fa-fw fa-lg"></i>Status
</a>
</li>
</ul>
</div>
</nav>
<section class="hero section">
<div class="container">
<image class="logo" src="/images/frankfurter.png" alt="">
<h1 class="display-4">Frankfurter</h1>
<p class="lead">
Foreign exchange rates and currency conversion API
</p>
</div>
</section>
<section class="introduction section">
<div class="container">
<p>
Frankfurter is an open-source API for current and historical forex rates published by the European Central Bank.
</p>
<p>
The API updates rates around 4PM CET every working day. Historical data goes back to early 1999.
</p>
<div class="content container">
<div class="logo">
<image src="/images/frankfurter.png" alt="">
</div>
</section>
<%= content %>
</div>
<section class="usage section">
<div class="footer">
<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 /2000-01-01 <i>HTTP/1.1</i></code></pre>
<p>
Get historical exchange rates for a given time period.
</p>
<pre><code class="http hljs small">GET /2000-01-01..2000-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 using <strong>amount</strong>.
</p>
<pre><code class="http hljs small">GET /current?amount=1000 <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 class="small">
<i class="far fa-heart"></i> Frankfurter
</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>
</div>
<script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/9.12.0/highlight.min.js"></script>
<script type="text/javascript" src="/javascripts/application.js"></script>
<script async type="text/javascript" src="//cdn.carbonads.com/carbon.js?serve=CK7D45QM&placement=frankfurterapp" id="_carbonads_js"></script>
</body>
</html>