mirror of
https://github.com/hakanensari/frankfurter.git
synced 2024-11-21 18:42:29 +01:00
Update homepage
This commit is contained in:
parent
8060f41566
commit
5789880a59
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
*.css*
|
||||
.bundle
|
||||
.env
|
||||
.sass-cache
|
||||
pkg
|
||||
public/stylesheets/*.css*
|
||||
|
||||
|
1
Gemfile
1
Gemfile
@ -9,6 +9,7 @@ gem 'ox'
|
||||
gem 'puma'
|
||||
gem 'rack-cors'
|
||||
gem 'rake'
|
||||
gem 'redcarpet'
|
||||
gem 'rufus-scheduler'
|
||||
gem 'sass'
|
||||
gem 'sequel_pg'
|
||||
|
@ -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
|
||||
|
65
README.md
65
README.md
@ -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 |
BIN
lib/web/public/images/icon.png
Normal file
BIN
lib/web/public/images/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
@ -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 */
|
||||
|
98
lib/web/public/stylesheets/sass/application.scss
Normal file
98
lib/web/public/stylesheets/sass/application.scss
Normal 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);
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user